From 66343adde8e8ff9f0b4442d8b31b6f898d097618 Mon Sep 17 00:00:00 2001 From: Bence Rochlitz <78215583+BenceVirtonomy@users.noreply.github.com> Date: Fri, 30 Jul 2021 16:18:51 +0200 Subject: [PATCH 01/40] Translate body part (#45) * Feature/contact stent 2 (#10) * PositionSolidBody added * boundary condition works but breaks numerically * no message * first unit test added * indentation change * PositionSolidBody test * remove file * small fixes * position constraint changed works between start and end time * scaling boundary condition * PositionScaleSolidBody unit test * particel adaptation changed * system resolution ratio added to particle adaptation * add googletest to docker and workflow * unit test removed temporarily * unit rest removed * SolidParticleRelaxation added to update initial particle position * gtest added again * gtest install changed * gtest * gtest * gtest * gtest * cd path updated * change to shared pointers * unit test pointers changed * gtest added to docker * initialization changed * disable tests for simbody * errors fixed * TranslationSolidBody added; translation cylinder * whitespace * fixes * TranslateSolidBody corrected * writeToFile update * change SET to set * catch(out_of_range& e) added * change to size_t * cmake minimum removed * BoundingBox test changed * fix writeToFIle * test change * Feature/position bc stent (#34) * PositionSolidBody added * boundary condition works but breaks numerically * no message * first unit test added * indentation change * PositionSolidBody test * remove file * small fixes * position constraint changed works between start and end time * scaling boundary condition * PositionScaleSolidBody unit test * particel adaptation changed * system resolution ratio added to particle adaptation * add googletest to docker and workflow * unit test removed temporarily * unit rest removed * SolidParticleRelaxation added to update initial particle position * gtest added again * gtest install changed * gtest * gtest * gtest * gtest * cd path updated * change to shared pointers * unit test pointers changed * gtest added to docker * initialization changed * errors fixed * TranslationSolidBody added; translation cylinder * whitespace * fixes * TranslateSolidBody corrected * writeToFile update * change SET to set * catch(out_of_range& e) added * change to size_t * cmake minimum removed * BoundingBox test changed * fix writeToFIle * test change Co-authored-by: JohnVirtonomy * -Werror only works for linux * TEST TranslateSolidBodyTuple * Update CMakeLists.txt remove werror flag for msvc * update from bitbucket 01.07.2021 * delete repeated files * brief information on google test in repo readme * delete clapack and simbody folder in root * google test works for windows but the unit test case did not copy stl file right in windows * now test_3d_unit works in windows. * information on installing google test in windows * add a comment on install ing for VS in windows * translation with same scale&unit test update * skip testing when [ci skip] in the commit message * update test_3d_unit - TranslateSolidBody not work * time-dependent-contact * number of time dep. contacts fixed * setup unit test for time dependent contact * Translate Solid Body fixed but tolerance is only 0.01 * output changed to 100 files always * 07.13.2021 update * typo * new files from bitbucket * update 07.13.2021 * time dependent contact fixed * time dep contact fixed * remove cout * time dep. contact tests finished * renaming * fix naming * move test class into seperate header * cmake changes * changing function names to lower case * change cmake list * small fix Co-authored-by: Ubuntu Co-authored-by: JohnVirtonomy Co-authored-by: Xiangyu Hu Co-authored-by: Wen-Yang Chu Co-authored-by: Xiangyu Hu Co-authored-by: Xiangyu Hu <37415154+Xiangyu-Hu@users.noreply.github.com> * TranslateSolidBodyPart added * particle relaxation fixed * change Get_ to get_ * checkIfPointInBoundingBox moved Co-authored-by: Ubuntu Co-authored-by: JohnVirtonomy Co-authored-by: Xiangyu Hu Co-authored-by: Wen-Yang Chu Co-authored-by: Xiangyu Hu Co-authored-by: Xiangyu Hu <37415154+Xiangyu-Hu@users.noreply.github.com> --- .../solid_structural_simulation_class.cpp | 65 ++++++++--- .../solid_structural_simulation_class.h | 9 ++ .../test_structural_simulation_class.h | 64 +++++------ .../particle_generator_network.h | 2 +- SPHINXsys/src/shared/bodies/base_body.h | 2 +- .../src/shared/common/sph_data_containers.cpp | 24 +++++ ...data_conainers.h => sph_data_containers.h} | 5 + .../src/shared/geometries/base_geometry.h | 2 +- SPHINXsys/src/shared/io_system/in_output.h | 2 +- SPHINXsys/src/shared/meshes/base_mesh.h | 2 +- .../base_particle_dynamics.h | 2 +- .../solid_dynamics/solid_dynamics.cpp | 49 ++++++--- .../solid_dynamics/solid_dynamics.h | 26 +++-- .../base_particle_generator.h | 2 +- .../src/shared/particles/base_particles.h | 2 +- .../shared/particles/particle_adaptation.h | 2 +- .../src/shared/particles/particle_sorting.h | 2 +- .../src/shared/simbody_sphinxsys/xml_engine.h | 2 +- .../src/shared/sphinxsys_system/sph_system.h | 2 +- .../position_based_boundary_conditions.cpp | 102 ++++++++++++++---- .../time_dep_contact.cpp | 26 ++--- 21 files changed, 276 insertions(+), 118 deletions(-) create mode 100644 SPHINXsys/src/shared/common/sph_data_containers.cpp rename SPHINXsys/src/shared/common/{sph_data_conainers.h => sph_data_containers.h} (95%) diff --git a/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp b/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp index e31f3a9f07..5795fe82f7 100644 --- a/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp +++ b/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp @@ -58,7 +58,7 @@ void expandBoundingBox(BoundingBox* original, BoundingBox* additional) } void relaxParticlesSingleResolution(In_Output* in_output, - bool write_particles_to_file, + bool write_particle_relaxation_data, ImportedModel* imported_model, ElasticSolidParticles* imported_model_particles, BodyRelationInner* imported_model_inner) @@ -78,15 +78,11 @@ void relaxParticlesSingleResolution(In_Output* in_output, //---------------------------------------------------------------------- random_imported_model_particles.parallel_exec(0.25); relaxation_step_inner.surface_bounding_.parallel_exec(); - if (write_particles_to_file) + if (write_particle_relaxation_data) { write_imported_model_to_vtu.writeToFile(0.0); } imported_model->updateCellLinkedList(); - if (write_particles_to_file) - { - mesh_cell_linked_list_recording.writeToFile(0.0); - } //---------------------------------------------------------------------- // Particle relaxation time stepping start here. //---------------------------------------------------------------------- @@ -97,14 +93,14 @@ void relaxParticlesSingleResolution(In_Output* in_output, ite_p += 1; if (ite_p % 100 == 0) { - cout << fixed << setprecision(9) << "Relaxation steps for the imported model N = " << ite_p << "\n"; - if (write_particles_to_file) + std::cout << std::fixed << std::setprecision(9) << "Relaxation steps for the imported model N = " << ite_p << "\n"; + if (write_particle_relaxation_data) { - write_imported_model_to_vtu.writeToFile(Real(ite_p) * 1.0e-4); + write_imported_model_to_vtu.writeToFile(ite_p); } } } - cout << "The physics relaxation process of imported model finish !" << endl; + std::cout << "The physics relaxation process of the imported model finished !" << std::endl; } StructuralSimulationInput::StructuralSimulationInput( @@ -131,6 +127,7 @@ StructuralSimulationInput::StructuralSimulationInput( // particle_relaxation option particle_relaxation_list_ = {}; for (size_t i = 0; i < resolution_list_.size(); i++){ particle_relaxation_list_.push_back(true); } + write_particle_relaxation_data_ = false; // scale system boundaries scale_system_boundaries_ = 1; // boundary conditions @@ -141,6 +138,7 @@ StructuralSimulationInput::StructuralSimulationInput( position_solid_body_tuple_ = {}; position_scale_solid_body_tuple_ = {}; translation_solid_body_tuple_ = {}; + translation_solid_body_part_tuple_ = {}; }; /////////////////////////////////////// @@ -161,6 +159,7 @@ StructuralSimulation::StructuralSimulation(StructuralSimulationInput& input): // default system, optional: particle relaxation, scale_system_boundaries particle_relaxation_list_(input.particle_relaxation_list_), + write_particle_relaxation_data_(input.write_particle_relaxation_data_), system_resolution_(0.0), system_(SPHSystem(BoundingBox(Vec3d(0), Vec3d(0)), system_resolution_)), scale_system_boundaries_(input.scale_system_boundaries_), @@ -173,7 +172,8 @@ StructuralSimulation::StructuralSimulation(StructuralSimulationInput& input): body_indeces_fixed_constraint_(input.body_indeces_fixed_constraint_), position_solid_body_tuple_(input.position_solid_body_tuple_), position_scale_solid_body_tuple_(input.position_scale_solid_body_tuple_), - translation_solid_body_tuple_(input.translation_solid_body_tuple_) + translation_solid_body_tuple_(input.translation_solid_body_tuple_), + translation_solid_body_part_tuple_(input.translation_solid_body_part_tuple_) { // scaling of translation and resolution scaleTranslationAndResolution(); @@ -199,6 +199,7 @@ StructuralSimulation::StructuralSimulation(StructuralSimulationInput& input): initializePositionSolidBody(); initializePositionScaleSolidBody(); initializeTranslateSolidBody(); + initializeTranslateSolidBodyPart(); // initialize simulation initializeSimulation(); @@ -282,7 +283,7 @@ void StructuralSimulation::initializeElasticSolidBodies() solid_body_list_.emplace_back(make_shared(system_, imported_stl_list_[i], body_mesh_list_[i], particle_adaptation_list_[i], physical_viscosity_, material_model_list_[i])); if (particle_relaxation_list_[i]) { - relaxParticlesSingleResolution(&in_output_, false, solid_body_list_[i]->getImportedModel(), solid_body_list_[i]->getElasticSolidParticles(), solid_body_list_[i]->getInnerBodyRelation()); + relaxParticlesSingleResolution(&in_output_, write_particle_relaxation_data_, solid_body_list_[i]->getImportedModel(), solid_body_list_[i]->getElasticSolidParticles(), solid_body_list_[i]->getInnerBodyRelation()); } } } @@ -355,7 +356,7 @@ void StructuralSimulation::initializeAccelerationForBodyPartInBoundingBox() { SolidBody* solid_body = solid_body_list_[get<0>(acceleration_bounding_box_tuple_[i])]->getImportedModel(); acceleration_bounding_box_.emplace_back(make_shared - (solid_body, &get<1>(acceleration_bounding_box_tuple_[i]), get<2>(acceleration_bounding_box_tuple_[i]))); + (solid_body, get<1>(acceleration_bounding_box_tuple_[i]), get<2>(acceleration_bounding_box_tuple_[i]))); } } @@ -419,9 +420,29 @@ void StructuralSimulation::initializeTranslateSolidBody() Real start_time = get<1>(translation_solid_body_tuple_[i]); Real end_time = get<2>(translation_solid_body_tuple_[i]); Vecd translation = get<3>(translation_solid_body_tuple_[i]); - BodyPartByParticleTriMesh* bp = new BodyPartByParticleTriMesh(solid_body_list_[body_index]->getImportedModel(), imported_stl_list_[body_index], &body_mesh_list_[body_index]); + BodyPartByParticleTriMesh* bp = new BodyPartByParticleTriMesh( + solid_body_list_[body_index]->getImportedModel(), imported_stl_list_[body_index], &body_mesh_list_[body_index]); + + translation_solid_body_.emplace_back(make_shared( + solid_body_list_[body_index]->getImportedModel(), bp, start_time, end_time, translation)); + } +} + +void StructuralSimulation::initializeTranslateSolidBodyPart() +{ + translation_solid_body_part_ = {}; + for (size_t i = 0; i < translation_solid_body_part_tuple_.size(); i++) + { + int body_index = get<0>(translation_solid_body_part_tuple_[i]); + Real start_time = get<1>(translation_solid_body_part_tuple_[i]); + Real end_time = get<2>(translation_solid_body_part_tuple_[i]); + Vecd translation = get<3>(translation_solid_body_part_tuple_[i]); + BoundingBox bbox = get<4>(translation_solid_body_part_tuple_[i]); + BodyPartByParticleTriMesh* bp = new BodyPartByParticleTriMesh( + solid_body_list_[body_index]->getImportedModel(), imported_stl_list_[body_index], &body_mesh_list_[body_index]); - translation_solid_body_.emplace_back(make_shared(solid_body_list_[body_index]->getImportedModel(), bp, start_time, end_time, translation)); + translation_solid_body_part_.emplace_back(make_shared( + solid_body_list_[body_index]->getImportedModel(), bp, start_time, end_time, translation, bbox)); } } @@ -547,6 +568,14 @@ void StructuralSimulation::executeTranslateSolidBody(Real dt) } } +void StructuralSimulation::executeTranslateSolidBodyPart(Real dt) +{ + for (size_t i = 0; i < translation_solid_body_part_.size(); i++) + { + translation_solid_body_part_[i]->parallel_exec(dt); + } +} + void StructuralSimulation::executeDamping(Real dt) { for (size_t i = 0; i < solid_body_list_.size(); i++) @@ -603,9 +632,12 @@ void StructuralSimulation::runSimulationStep(int &ite, Real &dt, Real &integrati if (ite % 100 == 0) cout << "N=" << ite << " Time: " << GlobalStaticVariables::physical_time_ << " dt: " << dt << "\n"; /** ACTIVE BOUNDARY CONDITIONS */ + // force (acceleration) based executeinitializeATimeStep(); executeAccelerationForBodyPartInBoundingBox(); executeSpringDamperConstraintParticleWise(); + // velocity based + executeTranslateSolidBodyPart(dt); /** CONTACT */ executeContactDensitySummation(); @@ -617,14 +649,13 @@ void StructuralSimulation::runSimulationStep(int &ite, Real &dt, Real &integrati executeConstrainSolidBodyRegion(); executePositionSolidBody(dt); executePositionScaleSolidBody(dt); - executeTranslateSolidBody(dt); + executeTranslateSolidBody(dt); // only one time executeDamping(dt); executeConstrainSolidBodyRegion(); executePositionSolidBody(dt); executePositionScaleSolidBody(dt); - executeTranslateSolidBody(dt); executeStressRelaxationSecondHalf(dt); diff --git a/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.h b/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.h index b66783c9fa..cefc7c2abb 100644 --- a/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.h +++ b/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.h @@ -21,6 +21,7 @@ using SpringDamperTuple = tuple; using PositionSolidBodyTuple = tuple; using PositionScaleSolidBodyTuple = tuple; using TranslateSolidBodyTuple = tuple; +using TranslateSolidBodyPartTuple = tuple; class BodyPartByParticleTriMesh : public BodyPartByParticle { @@ -89,6 +90,7 @@ class StructuralSimulationInput Real scale_system_boundaries_; // particle relaxation vector particle_relaxation_list_; + bool write_particle_relaxation_data_; // boundary conditions vector non_zero_gravity_; vector acceleration_bounding_box_tuple_; @@ -97,6 +99,7 @@ class StructuralSimulationInput vector position_solid_body_tuple_; vector position_scale_solid_body_tuple_; vector translation_solid_body_tuple_; + vector translation_solid_body_part_tuple_; StructuralSimulationInput( string relative_input_path, @@ -124,6 +127,7 @@ class StructuralSimulation vector> contacting_body_pairs_list_; vector, array>> time_dep_contacting_body_pairs_list_; //optional: time dependent contact vector particle_relaxation_list_; // optional: particle relaxation + bool write_particle_relaxation_data_; // internal members Real system_resolution_; @@ -160,6 +164,9 @@ class StructuralSimulation // for TranslateSolidBody vector> translation_solid_body_; vector translation_solid_body_tuple_; + // for TranslateSolidBodyPart + vector> translation_solid_body_part_; + vector translation_solid_body_part_tuple_; // for constructor, the order is important void scaleTranslationAndResolution(); @@ -179,6 +186,7 @@ class StructuralSimulation void initializePositionSolidBody(); void initializePositionScaleSolidBody(); void initializeTranslateSolidBody(); + void initializeTranslateSolidBodyPart(); // for runSimulation, the order is important void executeCorrectConfiguration(); @@ -192,6 +200,7 @@ class StructuralSimulation void executePositionSolidBody(Real dt); void executePositionScaleSolidBody(Real dt); void executeTranslateSolidBody(Real dt); + void executeTranslateSolidBodyPart(Real dt); void executeDamping(Real dt); void executeStressRelaxationSecondHalf(Real dt); void executeUpdateCellLinkedList(); diff --git a/SPHINXsys/src/for_3D_build/high_level_simulation_class/test_structural_simulation_class.h b/SPHINXsys/src/for_3D_build/high_level_simulation_class/test_structural_simulation_class.h index 6019e27559..c821888f97 100644 --- a/SPHINXsys/src/for_3D_build/high_level_simulation_class/test_structural_simulation_class.h +++ b/SPHINXsys/src/for_3D_build/high_level_simulation_class/test_structural_simulation_class.h @@ -8,45 +8,45 @@ class TestStructuralSimulation : StructuralSimulation TestStructuralSimulation(StructuralSimulationInput& input) : StructuralSimulation(input){}; void TestRunSimulation(Real end_time){ runSimulation(end_time); }; // input members - string Get_relative_input_path_(){ return relative_input_path_; }; - vector Get_imported_stl_list_(){ return imported_stl_list_; }; - Real Get_scale_stl_(){ return scale_stl_; }; - vector Get_translation_list_(){ return translation_list_; }; - Real Get_system_resolution_(){ return system_resolution_; }; - vector Get_resolution_list_(){ return resolution_list_; }; - vector Get_material_model_list_(){ return material_model_list_; }; - Real Get_physical_viscosity_(){ return physical_viscosity_; }; + string get_relative_input_path_(){ return relative_input_path_; }; + vector get_imported_stl_list_(){ return imported_stl_list_; }; + Real get_scale_stl_(){ return scale_stl_; }; + vector get_translation_list_(){ return translation_list_; }; + Real get_system_resolution_(){ return system_resolution_; }; + vector get_resolution_list_(){ return resolution_list_; }; + vector get_material_model_list_(){ return material_model_list_; }; + Real get_physical_viscosity_(){ return physical_viscosity_; }; // internal members - SPHSystem Get_system_(){ return system_; }; - Real Get_scale_system_boundaries_(){ return scale_system_boundaries_; }; - In_Output Get_in_output_(){ return in_output_; }; + SPHSystem get_system_(){ return system_; }; + Real get_scale_system_boundaries_(){ return scale_system_boundaries_; }; + In_Output get_in_output_(){ return in_output_; }; // other - vector Get_body_mesh_list_(){ return body_mesh_list_; }; - vector> Get_solid_body_list_(){ return solid_body_list_; }; - vector> Get_contacting_body_pairs_list_(){ return contacting_body_pairs_list_; }; - vector, array>> Get_time_dep_contacting_body_pairs_list_(){ return time_dep_contacting_body_pairs_list_; }; - vector> Get_contact_list_(){ return contact_list_; }; - vector> Get_contact_density_list_(){ return contact_density_list_; }; - vector> Get_contact_force_list_(){ return contact_force_list_; }; + vector get_body_mesh_list_(){ return body_mesh_list_; }; + vector> get_solid_body_list_(){ return solid_body_list_; }; + vector> get_contacting_body_pairs_list_(){ return contacting_body_pairs_list_; }; + vector, array>> get_time_dep_contacting_body_pairs_list_(){ return time_dep_contacting_body_pairs_list_; }; + vector> get_contact_list_(){ return contact_list_; }; + vector> get_contact_density_list_(){ return contact_density_list_; }; + vector> get_contact_force_list_(){ return contact_force_list_; }; // for initializeATimeStep - vector> Get_initialize_gravity_(){ return initialize_gravity_; }; - vector Get_non_zero_gravity_(){ return non_zero_gravity_; }; + vector> get_initialize_gravity_(){ return initialize_gravity_; }; + vector get_non_zero_gravity_(){ return non_zero_gravity_; }; // for AccelerationForBodyPartInBoundingBox - vector> Get_acceleration_bounding_box_(){ return acceleration_bounding_box_; }; - vector Get_acceleration_bounding_box_tuple_(){ return acceleration_bounding_box_tuple_; }; + vector> get_acceleration_bounding_box_(){ return acceleration_bounding_box_; }; + vector get_acceleration_bounding_box_tuple_(){ return acceleration_bounding_box_tuple_; }; // for SpringDamperConstraintParticleWise - vector> Get_spring_damper_constraint_(){ return spring_damper_constraint_; }; - vector Get_spring_damper_tuple_(){ return spring_damper_tuple_; }; + vector> get_spring_damper_constraint_(){ return spring_damper_constraint_; }; + vector get_spring_damper_tuple_(){ return spring_damper_tuple_; }; // for ConstrainSolidBodyRegion - vector> Get_fixed_constraint_(){ return fixed_constraint_; }; - vector Get_body_indeces_fixed_constraint_(){ return body_indeces_fixed_constraint_; }; + vector> get_fixed_constraint_(){ return fixed_constraint_; }; + vector get_body_indeces_fixed_constraint_(){ return body_indeces_fixed_constraint_; }; // for PositionSolidBody - vector> Get_position_solid_body_(){ return position_solid_body_; }; - vector Get_position_solid_body_tuple_(){ return position_solid_body_tuple_; }; + vector> get_position_solid_body_(){ return position_solid_body_; }; + vector get_position_solid_body_tuple_(){ return position_solid_body_tuple_; }; // for PositionScaleSolidBody - vector> Get_position_scale_solid_body_(){ return position_scale_solid_body_; }; - vector Get_position_scale_solid_body_tuple_(){ return position_scale_solid_body_tuple_; }; + vector> get_position_scale_solid_body_(){ return position_scale_solid_body_; }; + vector get_position_scale_solid_body_tuple_(){ return position_scale_solid_body_tuple_; }; // for TranslateSolidBody - vector> Get_translation_solid_body_(){ return translation_solid_body_; }; - vector Get_translation_solid_body_tuple_(){ return translation_solid_body_tuple_; }; + vector> get_translation_solid_body_(){ return translation_solid_body_; }; + vector get_translation_solid_body_tuple_(){ return translation_solid_body_tuple_; }; }; \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.h b/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.h index 1019c9e3f4..7416f9f8ef 100644 --- a/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.h +++ b/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.h @@ -31,7 +31,7 @@ #define PARTICLE_GENERATOR_NETWORK_H -#include "sph_data_conainers.h" +#include "sph_data_containers.h" #include "base_particle_generator.h" #include "generative_structures.h" diff --git a/SPHINXsys/src/shared/bodies/base_body.h b/SPHINXsys/src/shared/bodies/base_body.h index c722a59097..eff5bb9b4d 100644 --- a/SPHINXsys/src/shared/bodies/base_body.h +++ b/SPHINXsys/src/shared/bodies/base_body.h @@ -37,7 +37,7 @@ #define BASE_BODY_H #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" #include "particle_adaptation.h" #include "all_particle_generators.h" #include "particle_sorting.h" diff --git a/SPHINXsys/src/shared/common/sph_data_containers.cpp b/SPHINXsys/src/shared/common/sph_data_containers.cpp new file mode 100644 index 0000000000..770b9bc56e --- /dev/null +++ b/SPHINXsys/src/shared/common/sph_data_containers.cpp @@ -0,0 +1,24 @@ +/** + * @file sph_data_containers.cpp + * @brief Global functions related to sph_data_containers.h + * @author Bence Rochlitz, Luhui Han, Chi ZHang and Xiangyu Hu +*/ + +#include "sph_data_containers.h" + +namespace SPH { +//=================================================================================================// +bool checkIfPointInBoundingBox(Vec3d point, BoundingBox& bbox) +{ + return point[0] >= bbox.first[0] && point[0] <= bbox.second[0] && + point[1] >= bbox.first[1] && point[1] <= bbox.second[1] && + point[2] >= bbox.first[2] && point[2] <= bbox.second[2]; +} +//=================================================================================================// +bool checkIfPointInBoundingBox(Vec2d point, BoundingBox& bbox) +{ + return point[0] >= bbox.first[0] && point[0] <= bbox.second[0] && + point[1] >= bbox.first[1] && point[1] <= bbox.second[1]; +} +//=================================================================================================// +} \ No newline at end of file diff --git a/SPHINXsys/src/shared/common/sph_data_conainers.h b/SPHINXsys/src/shared/common/sph_data_containers.h similarity index 95% rename from SPHINXsys/src/shared/common/sph_data_conainers.h rename to SPHINXsys/src/shared/common/sph_data_containers.h index f32a75835c..9d0b783df1 100644 --- a/SPHINXsys/src/shared/common/sph_data_conainers.h +++ b/SPHINXsys/src/shared/common/sph_data_containers.h @@ -8,6 +8,7 @@ #define SPH_DATA_CONTAINERS_H #include "base_data_package.h" +#include "base_data_type.h" namespace SPH { /** @@ -24,6 +25,10 @@ namespace SPH { /** Bounding box for system, body, body part and shape, first: lower bound, second: upper bound. */ typedef std::pair BoundingBox; + /** Check if a point is inside the bounding box */ + bool checkIfPointInBoundingBox(Vec3d point, BoundingBox& bbox); + bool checkIfPointInBoundingBox(Vec2d point, BoundingBox& bbox); + /** Generalized particle data type */ typedef std::tuple*>, StdVec*>, StdVec*>, StdVec*>> ParticleData; diff --git a/SPHINXsys/src/shared/geometries/base_geometry.h b/SPHINXsys/src/shared/geometries/base_geometry.h index b43f7c58d1..add285b743 100644 --- a/SPHINXsys/src/shared/geometries/base_geometry.h +++ b/SPHINXsys/src/shared/geometries/base_geometry.h @@ -36,7 +36,7 @@ #include "base_particles.h" #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" #include diff --git a/SPHINXsys/src/shared/io_system/in_output.h b/SPHINXsys/src/shared/io_system/in_output.h index 58be9a013e..5a5a12ff3d 100644 --- a/SPHINXsys/src/shared/io_system/in_output.h +++ b/SPHINXsys/src/shared/io_system/in_output.h @@ -30,7 +30,7 @@ #define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" #include "all_physical_dynamics.h" #include "xml_engine.h" diff --git a/SPHINXsys/src/shared/meshes/base_mesh.h b/SPHINXsys/src/shared/meshes/base_mesh.h index 06fc818c2e..ef684a6f23 100644 --- a/SPHINXsys/src/shared/meshes/base_mesh.h +++ b/SPHINXsys/src/shared/meshes/base_mesh.h @@ -38,7 +38,7 @@ #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" #include "my_memory_pool.h" #include diff --git a/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.h index f5d7afd003..0acb33935b 100644 --- a/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.h +++ b/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.h @@ -33,7 +33,7 @@ #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" #include "all_particles.h" #include "all_materials.h" #include "neighbor_relation.h" diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.cpp index f2b0284dae..0f08dec00f 100644 --- a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.cpp +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.cpp @@ -369,7 +369,6 @@ namespace SPH PartSimpleDynamicsByParticle(body, body_part), SolidDataSimple(body), pos_n_(particles_->pos_n_), pos_0_(particles_->pos_0_), vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), - vel_ave_(particles_->vel_ave_), dvel_dt_ave_(particles_->dvel_dt_ave_), start_time_(start_time), end_time_(end_time), translation_(translation) {} //=================================================================================================// @@ -382,7 +381,7 @@ namespace SPH // distance left to reach the final position Vecd translation_left = translation_ * (end_time_ - GlobalStaticVariables::physical_time_) / (end_time_ - start_time_); // displacement is a portion of distance left, scaled by dt and remaining time - displacement = 0.5 * translation_left * dt / (end_time_ - GlobalStaticVariables::physical_time_); + displacement = translation_left * dt / (end_time_ - GlobalStaticVariables::physical_time_); } catch(out_of_range& e){ throw runtime_error(string("TranslateSolidBody::getDisplacement: particle index out of bounds") + to_string(index_i)); @@ -392,20 +391,40 @@ namespace SPH //=================================================================================================// void TranslateSolidBody::Update(size_t index_i, Real dt) { - try { - // only apply in the defined time period - if (GlobalStaticVariables::physical_time_ >= start_time_ && GlobalStaticVariables::physical_time_ <= end_time_) - { + // only apply in the defined time period + if (GlobalStaticVariables::physical_time_ >= start_time_ && GlobalStaticVariables::physical_time_ <= end_time_) + { + try { pos_n_[index_i] = pos_n_[index_i] + getDisplacement(index_i, dt); // displacement from the initial position vel_n_[index_i] = getVelocity(); dvel_dt_[index_i] = getAcceleration(); - /** the average values are prescirbed also. */ - vel_ave_[index_i] = vel_n_[index_i]; - dvel_dt_ave_[index_i] = dvel_dt_[index_i]; + } + catch(out_of_range& e){ + throw runtime_error(string("TranslateSolidBody::Update: particle index out of bounds") + to_string(index_i)); } } - catch(out_of_range& e){ - throw runtime_error(string("TranslateSolidBody::Update: particle index out of bounds") + to_string(index_i)); + } + //=================================================================================================// + TranslateSolidBodyPart:: + TranslateSolidBodyPart(SPHBody* body, BodyPartByParticle* body_part, Real start_time, Real end_time, Vecd translation, BoundingBox bbox): + TranslateSolidBody(body, body_part, start_time, end_time, translation), bbox_(bbox) + {} + //=================================================================================================// + void TranslateSolidBodyPart::Update(size_t index_i, Real dt) + { + // only apply in the defined time period + if (GlobalStaticVariables::physical_time_ >= start_time_ && GlobalStaticVariables::physical_time_ <= end_time_) + { + try { + Vecd point = pos_0_[index_i]; + if (checkIfPointInBoundingBox(point, bbox_)) + { + vel_n_[index_i] = getDisplacement(index_i, dt) / dt; + } + } + catch(out_of_range& e){ + throw runtime_error(string("TranslateSolidBodyPart::Update: particle index out of bounds") + to_string(index_i)); + } } } //=================================================================================================// @@ -557,7 +576,7 @@ namespace SPH } //=================================================================================================// AccelerationForBodyPartInBoundingBox:: - AccelerationForBodyPartInBoundingBox(SolidBody* body, BoundingBox* bounding_box, Vecd acceleration) : + AccelerationForBodyPartInBoundingBox(SolidBody* body, BoundingBox& bounding_box, Vecd acceleration) : ParticleDynamicsSimple(body), SolidDataSimple(body), pos_n_(particles_->pos_n_), dvel_dt_prior_(particles_->dvel_dt_prior_), @@ -574,11 +593,7 @@ namespace SPH if (pos_n_.size() > index_i) { Vecd point = pos_n_[index_i]; - if (point.size() >= 3 && bounding_box_ != nullptr && bounding_box_->first.size() >= 3 && - bounding_box_->second.size() >= 3 && point[0] >= bounding_box_->first[0] && - point[0] <= bounding_box_->second[0] && - point[1] >= bounding_box_->first[1] && point[1] <= bounding_box_->second[1] && - point[2] >= bounding_box_->first[2] && point[2] <= bounding_box_->second[2]) + if (checkIfPointInBoundingBox(point, bounding_box_)) { dvel_dt_prior_[index_i] += acceleration_; } diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.h index b220d272aa..764b6fd943 100644 --- a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.h +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.h @@ -241,7 +241,7 @@ namespace SPH virtual void Update(size_t index_i, Real dt = 0.0) override; }; - /** + /** * @class TranslateSolidBody * @brief Translates the body in a given time interval -translation driven boundary condition; only moving the body; end position irrelevant; * Note the average values for FSI are prescirbed also. @@ -252,11 +252,9 @@ namespace SPH public: TranslateSolidBody(SPHBody* body, BodyPartByParticle* body_part, Real start_time, Real end_time, Vecd translation); virtual ~TranslateSolidBody() {}; - StdLargeVec& GetParticlePos0(){ return pos_0_; }; - StdLargeVec& GetParticlePosN(){ return pos_n_; }; protected: StdLargeVec& pos_n_, &pos_0_; - StdLargeVec& vel_n_, &dvel_dt_, &vel_ave_, &dvel_dt_ave_; + StdLargeVec& vel_n_, &dvel_dt_; Real start_time_, end_time_; Vecd translation_; Vecd getDisplacement(size_t index_i, Real dt); @@ -266,6 +264,22 @@ namespace SPH virtual void Update(size_t index_i, Real dt = 0.0) override; }; + /** + * @class TranslateSolidBodyPart + * @brief Translates the body in a given time interval -translation driven boundary condition; only moving the body; end position irrelevant; + * Only the particles in a given Bounding Box are translated. The Bounding Box is defined for the undeformed shape. + * Note the average values for FSI are prescirbed also. + */ + class TranslateSolidBodyPart: public TranslateSolidBody + { + public: + TranslateSolidBodyPart(SPHBody* body, BodyPartByParticle* body_part, Real start_time, Real end_time, Vecd translation, BoundingBox bbox); + virtual ~TranslateSolidBodyPart() {}; + protected: + BoundingBox bbox_; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + /** * @class ConstrainSolidBodyRegionVelocity * @brief Constrain the velocity of a solid body part. @@ -397,11 +411,11 @@ namespace SPH : public ParticleDynamicsSimple, public SolidDataSimple { public: - AccelerationForBodyPartInBoundingBox(SolidBody* body, BoundingBox* bounding_box, Vecd acceleration); + AccelerationForBodyPartInBoundingBox(SolidBody* body, BoundingBox& bounding_box, Vecd acceleration); virtual ~AccelerationForBodyPartInBoundingBox() {}; protected: StdLargeVec& pos_n_,& dvel_dt_prior_; - BoundingBox* bounding_box_; + BoundingBox bounding_box_; Vecd acceleration_; virtual void setupDynamics(Real dt = 0.0) override; virtual void Update(size_t index_i, Real dt = 0.0) override; diff --git a/SPHINXsys/src/shared/particle_generator/base_particle_generator.h b/SPHINXsys/src/shared/particle_generator/base_particle_generator.h index d60cd2a27a..36ee2bc547 100644 --- a/SPHINXsys/src/shared/particle_generator/base_particle_generator.h +++ b/SPHINXsys/src/shared/particle_generator/base_particle_generator.h @@ -34,7 +34,7 @@ #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" namespace SPH { diff --git a/SPHINXsys/src/shared/particles/base_particles.h b/SPHINXsys/src/shared/particles/base_particles.h index 75ee9ea9ee..e52fdb05ef 100644 --- a/SPHINXsys/src/shared/particles/base_particles.h +++ b/SPHINXsys/src/shared/particles/base_particles.h @@ -34,7 +34,7 @@ #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" #include "xml_engine.h" #include diff --git a/SPHINXsys/src/shared/particles/particle_adaptation.h b/SPHINXsys/src/shared/particles/particle_adaptation.h index f86b6aa93f..bd1b19fa5a 100644 --- a/SPHINXsys/src/shared/particles/particle_adaptation.h +++ b/SPHINXsys/src/shared/particles/particle_adaptation.h @@ -34,7 +34,7 @@ #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" namespace SPH { diff --git a/SPHINXsys/src/shared/particles/particle_sorting.h b/SPHINXsys/src/shared/particles/particle_sorting.h index befbf0c5be..c1c150e63d 100644 --- a/SPHINXsys/src/shared/particles/particle_sorting.h +++ b/SPHINXsys/src/shared/particles/particle_sorting.h @@ -34,7 +34,7 @@ #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" /** this is a reformulation of tbb parallel_sort for particle data */ namespace tbb { diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.h b/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.h index ffa0dea909..0ad1258db8 100644 --- a/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.h +++ b/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.h @@ -33,7 +33,7 @@ #define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" #include "SimTKcommon.h" #include "SimTKcommon/internal/Xml.h" diff --git a/SPHINXsys/src/shared/sphinxsys_system/sph_system.h b/SPHINXsys/src/shared/sphinxsys_system/sph_system.h index 1a90115787..3439ce9176 100644 --- a/SPHINXsys/src/shared/sphinxsys_system/sph_system.h +++ b/SPHINXsys/src/shared/sphinxsys_system/sph_system.h @@ -19,7 +19,7 @@ namespace po = boost::program_options; #endif #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" #include #include diff --git a/cases_high_level_simulation/test_3d_unit_position_based_bc/position_based_boundary_conditions.cpp b/cases_high_level_simulation/test_3d_unit_position_based_bc/position_based_boundary_conditions.cpp index 52d0771b72..e8fd8ea448 100644 --- a/cases_high_level_simulation/test_3d_unit_position_based_bc/position_based_boundary_conditions.cpp +++ b/cases_high_level_simulation/test_3d_unit_position_based_bc/position_based_boundary_conditions.cpp @@ -63,45 +63,45 @@ TEST(StructuralSimulation, PositionSolidBodyTuple) //=================================================================================================// // test scaleTranslationAndResolution(); - EXPECT_EQ(sim.Get_translation_list_().size(), sim.Get_resolution_list_().size()); + EXPECT_EQ(sim.get_translation_list_().size(), sim.get_resolution_list_().size()); for (size_t i = 0; i < translation_list.size(); i++) { - EXPECT_EQ(sim.Get_translation_list_()[i], translation_list[i] * scale_stl); - EXPECT_EQ(sim.Get_resolution_list_()[i], resolution_list[i] * scale_stl); + EXPECT_EQ(sim.get_translation_list_()[i], translation_list[i] * scale_stl); + EXPECT_EQ(sim.get_resolution_list_()[i], resolution_list[i] * scale_stl); } - EXPECT_EQ(sim.Get_system_resolution_(), resolution_mass * scale_stl); + EXPECT_EQ(sim.get_system_resolution_(), resolution_mass * scale_stl); //=================================================================================================// // test createBodyMeshList(); - EXPECT_EQ(sim.Get_body_mesh_list_().size(), number_of_bodies); + EXPECT_EQ(sim.get_body_mesh_list_().size(), number_of_bodies); //=================================================================================================// // test calculateSystemBoundaries(); Real ball_radius = 100 * scale_stl * 0.5; BoundingBox test_bounds(Vec3d(-ball_radius * scale_system_bounds), Vec3d(ball_radius * scale_system_bounds)); for (size_t i = 0; i < 3; i++) { - EXPECT_NEAR(sim.Get_system_().system_domain_bounds_.first[i], test_bounds.first[i], abs(test_bounds.first[i] * tolerance)); - EXPECT_NEAR(sim.Get_system_().system_domain_bounds_.second[i], test_bounds.second[i], abs(test_bounds.first[i] * tolerance)); + EXPECT_NEAR(sim.get_system_().system_domain_bounds_.first[i], test_bounds.first[i], abs(test_bounds.first[i] * tolerance)); + EXPECT_NEAR(sim.get_system_().system_domain_bounds_.second[i], test_bounds.second[i], abs(test_bounds.first[i] * tolerance)); } //=================================================================================================// // test InitializeElasticSolidBodies(); - EXPECT_EQ(sim.Get_solid_body_list_().size(), number_of_bodies); + EXPECT_EQ(sim.get_solid_body_list_().size(), number_of_bodies); //=================================================================================================// // test InitializeAllContacts(); - EXPECT_EQ(sim.Get_contacting_body_pairs_list_().size(), 0); - EXPECT_EQ(sim.Get_contact_list_().size(), 0); - EXPECT_EQ(sim.Get_contact_density_list_().size(), 0); - EXPECT_EQ(sim.Get_contact_force_list_().size(), 0); + EXPECT_EQ(sim.get_contacting_body_pairs_list_().size(), 0); + EXPECT_EQ(sim.get_contact_list_().size(), 0); + EXPECT_EQ(sim.get_contact_density_list_().size(), 0); + EXPECT_EQ(sim.get_contact_force_list_().size(), 0); //=================================================================================================// // test Boundary Conditions - EXPECT_EQ(sim.Get_position_solid_body_().size(), 2); - EXPECT_EQ(sim.Get_position_solid_body_tuple_().size(), 2); + EXPECT_EQ(sim.get_position_solid_body_().size(), 2); + EXPECT_EQ(sim.get_position_solid_body_tuple_().size(), 2); //=================================================================================================// //=================================================================================================// /** START SIMULATION */ sim.TestRunSimulation(end_time); - StdLargeVec& pos_0 = sim.Get_position_solid_body_()[0]->GetParticlePos0(); - StdLargeVec& pos_n = sim.Get_position_solid_body_()[0]->GetParticlePosN(); + StdLargeVec& pos_0 = sim.get_position_solid_body_()[0]->GetParticlePos0(); + StdLargeVec& pos_n = sim.get_position_solid_body_()[0]->GetParticlePosN(); for (size_t index = 0; index < pos_0.size(); index++) { @@ -151,8 +151,8 @@ TEST(StructuralSimulation, PositionScaleSolidBodyTuple) sim.TestRunSimulation(end_time_simulation); //=================================================================================================// - StdLargeVec& pos_0 = sim.Get_position_scale_solid_body_()[0]->GetParticlePos0(); - StdLargeVec& pos_n = sim.Get_position_scale_solid_body_()[0]->GetParticlePosN(); + StdLargeVec& pos_0 = sim.get_position_scale_solid_body_()[0]->GetParticlePos0(); + StdLargeVec& pos_n = sim.get_position_scale_solid_body_()[0]->GetParticlePosN(); string name = "./input/cylinder.stl"; TriangleMeshShape cylinder_mesh(name, translation_list[0] * scale_stl, scale_stl); @@ -198,7 +198,7 @@ TEST(StructuralSimulation, TranslateSolidBodyTuple) physical_viscosity, {} }; - Vecd translation_vector = Vec3d(0.0, 0.0, 0.1); //changed to be more like PositionSolidBodyTuple + Vecd translation_vector = Vec3d(0.0, 0.0, 0.1); input.translation_solid_body_tuple_ = { TranslateSolidBodyTuple(0, end_time * 0.32, end_time, translation_vector) }; //=================================================================================================// @@ -206,8 +206,8 @@ TEST(StructuralSimulation, TranslateSolidBodyTuple) sim.TestRunSimulation(end_time); //=================================================================================================// - StdLargeVec& pos_0 = sim.Get_translation_solid_body_()[0]->GetParticlePos0(); - StdLargeVec& pos_n = sim.Get_translation_solid_body_()[0]->GetParticlePosN(); +StdLargeVec& pos_0 = sim.get_solid_body_list_()[0].get()->getElasticSolidParticles()->pos_0_; +StdLargeVec& pos_n = sim.get_solid_body_list_()[0].get()->getElasticSolidParticles()->pos_n_; for (size_t index = 0; index < pos_0.size(); index++) { @@ -216,6 +216,66 @@ TEST(StructuralSimulation, TranslateSolidBodyTuple) } } +TEST(StructuralSimulation, TranslateSolidBodyPartTuple) +{ + Real scale_stl = 0.001 / 4; // diameter of 0.025 m + Real resolution_mass = 8.0; + Real poisson = 0.35; + Real Youngs_modulus = 1e4; + Real physical_viscosity = 200; + Real rho_0 = 1000; + Real end_time = 0.1; + + /** STL IMPORT PARAMETERS */ + std::string relative_input_path = "./input/"; //path definition for linux + std::vector imported_stl_list = { "ball_mass.stl" }; + std::vector translation_list = { Vec3d(0) }; + std::vector resolution_list = { resolution_mass}; + LinearElasticSolid material = LinearElasticSolid(rho_0, Youngs_modulus, poisson); + std::vector material_model_list = { material }; + + TriangleMeshShape ball_mesh("./input/ball_mass.stl", translation_list[0] * scale_stl, scale_stl); + BoundingBox bbox = ball_mesh.findBounds(); + Real z_limit = 0.75 * bbox.first[2] + 0.25 * bbox.second[2]; // only apply to the bottom 25% of the ball + bbox.second[2] = z_limit; + + StructuralSimulationInput input + { + relative_input_path, + imported_stl_list, + scale_stl, + translation_list, + resolution_list, + material_model_list, + physical_viscosity, + {} + }; + Vecd translation_vector = Vec3d(0.0, 0.0, 0.02); + input.translation_solid_body_part_tuple_ = { TranslateSolidBodyPartTuple(0, end_time * 0.124, end_time, translation_vector, bbox) }; + + //=================================================================================================// + TestStructuralSimulation sim (input); + sim.TestRunSimulation(end_time); + //=================================================================================================// + + StdLargeVec& pos_0 = sim.get_solid_body_list_()[0].get()->getElasticSolidParticles()->pos_0_; + StdLargeVec& pos_n = sim.get_solid_body_list_()[0].get()->getElasticSolidParticles()->pos_n_; + + for (size_t index = 0; index < pos_0.size(); index++) + { + if (pos_0[index][2] < z_limit) + { + Vec3d end_pos = pos_0[index] + translation_vector; + EXPECT_NEAR(pos_n[index][2], end_pos[2], end_pos.norm() * 0.05); + } + else + { + Real z_limit_end = z_limit + translation_vector[2] * 0.95; // z_limit + translation_vector[2] = 0.01375 is the actual limit, but some particle go below this level + EXPECT_GT(pos_n[index][2], z_limit_end); + } + } +} + int main(int argc, char* argv[]) { testing::InitGoogleTest(&argc, argv); diff --git a/cases_high_level_simulation/test_3d_unit_time_dep_contact/time_dep_contact.cpp b/cases_high_level_simulation/test_3d_unit_time_dep_contact/time_dep_contact.cpp index 53f2048242..a9c2a7897f 100644 --- a/cases_high_level_simulation/test_3d_unit_time_dep_contact/time_dep_contact.cpp +++ b/cases_high_level_simulation/test_3d_unit_time_dep_contact/time_dep_contact.cpp @@ -48,8 +48,8 @@ TEST(StructuralSimulation, TimeDependentContact) sim.TestRunSimulation(end_time); // check that the two plates don't overlap, this will check if the contact works properly - StdLargeVec& pos_n_1 = sim.Get_solid_body_list_()[0].get()->getElasticSolidParticles()->pos_n_; // plate 1 particles - StdLargeVec& pos_n_2 = sim.Get_solid_body_list_()[1].get()->getElasticSolidParticles()->pos_n_; // plate 2 particles + StdLargeVec& pos_n_1 = sim.get_solid_body_list_()[0].get()->getElasticSolidParticles()->pos_n_; // plate 1 particles + StdLargeVec& pos_n_2 = sim.get_solid_body_list_()[1].get()->getElasticSolidParticles()->pos_n_; // plate 2 particles // plate 1 max z-coordinate Real max_z_1 = -1000.0; @@ -116,8 +116,8 @@ TEST(StructuralSimulation, TimeDependentContactStiff) sim.TestRunSimulation(end_time); // check that the two plates don't overlap, this will check if the contact works properly - StdLargeVec& pos_n_1 = sim.Get_solid_body_list_()[0].get()->getElasticSolidParticles()->pos_n_; // plate 1 particles - StdLargeVec& pos_n_2 = sim.Get_solid_body_list_()[1].get()->getElasticSolidParticles()->pos_n_; // plate 2 particles + StdLargeVec& pos_n_1 = sim.get_solid_body_list_()[0].get()->getElasticSolidParticles()->pos_n_; // plate 1 particles + StdLargeVec& pos_n_2 = sim.get_solid_body_list_()[1].get()->getElasticSolidParticles()->pos_n_; // plate 2 particles // plate 1 max z-coordinate Real max_z_1 = -1000.0; @@ -178,18 +178,18 @@ TEST(StructuralSimulation, MockStent) /** SIMULATION MODEL */ TestStructuralSimulation sim(input); - EXPECT_EQ(sim.Get_contacting_body_pairs_list_().size(), 0); - EXPECT_EQ(sim.Get_time_dep_contacting_body_pairs_list_().size(), 2); - EXPECT_EQ(sim.Get_contact_list_().size(), 4); - EXPECT_EQ(sim.Get_contact_density_list_().size(), 4); - EXPECT_EQ(sim.Get_contact_force_list_().size(), 4); + EXPECT_EQ(sim.get_contacting_body_pairs_list_().size(), 0); + EXPECT_EQ(sim.get_time_dep_contacting_body_pairs_list_().size(), 2); + EXPECT_EQ(sim.get_contact_list_().size(), 4); + EXPECT_EQ(sim.get_contact_density_list_().size(), 4); + EXPECT_EQ(sim.get_contact_force_list_().size(), 4); /** START SIMULATION */ sim.TestRunSimulation(end_time); // check the plate position, that the translation is correct - StdLargeVec& pos_0 = sim.Get_solid_body_list_()[1].get()->getElasticSolidParticles()->pos_0_; - StdLargeVec& pos_n = sim.Get_solid_body_list_()[1].get()->getElasticSolidParticles()->pos_n_; + StdLargeVec& pos_0 = sim.get_solid_body_list_()[1].get()->getElasticSolidParticles()->pos_0_; + StdLargeVec& pos_n = sim.get_solid_body_list_()[1].get()->getElasticSolidParticles()->pos_n_; for (size_t index = 0; index < pos_0.size(); index++) { Vec3d end_pos = pos_0[index] + translation_vector; @@ -199,8 +199,8 @@ TEST(StructuralSimulation, MockStent) // check that the stent is inside the vessel: // 1. the max y pos is larger for the vessel than the stent // 1. the min y pos is smaller for the vessel than the stent - StdLargeVec& pos_n_stent = sim.Get_solid_body_list_()[0].get()->getElasticSolidParticles()->pos_n_; // stent particles - StdLargeVec& pos_n_vessel = sim.Get_solid_body_list_()[2].get()->getElasticSolidParticles()->pos_n_; // vessel particles + StdLargeVec& pos_n_stent = sim.get_solid_body_list_()[0].get()->getElasticSolidParticles()->pos_n_; // stent particles + StdLargeVec& pos_n_vessel = sim.get_solid_body_list_()[2].get()->getElasticSolidParticles()->pos_n_; // vessel particles Real max_y_stent = -1000.0; Real min_y_stent = 1000.0; From 69ace2dc644cbbddda89d232969c9b9b90da1fdf Mon Sep 17 00:00:00 2001 From: Xiangyu Hu Date: Fri, 30 Jul 2021 16:47:06 +0200 Subject: [PATCH 02/40] update from bitbucket 07-30-2021 --- SPHINXsys/CMakeLists.txt | 13 - SPHINXsys/cmake/Dirsearch_for_2D_build.cmake | 22 - SPHINXsys/cmake/Dirsearch_for_3D_build.cmake | 23 - .../cmake/Dirsearch_for_image_process.cmake | 22 - SPHINXsys/cmake/Dirsearch_shared.cmake | 50 - SPHINXsys/cmake/Headersearch.cmake | 8 - .../cmake/Simbody_header_directories.cmake | 15 - SPHINXsys/cmake/oneTBB_header_directory.cmake | 5 - SPHINXsys/logo-16x16.ico | Bin 632 -> 0 bytes SPHINXsys/logo-32x32.ico | Bin 1603 -> 0 bytes SPHINXsys/logo-white.png | Bin 5113 -> 0 bytes SPHINXsys/logo-with-name.png | Bin 9614 -> 0 bytes SPHINXsys/logo-with-white.png | Bin 7305 -> 0 bytes SPHINXsys/logo.png | Bin 9484 -> 0 bytes SPHINXsys/mainpage.md | 100 -- SPHINXsys/src/CMakeLists.txt | 23 - SPHINXsys/src/Readme.txt | 5 - SPHINXsys/src/for_2D_build/CMakeLists.txt | 81 - .../src/for_2D_build/bodies/CMakeLists.txt | 7 - .../bodies/solid_body_supplementary.cpp | 55 - .../src/for_2D_build/common/CMakeLists.txt | 7 - SPHINXsys/src/for_2D_build/common/data_type.h | 39 - .../common/scalar_functions_supplementary.cpp | 14 - .../for_2D_build/geometries/CMakeLists.txt | 7 - .../src/for_2D_build/geometries/geometry.cpp | 297 ---- .../src/for_2D_build/geometries/geometry.h | 103 -- .../geometries/level_set_supplementary.cpp | 440 ------ .../src/for_2D_build/meshes/CMakeLists.txt | 7 - .../meshes/base_mesh_supplementary.cpp | 49 - .../meshes/mesh_cell_linked_list.hpp | 47 - .../mesh_cell_linked_list_supplementary.cpp | 243 --- .../meshes/mesh_with_data_packages.hpp | 168 -- .../particle_dynamics/CMakeLists.txt | 7 - .../solid_dynamics/CMakeLists.txt | 8 - .../solid_dynamics_supplementary.cpp | 75 - .../particle_generator/CMakeLists.txt | 7 - .../all_particle_generators.h | 8 - ...rticle_generator_lattice_supplementary.cpp | 36 - .../src/for_2D_build/particles/CMakeLists.txt | 7 - .../solid_particles_supplementary.cpp | 35 - SPHINXsys/src/for_3D_build/CMakeLists.txt | 116 -- .../src/for_3D_build/bodies/CMakeLists.txt | 7 - .../bodies/solid_body_supplementary.cpp | 59 - .../src/for_3D_build/common/CMakeLists.txt | 7 - SPHINXsys/src/for_3D_build/common/data_type.h | 39 - .../common/scalar_functions_supplementary.cpp | 8 - .../for_3D_build/geometries/CMakeLists.txt | 7 - .../src/for_3D_build/geometries/geometry.cpp | 309 ---- .../src/for_3D_build/geometries/geometry.h | 113 -- .../geometries/level_set_supplementary.cpp | 476 ------ .../CMakeLists.txt | 7 - .../solid_structural_simulation_class.cpp | 658 -------- .../solid_structural_simulation_class.h | 212 --- .../src/for_3D_build/meshes/CMakeLists.txt | 7 - .../meshes/base_mesh_supplementary.cpp | 56 - .../meshes/mesh_cell_linked_list.hpp | 51 - .../mesh_cell_linked_list_supplementary.cpp | 312 ---- .../meshes/mesh_with_data_packages.hpp | 185 --- .../particle_dynamics/CMakeLists.txt | 7 - .../solid_dynamics/CMakeLists.txt | 8 - .../solid_dynamics/polar_decomposition_3x3.h | 68 - .../polar_decomposition_3x3_impl.h | 1373 ----------------- .../polar_decomposition_3x3_matrix.h | 359 ----- .../solid_dynamics_supplementary.cpp | 69 - .../particle_generator/CMakeLists.txt | 7 - .../all_particle_generators.h | 9 - ...rticle_generator_lattice_supplementary.cpp | 33 - .../particle_generator_network.cpp | 239 --- .../particle_generator_network.h | 116 -- .../src/for_3D_build/particles/CMakeLists.txt | 7 - .../solid_particles_supplementary.cpp | 34 - SPHINXsys/src/shared/CMakeLists.txt | 8 - SPHINXsys/src/shared/bodies/CMakeLists.txt | 8 - SPHINXsys/src/shared/bodies/all_bodies.h | 14 - SPHINXsys/src/shared/bodies/base_body.cpp | 276 ---- SPHINXsys/src/shared/bodies/base_body.h | 314 ---- SPHINXsys/src/shared/bodies/body_relation.cpp | 268 ---- SPHINXsys/src/shared/bodies/body_relation.h | 308 ---- SPHINXsys/src/shared/bodies/fluid_body.cpp | 28 - SPHINXsys/src/shared/bodies/fluid_body.h | 73 - SPHINXsys/src/shared/bodies/solid_body.cpp | 39 - SPHINXsys/src/shared/bodies/solid_body.h | 91 -- SPHINXsys/src/shared/common/CMakeLists.txt | 8 - .../src/shared/common/array_allocation.h | 91 -- .../src/shared/common/base_data_package.h | 34 - SPHINXsys/src/shared/common/base_data_type.h | 363 ----- .../src/shared/common/large_data_containers.h | 58 - .../src/shared/common/scalar_functions.cpp | 15 - .../src/shared/common/scalar_functions.h | 184 --- SPHINXsys/src/shared/common/small_vectors.cpp | 217 --- SPHINXsys/src/shared/common/small_vectors.h | 79 - .../src/shared/common/sph_data_conainers.h | 115 -- .../generative_structures.cpp | 270 ---- .../generative_structures.h | 118 -- .../src/shared/geometries/CMakeLists.txt | 8 - .../src/shared/geometries/all_geometries.h | 12 - .../src/shared/geometries/base_geometry.h | 91 -- .../shared/geometries/geometry_level_set.cpp | 65 - .../shared/geometries/geometry_level_set.h | 44 - SPHINXsys/src/shared/geometries/level_set.cpp | 247 --- SPHINXsys/src/shared/geometries/level_set.h | 175 --- SPHINXsys/src/shared/include/CMakeLists.txt | 8 - SPHINXsys/src/shared/include/sphinxsys.h | 44 - SPHINXsys/src/shared/io_system/CMakeLists.txt | 8 - SPHINXsys/src/shared/io_system/in_output.cpp | 377 ----- SPHINXsys/src/shared/io_system/in_output.h | 654 -------- .../src/shared/io_system/parameterization.cpp | 23 - .../src/shared/io_system/parameterization.h | 96 -- SPHINXsys/src/shared/kernels/CMakeLists.txt | 8 - SPHINXsys/src/shared/kernels/all_kernels.h | 10 - SPHINXsys/src/shared/kernels/base_kernel.cpp | 180 --- SPHINXsys/src/shared/kernels/base_kernel.h | 176 --- .../src/shared/kernels/kernel_hyperbolic.cpp | 84 - .../src/shared/kernels/kernel_hyperbolic.h | 67 - .../src/shared/kernels/kernel_quadratic.cpp | 84 - .../src/shared/kernels/kernel_quadratic.h | 67 - .../src/shared/kernels/kernel_tabulated.hpp | 152 -- .../src/shared/kernels/kernel_wenland_c2.cpp | 67 - .../src/shared/kernels/kernel_wenland_c2.h | 67 - SPHINXsys/src/shared/materials/CMakeLists.txt | 8 - .../src/shared/materials/all_materials.h | 16 - .../src/shared/materials/base_material.h | 188 --- .../src/shared/materials/complex_solid.h | 56 - .../src/shared/materials/complex_solid.hpp | 41 - .../shared/materials/compressible_fluid.cpp | 22 - .../src/shared/materials/compressible_fluid.h | 70 - .../shared/materials/diffusion_reaction.cpp | 149 -- .../src/shared/materials/diffusion_reaction.h | 338 ---- .../src/shared/materials/elastic_solid.cpp | 253 --- .../src/shared/materials/elastic_solid.h | 263 ---- .../src/shared/materials/inelastic_solid.cpp | 49 - .../src/shared/materials/inelastic_solid.h | 92 -- .../src/shared/materials/riemann_solver.cpp | 233 --- .../src/shared/materials/riemann_solver.h | 108 -- .../materials/weakly_compressible_fluid.cpp | 48 - .../materials/weakly_compressible_fluid.h | 158 -- SPHINXsys/src/shared/meshes/CMakeLists.txt | 8 - SPHINXsys/src/shared/meshes/base_mesh.cpp | 110 -- SPHINXsys/src/shared/meshes/base_mesh.h | 183 --- .../shared/meshes/mesh_cell_linked_list.cpp | 139 -- .../src/shared/meshes/mesh_cell_linked_list.h | 187 --- .../shared/meshes/mesh_with_data_packages.h | 258 ---- SPHINXsys/src/shared/meshes/my_memory_pool.h | 62 - .../shared/particle_dynamics/CMakeLists.txt | 8 - .../active_muscle_dynamics/CMakeLists.txt | 8 - .../active_muscle_dynamics.cpp | 57 - .../active_muscle_dynamics.h | 106 -- .../particle_dynamics/all_particle_dynamics.h | 9 - .../particle_dynamics/all_physical_dynamics.h | 25 - .../base_particle_dynamics.cpp | 99 -- .../base_particle_dynamics.h | 272 ---- .../base_particle_dynamics.hpp | 95 -- .../CMakeLists.txt | 8 - .../particle_dynamics_diffusion_reaction.h | 297 ---- .../particle_dynamics_diffusion_reaction.hpp | 314 ---- .../dissipation_dynamics/CMakeLists.txt | 8 - .../particle_dynamics_dissipation.h | 201 --- .../particle_dynamics_dissipation.hpp | 418 ----- .../electro_physiology/CMakeLists.txt | 8 - .../electro_physiology/electro_physiology.cpp | 24 - .../electro_physiology/electro_physiology.h | 149 -- .../external_force/CMakeLists.txt | 8 - .../external_force/external_force.cpp | 26 - .../external_force/external_force.h | 69 - .../fluid_dynamics/CMakeLists.txt | 8 - .../fluid_dynamics/all_fluid_dynamics.h | 42 - .../eulerian_fluid_dynamics/CMakeLists.txt | 8 - .../all_eulerian_fluid_dynamics.h | 34 - .../eulerian_fluid_dynamics_complex.cpp | 20 - .../eulerian_fluid_dynamics_complex.h | 184 --- .../eulerian_fluid_dynamics_complex.hpp | 232 --- .../eulerian_fluid_dynamics_inner.cpp | 142 -- .../eulerian_fluid_dynamics_inner.h | 188 --- .../eulerian_fluid_dynamics_inner.hpp | 79 - .../fluid_dynamics/fluid_dynamics_complex.cpp | 299 ---- .../fluid_dynamics/fluid_dynamics_complex.h | 365 ----- .../fluid_dynamics/fluid_dynamics_complex.hpp | 368 ----- .../fluid_dynamics_compound.cpp | 18 - .../fluid_dynamics/fluid_dynamics_compound.h | 103 -- .../fluid_dynamics_compound.hpp | 21 - .../fluid_dynamics/fluid_dynamics_inner.cpp | 771 --------- .../fluid_dynamics/fluid_dynamics_inner.h | 747 --------- .../fluid_dynamics/fluid_dynamics_inner.hpp | 70 - .../fluid_dynamics_multi_phase.cpp | 111 -- .../fluid_dynamics_multi_phase.h | 153 -- .../fluid_dynamics_multi_phase.hpp | 167 -- .../general_dynamics/CMakeLists.txt | 8 - .../general_dynamics/general_dynamics.cpp | 535 ------- .../general_dynamics/general_dynamics.h | 547 ------- .../observer_dynamics/CMakeLists.txt | 8 - .../observer_dynamics/observer_dynamics.cpp | 69 - .../observer_dynamics/observer_dynamics.h | 123 -- .../particle_dynamics_algorithms.cpp | 148 -- .../particle_dynamics_algorithms.h | 213 --- .../particle_dynamics_bodypart.cpp | 161 -- .../particle_dynamics_bodypart.h | 264 ---- .../relax_dynamics/CMakeLists.txt | 8 - .../relax_dynamics/relax_dynamics.cpp | 239 --- .../relax_dynamics/relax_dynamics.h | 260 ---- .../solid_dynamics/CMakeLists.txt | 8 - .../solid_dynamics/all_solid_dynamics.h | 10 - .../fluid_structure_interaction.cpp | 150 -- .../fluid_structure_interaction.h | 234 --- .../solid_dynamics/inelastic_dynamics.cpp | 33 - .../solid_dynamics/inelastic_dynamics.h | 56 - .../solid_dynamics/solid_dynamics.cpp | 796 ---------- .../solid_dynamics/solid_dynamics.h | 618 -------- .../thin_structure_dynamics.cpp | 521 ------- .../solid_dynamics/thin_structure_dynamics.h | 302 ---- .../solid_dynamics/thin_structure_math.cpp | 148 -- .../solid_dynamics/thin_structure_math.h | 69 - .../shared/particle_generator/CMakeLists.txt | 8 - .../base_particle_generator.cpp | 67 - .../base_particle_generator.h | 86 -- .../particle_generator_lattice.cpp | 63 - .../particle_generator_lattice.h | 82 - SPHINXsys/src/shared/particles/CMakeLists.txt | 8 - .../src/shared/particles/all_particles.h | 11 - .../src/shared/particles/base_particles.cpp | 329 ---- .../src/shared/particles/base_particles.h | 350 ----- .../diffusion_reaction_particles.cpp | 24 - .../particles/diffusion_reaction_particles.h | 133 -- .../src/shared/particles/fluid_particles.cpp | 77 - .../src/shared/particles/fluid_particles.h | 96 -- .../shared/particles/neighbor_relation.cpp | 175 --- .../src/shared/particles/neighbor_relation.h | 162 -- .../shared/particles/particle_adaptation.cpp | 179 --- .../shared/particles/particle_adaptation.h | 147 -- .../src/shared/particles/particle_sorting.cpp | 63 - .../src/shared/particles/particle_sorting.h | 265 ---- .../src/shared/particles/solid_particles.cpp | 173 --- .../src/shared/particles/solid_particles.h | 173 --- .../shared/regression_testing/CMakeLists.txt | 8 - .../meanvalue_variance_method.h | 203 --- .../meanvalue_variance_method.hpp | 663 -------- .../regression_testing/regression_testing.h | 33 - .../shared/simbody_sphinxsys/CMakeLists.txt | 8 - .../shared/simbody_sphinxsys/all_simbody.h | 14 - .../src/shared/simbody_sphinxsys/array.h | 648 -------- .../shared/simbody_sphinxsys/exception.cpp | 84 - .../src/shared/simbody_sphinxsys/exception.h | 142 -- .../shared/simbody_sphinxsys/simbody_middle.h | 23 - .../shared/simbody_sphinxsys/state_engine.cpp | 363 ----- .../shared/simbody_sphinxsys/state_engine.h | 380 ----- .../shared/simbody_sphinxsys/xml_engine.cpp | 116 -- .../src/shared/simbody_sphinxsys/xml_engine.h | 114 -- .../shared/sphinxsys_system/CMakeLists.txt | 8 - .../shared/sphinxsys_system/sph_system.cpp | 139 -- .../src/shared/sphinxsys_system/sph_system.h | 80 - SPHINXsys/src/shared/tbb/CMakeLists.txt | 8 - .../test_3d_TAH_path/CMakeLists.txt | 4 +- .../sim_total_artificial_heart.h | 4 +- .../CMakeLists.txt | 4 +- .../test_3d_ball_position_solid_body.cpp | 2 +- .../CMakeLists.txt | 7 +- .../input/ball_mass.stl | Bin .../input/cylinder.stl | Bin .../position_based_boundary_conditions.cpp} | 110 +- .../CMakeLists.txt | 70 + .../input/mock_stent.stl | Bin 0 -> 23884 bytes .../input/plate.stl | Bin 0 -> 684 bytes .../input/plate2.stl | Bin 0 -> 684 bytes .../input/plate_stent.stl | Bin 0 -> 684 bytes .../input/vessel_cylinder.stl | Bin 0 -> 19084 bytes .../time_dep_contact.cpp | 227 +++ .../test_1d_shock_tube/src/CMakeLists.txt | 2 +- .../test_2d_T_shaped_pipe/src/CMakeLists.txt | 2 +- cases_test/test_2d_airfoil/CMakeLists.txt | 2 +- cases_test/test_2d_airfoil/airfoil_2d.cpp | 4 +- cases_test/test_2d_airfoil/airfoil_2d.h | 2 +- .../test_2d_collision/src/CMakeLists.txt | 2 +- .../test_2d_dambreak/src/CMakeLists.txt | 2 +- .../test_2d_depolarization/src/CMakeLists.txt | 2 +- .../src/depolarization.cpp | 2 +- .../test_2d_diffusion/src/CMakeLists.txt | 2 +- .../test_2d_elastic_gate/src/CMakeLists.txt | 2 +- .../test_2d_elastic_gate/src/elastic_gate.cpp | 4 +- .../src/CMakeLists.txt | 2 +- .../test_2d_filling_tank/src/CMakeLists.txt | 2 +- .../test_2d_filling_tank/src/filling_tank.cpp | 18 +- .../src/2d_flow_around_cylinder.h | 2 +- .../src/CMakeLists.txt | 2 +- .../src/2d_free_stream_around_cylinder.h | 2 +- .../src/CMakeLists.txt | 2 +- cases_test/test_2d_fsi2/src/CMakeLists.txt | 2 +- cases_test/test_2d_fsi2/src/fsi2_case.h | 4 +- .../test_2d_heat_transfer/src/CMakeLists.txt | 2 +- .../src/CMakeLists.txt | 2 +- .../src/hydrostatic_fsi.cpp | 36 +- .../src/CMakeLists.txt | 2 +- .../src/oscillating_beam.cpp | 2 +- cases_test/test_2d_owsc/src/CMakeLists.txt | 2 +- .../CMakeLists.txt | 2 +- .../particle_generator_single_resolution.cpp | 4 +- cases_test/test_2d_plate/src/2d_plate.cpp | 2 +- cases_test/test_2d_plate/src/CMakeLists.txt | 2 +- .../src/CMakeLists.txt | 2 +- cases_test/test_2d_shell/src/2d_shell.cpp | 2 +- cases_test/test_2d_shell/src/CMakeLists.txt | 2 +- cases_test/test_2d_sliding/src/CMakeLists.txt | 2 +- .../test_2d_square_droplet/src/CMakeLists.txt | 2 +- cases_test/test_2d_square_droplet/src/case.h | 2 +- .../src/CMakeLists.txt | 2 +- .../test_2d_taylor_green/src/CMakeLists.txt | 2 +- .../src/CMakeLists.txt | 2 +- .../src/tethered_dead_fish_in_flow.cpp | 4 +- cases_test/test_2d_throat/src/CMakeLists.txt | 2 +- cases_test/test_2d_throat/src/throat.cpp | 14 +- .../src/CMakeLists.txt | 2 +- .../test_2d_two_phase_dambreak/src/case.h | 2 +- .../src/CMakeLists.txt | 2 +- cases_test/test_2d_wetting_effects/src/case.h | 2 +- cases_test/test_3d_arch/src/3d_arch.cpp | 2 +- cases_test/test_3d_arch/src/CMakeLists.txt | 2 +- .../test_3d_dambreak/src/CMakeLists.txt | 2 +- .../CMakeLists.txt | 2 +- .../src/CMakeLists.txt | 2 +- .../src/CMakeLists.txt | 2 +- .../src/muscle_contact_soft_body_contact.cpp | 6 +- .../test_3d_myocaridum/src/CMakeLists.txt | 2 +- cases_test/test_3d_network/CMakeLists.txt | 2 +- cases_test/test_3d_network/sphere.h | 2 +- .../CMakeLists.txt | 2 +- cases_test/test_3d_particle_generation/case.h | 2 +- .../particle_generation.cpp | 4 +- .../CMakeLists.txt | 2 +- ...article_generator_single_resolution_3D.cpp | 4 +- .../src/CMakeLists.txt | 2 +- .../src/CMakeLists.txt | 2 +- .../CMakeLists.txt | 2 +- .../test_3d_play_simbody/src/CMakeLists.txt | 2 +- cases_test/test_3d_roof/src/3d_roof.cpp | 2 +- cases_test/test_3d_roof/src/CMakeLists.txt | 2 +- .../test_3d_taylor_bar/src/CMakeLists.txt | 2 +- cases_test/test_3d_taylor_bar/src/case.h | 4 +- .../test_3d_thin_plate/src/CMakeLists.txt | 2 +- .../src/test_3d_thin_plate.cpp | 2 +- .../src/CMakeLists.txt | 2 +- cases_test/test_3d_twisting_column/src/case.h | 4 +- .../src/twisting_column.cpp | 4 +- gpuSPHINXsys/gpuSPHinxsys.cu | 198 +++ gpuSPHINXsys/gpuSPHinxsys.cuh | 45 + gpuSPHINXsys/src/Box.cuh | 67 + gpuSPHINXsys/src/CellList.cuh | 568 +++++++ gpuSPHINXsys/src/GPUUtils.cuh | 38 + gpuSPHINXsys/src/Grid.cuh | 167 ++ gpuSPHINXsys/src/Kernel.cuh | 121 ++ gpuSPHINXsys/src/Log.h | 63 + gpuSPHINXsys/src/ParticleData.cuh | 318 ++++ gpuSPHINXsys/src/ParticleGroup.cuh | 471 ++++++ gpuSPHINXsys/src/ParticleSorter.cuh | 297 ++++ gpuSPHINXsys/src/Property.cuh | 278 ++++ gpuSPHINXsys/src/System.h | 89 ++ gpuSPHINXsys/src/allocator.h | 211 +++ gpuSPHINXsys/src/debugTools.cuh | 44 + gpuSPHINXsys/src/defines.h | 36 + gpuSPHINXsys/src/dtSizeCalc.cu | 154 ++ gpuSPHINXsys/src/dtSizeCalc.cuh | 57 + gpuSPHINXsys/src/fluidDynamics.cu | 686 ++++++++ gpuSPHINXsys/src/fluidDynamics.cuh | 64 + gpuSPHINXsys/src/inOut.cu | 294 ++++ gpuSPHINXsys/src/inOut.cuh | 61 + gpuSPHINXsys/src/include/nod/README.md | 257 +++ gpuSPHINXsys/src/include/nod/nod.hpp | 631 ++++++++ gpuSPHINXsys/src/timeStepping.cu | 259 ++++ gpuSPHINXsys/src/timeStepping.cuh | 74 + gpuSPHINXsys/src/vector.cuh | 926 +++++++++++ 367 files changed, 6952 insertions(+), 33581 deletions(-) delete mode 100644 SPHINXsys/CMakeLists.txt delete mode 100644 SPHINXsys/cmake/Dirsearch_for_2D_build.cmake delete mode 100644 SPHINXsys/cmake/Dirsearch_for_3D_build.cmake delete mode 100644 SPHINXsys/cmake/Dirsearch_for_image_process.cmake delete mode 100644 SPHINXsys/cmake/Dirsearch_shared.cmake delete mode 100644 SPHINXsys/cmake/Headersearch.cmake delete mode 100644 SPHINXsys/cmake/Simbody_header_directories.cmake delete mode 100644 SPHINXsys/cmake/oneTBB_header_directory.cmake delete mode 100644 SPHINXsys/logo-16x16.ico delete mode 100644 SPHINXsys/logo-32x32.ico delete mode 100644 SPHINXsys/logo-white.png delete mode 100644 SPHINXsys/logo-with-name.png delete mode 100644 SPHINXsys/logo-with-white.png delete mode 100644 SPHINXsys/logo.png delete mode 100644 SPHINXsys/mainpage.md delete mode 100644 SPHINXsys/src/CMakeLists.txt delete mode 100644 SPHINXsys/src/Readme.txt delete mode 100644 SPHINXsys/src/for_2D_build/CMakeLists.txt delete mode 100644 SPHINXsys/src/for_2D_build/bodies/CMakeLists.txt delete mode 100644 SPHINXsys/src/for_2D_build/bodies/solid_body_supplementary.cpp delete mode 100644 SPHINXsys/src/for_2D_build/common/CMakeLists.txt delete mode 100644 SPHINXsys/src/for_2D_build/common/data_type.h delete mode 100644 SPHINXsys/src/for_2D_build/common/scalar_functions_supplementary.cpp delete mode 100644 SPHINXsys/src/for_2D_build/geometries/CMakeLists.txt delete mode 100644 SPHINXsys/src/for_2D_build/geometries/geometry.cpp delete mode 100644 SPHINXsys/src/for_2D_build/geometries/geometry.h delete mode 100644 SPHINXsys/src/for_2D_build/geometries/level_set_supplementary.cpp delete mode 100644 SPHINXsys/src/for_2D_build/meshes/CMakeLists.txt delete mode 100644 SPHINXsys/src/for_2D_build/meshes/base_mesh_supplementary.cpp delete mode 100644 SPHINXsys/src/for_2D_build/meshes/mesh_cell_linked_list.hpp delete mode 100644 SPHINXsys/src/for_2D_build/meshes/mesh_cell_linked_list_supplementary.cpp delete mode 100644 SPHINXsys/src/for_2D_build/meshes/mesh_with_data_packages.hpp delete mode 100644 SPHINXsys/src/for_2D_build/particle_dynamics/CMakeLists.txt delete mode 100644 SPHINXsys/src/for_2D_build/particle_dynamics/solid_dynamics/CMakeLists.txt delete mode 100644 SPHINXsys/src/for_2D_build/particle_dynamics/solid_dynamics/solid_dynamics_supplementary.cpp delete mode 100644 SPHINXsys/src/for_2D_build/particle_generator/CMakeLists.txt delete mode 100644 SPHINXsys/src/for_2D_build/particle_generator/all_particle_generators.h delete mode 100644 SPHINXsys/src/for_2D_build/particle_generator/particle_generator_lattice_supplementary.cpp delete mode 100644 SPHINXsys/src/for_2D_build/particles/CMakeLists.txt delete mode 100644 SPHINXsys/src/for_2D_build/particles/solid_particles_supplementary.cpp delete mode 100644 SPHINXsys/src/for_3D_build/CMakeLists.txt delete mode 100644 SPHINXsys/src/for_3D_build/bodies/CMakeLists.txt delete mode 100644 SPHINXsys/src/for_3D_build/bodies/solid_body_supplementary.cpp delete mode 100644 SPHINXsys/src/for_3D_build/common/CMakeLists.txt delete mode 100644 SPHINXsys/src/for_3D_build/common/data_type.h delete mode 100644 SPHINXsys/src/for_3D_build/common/scalar_functions_supplementary.cpp delete mode 100644 SPHINXsys/src/for_3D_build/geometries/CMakeLists.txt delete mode 100644 SPHINXsys/src/for_3D_build/geometries/geometry.cpp delete mode 100644 SPHINXsys/src/for_3D_build/geometries/geometry.h delete mode 100644 SPHINXsys/src/for_3D_build/geometries/level_set_supplementary.cpp delete mode 100644 SPHINXsys/src/for_3D_build/high_level_simulation_class/CMakeLists.txt delete mode 100644 SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp delete mode 100644 SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.h delete mode 100644 SPHINXsys/src/for_3D_build/meshes/CMakeLists.txt delete mode 100644 SPHINXsys/src/for_3D_build/meshes/base_mesh_supplementary.cpp delete mode 100644 SPHINXsys/src/for_3D_build/meshes/mesh_cell_linked_list.hpp delete mode 100644 SPHINXsys/src/for_3D_build/meshes/mesh_cell_linked_list_supplementary.cpp delete mode 100644 SPHINXsys/src/for_3D_build/meshes/mesh_with_data_packages.hpp delete mode 100644 SPHINXsys/src/for_3D_build/particle_dynamics/CMakeLists.txt delete mode 100644 SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/CMakeLists.txt delete mode 100644 SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3.h delete mode 100644 SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3_impl.h delete mode 100644 SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3_matrix.h delete mode 100644 SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/solid_dynamics_supplementary.cpp delete mode 100644 SPHINXsys/src/for_3D_build/particle_generator/CMakeLists.txt delete mode 100644 SPHINXsys/src/for_3D_build/particle_generator/all_particle_generators.h delete mode 100644 SPHINXsys/src/for_3D_build/particle_generator/particle_generator_lattice_supplementary.cpp delete mode 100644 SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.cpp delete mode 100644 SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.h delete mode 100644 SPHINXsys/src/for_3D_build/particles/CMakeLists.txt delete mode 100644 SPHINXsys/src/for_3D_build/particles/solid_particles_supplementary.cpp delete mode 100644 SPHINXsys/src/shared/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/bodies/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/bodies/all_bodies.h delete mode 100644 SPHINXsys/src/shared/bodies/base_body.cpp delete mode 100644 SPHINXsys/src/shared/bodies/base_body.h delete mode 100644 SPHINXsys/src/shared/bodies/body_relation.cpp delete mode 100644 SPHINXsys/src/shared/bodies/body_relation.h delete mode 100644 SPHINXsys/src/shared/bodies/fluid_body.cpp delete mode 100644 SPHINXsys/src/shared/bodies/fluid_body.h delete mode 100644 SPHINXsys/src/shared/bodies/solid_body.cpp delete mode 100644 SPHINXsys/src/shared/bodies/solid_body.h delete mode 100644 SPHINXsys/src/shared/common/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/common/array_allocation.h delete mode 100644 SPHINXsys/src/shared/common/base_data_package.h delete mode 100644 SPHINXsys/src/shared/common/base_data_type.h delete mode 100644 SPHINXsys/src/shared/common/large_data_containers.h delete mode 100644 SPHINXsys/src/shared/common/scalar_functions.cpp delete mode 100644 SPHINXsys/src/shared/common/scalar_functions.h delete mode 100644 SPHINXsys/src/shared/common/small_vectors.cpp delete mode 100644 SPHINXsys/src/shared/common/small_vectors.h delete mode 100644 SPHINXsys/src/shared/common/sph_data_conainers.h delete mode 100644 SPHINXsys/src/shared/generative_structures/generative_structures.cpp delete mode 100644 SPHINXsys/src/shared/generative_structures/generative_structures.h delete mode 100644 SPHINXsys/src/shared/geometries/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/geometries/all_geometries.h delete mode 100644 SPHINXsys/src/shared/geometries/base_geometry.h delete mode 100644 SPHINXsys/src/shared/geometries/geometry_level_set.cpp delete mode 100644 SPHINXsys/src/shared/geometries/geometry_level_set.h delete mode 100644 SPHINXsys/src/shared/geometries/level_set.cpp delete mode 100644 SPHINXsys/src/shared/geometries/level_set.h delete mode 100644 SPHINXsys/src/shared/include/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/include/sphinxsys.h delete mode 100644 SPHINXsys/src/shared/io_system/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/io_system/in_output.cpp delete mode 100644 SPHINXsys/src/shared/io_system/in_output.h delete mode 100644 SPHINXsys/src/shared/io_system/parameterization.cpp delete mode 100644 SPHINXsys/src/shared/io_system/parameterization.h delete mode 100644 SPHINXsys/src/shared/kernels/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/kernels/all_kernels.h delete mode 100644 SPHINXsys/src/shared/kernels/base_kernel.cpp delete mode 100644 SPHINXsys/src/shared/kernels/base_kernel.h delete mode 100644 SPHINXsys/src/shared/kernels/kernel_hyperbolic.cpp delete mode 100644 SPHINXsys/src/shared/kernels/kernel_hyperbolic.h delete mode 100644 SPHINXsys/src/shared/kernels/kernel_quadratic.cpp delete mode 100644 SPHINXsys/src/shared/kernels/kernel_quadratic.h delete mode 100644 SPHINXsys/src/shared/kernels/kernel_tabulated.hpp delete mode 100644 SPHINXsys/src/shared/kernels/kernel_wenland_c2.cpp delete mode 100644 SPHINXsys/src/shared/kernels/kernel_wenland_c2.h delete mode 100644 SPHINXsys/src/shared/materials/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/materials/all_materials.h delete mode 100644 SPHINXsys/src/shared/materials/base_material.h delete mode 100644 SPHINXsys/src/shared/materials/complex_solid.h delete mode 100644 SPHINXsys/src/shared/materials/complex_solid.hpp delete mode 100644 SPHINXsys/src/shared/materials/compressible_fluid.cpp delete mode 100644 SPHINXsys/src/shared/materials/compressible_fluid.h delete mode 100644 SPHINXsys/src/shared/materials/diffusion_reaction.cpp delete mode 100644 SPHINXsys/src/shared/materials/diffusion_reaction.h delete mode 100644 SPHINXsys/src/shared/materials/elastic_solid.cpp delete mode 100644 SPHINXsys/src/shared/materials/elastic_solid.h delete mode 100644 SPHINXsys/src/shared/materials/inelastic_solid.cpp delete mode 100644 SPHINXsys/src/shared/materials/inelastic_solid.h delete mode 100644 SPHINXsys/src/shared/materials/riemann_solver.cpp delete mode 100644 SPHINXsys/src/shared/materials/riemann_solver.h delete mode 100644 SPHINXsys/src/shared/materials/weakly_compressible_fluid.cpp delete mode 100644 SPHINXsys/src/shared/materials/weakly_compressible_fluid.h delete mode 100644 SPHINXsys/src/shared/meshes/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/meshes/base_mesh.cpp delete mode 100644 SPHINXsys/src/shared/meshes/base_mesh.h delete mode 100644 SPHINXsys/src/shared/meshes/mesh_cell_linked_list.cpp delete mode 100644 SPHINXsys/src/shared/meshes/mesh_cell_linked_list.h delete mode 100644 SPHINXsys/src/shared/meshes/mesh_with_data_packages.h delete mode 100644 SPHINXsys/src/shared/meshes/my_memory_pool.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/active_muscle_dynamics.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/active_muscle_dynamics.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/all_particle_dynamics.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/all_physical_dynamics.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.hpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/particle_dynamics_diffusion_reaction.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/particle_dynamics_diffusion_reaction.hpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/particle_dynamics_dissipation.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/particle_dynamics_dissipation.hpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/electro_physiology/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/particle_dynamics/electro_physiology/electro_physiology.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/electro_physiology/electro_physiology.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/external_force/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/particle_dynamics/external_force/external_force.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/external_force/external_force.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/all_fluid_dynamics.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/all_eulerian_fluid_dynamics.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.hpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.hpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.hpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.hpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.hpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.hpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/general_dynamics/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/particle_dynamics/general_dynamics/general_dynamics.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/general_dynamics/general_dynamics.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/observer_dynamics/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/particle_dynamics/observer_dynamics/observer_dynamics.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/observer_dynamics/observer_dynamics.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/particle_dynamics_algorithms.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/particle_dynamics_algorithms.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/particle_dynamics_bodypart.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/particle_dynamics_bodypart.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/relax_dynamics/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/particle_dynamics/relax_dynamics/relax_dynamics.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/relax_dynamics/relax_dynamics.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/all_solid_dynamics.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/fluid_structure_interaction.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/fluid_structure_interaction.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.h delete mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.cpp delete mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.h delete mode 100644 SPHINXsys/src/shared/particle_generator/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/particle_generator/base_particle_generator.cpp delete mode 100644 SPHINXsys/src/shared/particle_generator/base_particle_generator.h delete mode 100644 SPHINXsys/src/shared/particle_generator/particle_generator_lattice.cpp delete mode 100644 SPHINXsys/src/shared/particle_generator/particle_generator_lattice.h delete mode 100644 SPHINXsys/src/shared/particles/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/particles/all_particles.h delete mode 100644 SPHINXsys/src/shared/particles/base_particles.cpp delete mode 100644 SPHINXsys/src/shared/particles/base_particles.h delete mode 100644 SPHINXsys/src/shared/particles/diffusion_reaction_particles.cpp delete mode 100644 SPHINXsys/src/shared/particles/diffusion_reaction_particles.h delete mode 100644 SPHINXsys/src/shared/particles/fluid_particles.cpp delete mode 100644 SPHINXsys/src/shared/particles/fluid_particles.h delete mode 100644 SPHINXsys/src/shared/particles/neighbor_relation.cpp delete mode 100644 SPHINXsys/src/shared/particles/neighbor_relation.h delete mode 100644 SPHINXsys/src/shared/particles/particle_adaptation.cpp delete mode 100644 SPHINXsys/src/shared/particles/particle_adaptation.h delete mode 100644 SPHINXsys/src/shared/particles/particle_sorting.cpp delete mode 100644 SPHINXsys/src/shared/particles/particle_sorting.h delete mode 100644 SPHINXsys/src/shared/particles/solid_particles.cpp delete mode 100644 SPHINXsys/src/shared/particles/solid_particles.h delete mode 100644 SPHINXsys/src/shared/regression_testing/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/regression_testing/meanvalue_variance_method.h delete mode 100644 SPHINXsys/src/shared/regression_testing/meanvalue_variance_method.hpp delete mode 100644 SPHINXsys/src/shared/regression_testing/regression_testing.h delete mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/all_simbody.h delete mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/array.h delete mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/exception.cpp delete mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/exception.h delete mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/simbody_middle.h delete mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/state_engine.cpp delete mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/state_engine.h delete mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.cpp delete mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.h delete mode 100644 SPHINXsys/src/shared/sphinxsys_system/CMakeLists.txt delete mode 100644 SPHINXsys/src/shared/sphinxsys_system/sph_system.cpp delete mode 100644 SPHINXsys/src/shared/sphinxsys_system/sph_system.h delete mode 100644 SPHINXsys/src/shared/tbb/CMakeLists.txt rename cases_high_level_simulation/{test_3d_unit => test_3d_unit_position_based_bc}/CMakeLists.txt (87%) rename cases_high_level_simulation/{test_3d_unit => test_3d_unit_position_based_bc}/input/ball_mass.stl (100%) rename cases_high_level_simulation/{test_3d_unit => test_3d_unit_position_based_bc}/input/cylinder.stl (100%) rename cases_high_level_simulation/{test_3d_unit/test_high_level_structural_class.cpp => test_3d_unit_position_based_bc/position_based_boundary_conditions.cpp} (66%) create mode 100644 cases_high_level_simulation/test_3d_unit_time_dep_contact/CMakeLists.txt create mode 100644 cases_high_level_simulation/test_3d_unit_time_dep_contact/input/mock_stent.stl create mode 100644 cases_high_level_simulation/test_3d_unit_time_dep_contact/input/plate.stl create mode 100644 cases_high_level_simulation/test_3d_unit_time_dep_contact/input/plate2.stl create mode 100644 cases_high_level_simulation/test_3d_unit_time_dep_contact/input/plate_stent.stl create mode 100644 cases_high_level_simulation/test_3d_unit_time_dep_contact/input/vessel_cylinder.stl create mode 100644 cases_high_level_simulation/test_3d_unit_time_dep_contact/time_dep_contact.cpp create mode 100644 gpuSPHINXsys/gpuSPHinxsys.cu create mode 100644 gpuSPHINXsys/gpuSPHinxsys.cuh create mode 100644 gpuSPHINXsys/src/Box.cuh create mode 100644 gpuSPHINXsys/src/CellList.cuh create mode 100644 gpuSPHINXsys/src/GPUUtils.cuh create mode 100644 gpuSPHINXsys/src/Grid.cuh create mode 100644 gpuSPHINXsys/src/Kernel.cuh create mode 100644 gpuSPHINXsys/src/Log.h create mode 100644 gpuSPHINXsys/src/ParticleData.cuh create mode 100644 gpuSPHINXsys/src/ParticleGroup.cuh create mode 100644 gpuSPHINXsys/src/ParticleSorter.cuh create mode 100644 gpuSPHINXsys/src/Property.cuh create mode 100644 gpuSPHINXsys/src/System.h create mode 100644 gpuSPHINXsys/src/allocator.h create mode 100644 gpuSPHINXsys/src/debugTools.cuh create mode 100644 gpuSPHINXsys/src/defines.h create mode 100644 gpuSPHINXsys/src/dtSizeCalc.cu create mode 100644 gpuSPHINXsys/src/dtSizeCalc.cuh create mode 100644 gpuSPHINXsys/src/fluidDynamics.cu create mode 100644 gpuSPHINXsys/src/fluidDynamics.cuh create mode 100644 gpuSPHINXsys/src/inOut.cu create mode 100644 gpuSPHINXsys/src/inOut.cuh create mode 100644 gpuSPHINXsys/src/include/nod/README.md create mode 100644 gpuSPHINXsys/src/include/nod/nod.hpp create mode 100644 gpuSPHINXsys/src/timeStepping.cu create mode 100644 gpuSPHINXsys/src/timeStepping.cuh create mode 100644 gpuSPHINXsys/src/vector.cuh diff --git a/SPHINXsys/CMakeLists.txt b/SPHINXsys/CMakeLists.txt deleted file mode 100644 index 665d493d99..0000000000 --- a/SPHINXsys/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # project specific cmake dir - -PROJECT("${CURRENT_FOLDER}") - -set(CMAKE_VERBOSE_MAKEFILE on) - -SET(EXECUTABLE_OUTPUT_PATH "${PROJECT_BINARY_DIR}/bin/") -SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) - -ADD_SUBDIRECTORY(src) - -INSTALL(FILES "logo.png" DESTINATION ${CMAKE_INSTALL_PREFIX}/sphinxsys_logo) diff --git a/SPHINXsys/cmake/Dirsearch_for_2D_build.cmake b/SPHINXsys/cmake/Dirsearch_for_2D_build.cmake deleted file mode 100644 index 1b7000f465..0000000000 --- a/SPHINXsys/cmake/Dirsearch_for_2D_build.cmake +++ /dev/null @@ -1,22 +0,0 @@ -MACRO(HEADER_DIRECTORIES_2D return_list) - FILE(GLOB_RECURSE new_list ${PROJECT_SOURCE_DIR}/src/for_2D_build/*.h) - FILE(GLOB_RECURSE new_list_hpp ${PROJECT_SOURCE_DIR}/src/for_2D_build/*.hpp) - SET(dir_list "") - FOREACH(file_path ${new_list} ${new_list_hpp}) - GET_FILENAME_COMPONENT(dir_path ${file_path} PATH) - SET(dir_list ${dir_list} ${dir_path}) - ENDFOREACH() - LIST(REMOVE_DUPLICATES dir_list) - SET(${return_list} ${dir_list}) -ENDMACRO() - -MACRO(SOURCE_DIRECTORIES_2D return_list) - FILE(GLOB_RECURSE new_list ${PROJECT_SOURCE_DIR}/src/for_2D_build/*.cpp) - SET(dir_list "") - FOREACH(file_path ${new_list}) - GET_FILENAME_COMPONENT(dir_path ${file_path} PATH) - SET(dir_list ${dir_list} ${dir_path}) - ENDFOREACH() - LIST(REMOVE_DUPLICATES dir_list) - SET(${return_list} ${dir_list}) -ENDMACRO() diff --git a/SPHINXsys/cmake/Dirsearch_for_3D_build.cmake b/SPHINXsys/cmake/Dirsearch_for_3D_build.cmake deleted file mode 100644 index 06e817e586..0000000000 --- a/SPHINXsys/cmake/Dirsearch_for_3D_build.cmake +++ /dev/null @@ -1,23 +0,0 @@ -MACRO(HEADER_DIRECTORIES_3D return_list) - FILE(GLOB_RECURSE new_list ${PROJECT_SOURCE_DIR}/src/for_3D_build/*.h) - FILE(GLOB_RECURSE new_list_hpp ${PROJECT_SOURCE_DIR}/src/for_3D_build/*.hpp) - SET(dir_list "") - FOREACH(file_path ${new_list} ${new_list_hpp}) - GET_FILENAME_COMPONENT(dir_path ${file_path} PATH) - SET(dir_list ${dir_list} ${dir_path}) - ENDFOREACH() - LIST(REMOVE_DUPLICATES dir_list) - SET(${return_list} ${dir_list}) -ENDMACRO() - -MACRO(SOURCE_DIRECTORIES_3D return_list) - FILE(GLOB_RECURSE new_list ${PROJECT_SOURCE_DIR}/src/for_3D_build/*.cpp) - FILE(GLOB_RECURSE new_list_c ${PROJECT_SOURCE_DIR}/src/for_3D_build/*.c) - SET(dir_list "") - FOREACH(file_path ${new_list} ${new_list_c}) - GET_FILENAME_COMPONENT(dir_path ${file_path} PATH) - SET(dir_list ${dir_list} ${dir_path}) - ENDFOREACH() - LIST(REMOVE_DUPLICATES dir_list) - SET(${return_list} ${dir_list}) -ENDMACRO() \ No newline at end of file diff --git a/SPHINXsys/cmake/Dirsearch_for_image_process.cmake b/SPHINXsys/cmake/Dirsearch_for_image_process.cmake deleted file mode 100644 index 2f4c5fe28d..0000000000 --- a/SPHINXsys/cmake/Dirsearch_for_image_process.cmake +++ /dev/null @@ -1,22 +0,0 @@ -MACRO(HEADER_DIRECTORIES_image_process return_list) - FILE(GLOB_RECURSE new_list ${PROJECT_SOURCE_DIR}/src/image_processing/*.h) - FILE(GLOB_RECURSE new_list_hpp ${PROJECT_SOURCE_DIR}/src/image_processing/*.hpp) - SET(dir_list "") - FOREACH(file_path ${new_list} ${new_list_hpp}) - GET_FILENAME_COMPONENT(dir_path ${file_path} PATH) - SET(dir_list ${dir_list} ${dir_path}) - ENDFOREACH() - LIST(REMOVE_DUPLICATES dir_list) - SET(${return_list} ${dir_list}) -ENDMACRO() - -MACRO(SOURCE_DIRECTORIES_image_process return_list) - FILE(GLOB_RECURSE new_list ${PROJECT_SOURCE_DIR}/src/image_processing/*.cpp) - SET(dir_list "") - FOREACH(file_path ${new_list}) - GET_FILENAME_COMPONENT(dir_path ${file_path} PATH) - SET(dir_list ${dir_list} ${dir_path}) - ENDFOREACH() - LIST(REMOVE_DUPLICATES dir_list) - SET(${return_list} ${dir_list}) -ENDMACRO() diff --git a/SPHINXsys/cmake/Dirsearch_shared.cmake b/SPHINXsys/cmake/Dirsearch_shared.cmake deleted file mode 100644 index 4791415043..0000000000 --- a/SPHINXsys/cmake/Dirsearch_shared.cmake +++ /dev/null @@ -1,50 +0,0 @@ -if(BUILD_WITH_SIMBODY) - include(Simbody_header_directories) -endif() -if(BUILD_WITH_ONETBB) - include(oneTBB_header_directory) -endif() - -MACRO(HEADER_DIRECTORIES_SHARED return_list) - FILE(GLOB_RECURSE new_list ${PROJECT_SOURCE_DIR}/src/shared/*.h) - FILE(GLOB_RECURSE new_list_hpp ${PROJECT_SOURCE_DIR}/src/shared/*.hpp) - SET(dir_list "") - FOREACH(file_path ${new_list} ${new_list_hpp}) - GET_FILENAME_COMPONENT(dir_path ${file_path} PATH) - SET(dir_list ${dir_list} ${dir_path}) - ENDFOREACH() - LIST(REMOVE_DUPLICATES dir_list) - - if(BUILD_WITH_SIMBODY) - FOREACH(simbody_header_path ${SIMBODY_HEADER_DIRECTORIES}) - SET(dir_list ${dir_list} ${simbody_header_path}) - ENDFOREACH() - endif() - - if(BUILD_WITH_ONETBB) - SET(dir_list ${dir_list} ${ONETBB_HEADER_DIRECTORY}) - endif() - - SET(${return_list} ${dir_list}) -ENDMACRO() - -MACRO(SOURCE_DIRECTORIES_SHARED return_list) - FILE(GLOB_RECURSE new_list ${PROJECT_SOURCE_DIR}/src/shared/*.cpp) - FILE(GLOB_RECURSE new_list_c ${PROJECT_SOURCE_DIR}/src/shared/*.c) - SET(dir_list "") - FOREACH(file_path ${new_list} ${new_list_c}) - GET_FILENAME_COMPONENT(dir_path ${file_path} PATH) - - if(BUILD_WITH_SIMBODY) - SET(dir_list ${dir_list} ${dir_path}) - else() # if Simbody is not built, ignore the simbody and clapack folders - if(NOT dir_list MATCHES ("/simbody/+" OR "/clapack_for_SPHinXsys/+") ) - SET(dir_list ${dir_list} ${dir_path}) - endif() - endif() - - ENDFOREACH() - LIST(REMOVE_DUPLICATES dir_list) - SET(${return_list} ${dir_list}) -ENDMACRO() - diff --git a/SPHINXsys/cmake/Headersearch.cmake b/SPHINXsys/cmake/Headersearch.cmake deleted file mode 100644 index b18e964829..0000000000 --- a/SPHINXsys/cmake/Headersearch.cmake +++ /dev/null @@ -1,8 +0,0 @@ -MACRO(DIR_INC_HEADER_NAMES input_list return_list) - SET(name_list "") - foreach(filenames ${input_list}) - get_filename_component(HEADER_NAMES ${filenames} NAME) - list(APPEND name_list ${HEADER_NAMES}) - endforeach() - SET(${return_list} ${name_list}) -ENDMACRO() \ No newline at end of file diff --git a/SPHINXsys/cmake/Simbody_header_directories.cmake b/SPHINXsys/cmake/Simbody_header_directories.cmake deleted file mode 100644 index fea719dbe3..0000000000 --- a/SPHINXsys/cmake/Simbody_header_directories.cmake +++ /dev/null @@ -1,15 +0,0 @@ -set(SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS "") -set(SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS "${CMAKE_SOURCE_DIR}") #SPHinXsys - -set(SIMBODY_HEADER_DIRECTORIES - "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKcommon/BigMatrix/include" - "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKcommon/Geometry/include" - "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKcommon/Mechanics/include" - "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKcommon/Polynomial/include" - "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKcommon/Random/include" - "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKcommon/Scalar/include" - "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKcommon/Simulation/include" - "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKcommon/SmallMatrix/include" - "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKmath/Geometry/include" - "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKmath/Integrators/include" -) \ No newline at end of file diff --git a/SPHINXsys/cmake/oneTBB_header_directory.cmake b/SPHINXsys/cmake/oneTBB_header_directory.cmake deleted file mode 100644 index 0ca43f34ea..0000000000 --- a/SPHINXsys/cmake/oneTBB_header_directory.cmake +++ /dev/null @@ -1,5 +0,0 @@ -set(SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS "${CMAKE_SOURCE_DIR}") #SPHinXsys - -set(ONETBB_HEADER_DIRECTORY - "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/oneTBB/include" -) \ No newline at end of file diff --git a/SPHINXsys/logo-16x16.ico b/SPHINXsys/logo-16x16.ico deleted file mode 100644 index a5de17d43c1fea419d32a4b9b2727628852a4bb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 632 zcmV-;0*Czo0096201yxW0000W0Ad0F02TlM0EtjeM-2)Z3IG5A4M|8uQUCw|5C8xG z5C{eU001BJ|6u?C00DDSM?wIu&K&6g000DMK}|sb0I`n?{9y$E000SaNLh0L01m?d z01m?e$8V@)0005^Nkl?3u~wDS`@Ks?@5bh;79S zRz+I{?eWq=MMV^`CaG=GBu&#K#xxgu>L>UeK8P>A_`KY+&-f4tN^!x!Vb-j*_v~3G z@!!y4#i-dD^wT=@i;vJR8*o%F?C>V?pph)-^n1|JG!ptr-RNg5e7ym)(1c01U{bA~ zCUmm|?@9r5AqN^>0v*r9=S0lHWVQpp@CnI6r>B9svj)0Wgg3SVI=2iux#$eIvK9BQ zZz8Bi2x<|MrOsC&9n`}r)N~m%xr+E2Y;ejMSomBu_z>Yx^ce%~E%=qqt}gWB8q_^D zxWNXO^6*BoKOEH!7Jm<#Thficw6V|b@qi=!h&#AjhBsMk$-#fYJ1w2I(`jd>GksBOV&aEC{%GQtiN+5#8byr>{wUe5z31HN zOexS3z?JOGWRV?u z3!qs}<;AE|(Lw5!FsU_dq{>=Il{S-F1}kZz0uzLQk3fFF5`)wnA0TrQR+l1kDnLj%h0@k&YdJzK3TS=`9ky;J`PkRDDE<0x8I@oCtpG}iF54$iZ z=F|^8Vi%qkoa)H1+S*0V>k(3~ULu7uG#WCKj6~ldc0@8ynL*W2CnCl6n^c-s;YVLGpl4!&-R3=0RrC*@3SO z#~g0wo%=v8#QkFwNLq`J$unQi$d5AVI^YwfcH>yrK)`!BVaT2aD*(rFaarhoI}KiF z9K0;MXSYX3g)oqU4Sh_-us|A?$cV60u7Ns~C^36+r1@U?5CS+4-p1F@9zSC32MLog@zSwzDuERbL%_fo`TG!x=%syjN*-{J{ABntV*xv+7T~OF z|I?I*AFxxVhOc+@9J`$b2>Cc{1OniZA{Nj!`nMgLg$aH-$M8jV9RY<^oqsqRI|6q2 z^iY%2ehA(H{78EQ9#_lu6lBgpI(hy*iaHV~Hdx5*a@JZnlV;a(}{7j!5_*tWd zr~~nD{0CF>W#FBL+c+3D=TRs44mqBr!o%?=?5n#6a|U$-Ra2vu*hhBb@2!Fnba3jxK0d^??~Tc{9Nyihozy}g)6uwx_ePo)4ej` zb^&oN*QW=r3Nbe-Ts@B@Kew+1YFF$ZK6=?Av@P=OQ(HQpw6AB5#Rmo-*2fZ`2$^R% zYTP@p%lYWpp#mJcS|R5GG^q?b8`@7z+bb#AhwPLLaF5@EYx64CaK6L2o^wKJzR_}xpY4cUwbQPF zsR+-Jopb5+$i4dSbwOxC)H6+_0uA5WX{;P>P#U^2ojR zi@RX{7Ff#UjUvDV>ND$cwsPDQTBkfQ?3rBK*7cW6xCtcY>~<#iT5u-EcR((=DS`k~ zdGT>MDw`kzv6C3G!v>)v17?dzB8BMOovMV%MG)oE4$M z$!?!a?)uL|!0M1IvEolAP8t@&F=Rm^Dojh(w?Asvoe>I48fzwEel2-MKq&;UyEs|+ zlZnN6g4VP)FDMV@UAZ%b-yfQ<1NC1`Meh2}(SKKE?_NFL-f{o{002ovPDHLkV1h7d B>ze=o diff --git a/SPHINXsys/logo-white.png b/SPHINXsys/logo-white.png deleted file mode 100644 index 660e947c3a304384b7b61d3e050d362fc0aa01a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5113 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 diff --git a/SPHINXsys/logo-with-name.png b/SPHINXsys/logo-with-name.png deleted file mode 100644 index ac561518c9ac56fa937d93427c11e79e949ec487..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9614 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

A`5`35ZS=5Mc`o?QWKY+i#4%p{I1w z`UtX%h>^h?9dz@1U<)1lz7n?$o!pQz_}YPY|Zwv1;r!BNqC z#;++z{hOyJO>DM7vD36`!HZMI42Na(rb^EJ zKDi=dXwJmC#uE~qD)Q`!=(Ml4yDs6qyQPdDAYHT-QhqrYNP-$32n>T293dWpKvOQj z4ZXB^2ndOJG-KN)!(<))LeFKDG-@3Z5ZZ4{(sLR4J91x$4{0!PTyjOXfUfmRj}~6< zYSt5^wYxLsFO}lr`bj(ZbD&EB$IHCDAbS7ugsCXP4!0$)>F7Pv$Yc4+c)P=p)gC&CIdRqAxM$gV8J z6DH5)mtHdaq2U+hP0!Ci`wM(17)MA)M1qk}pnLpdmY8%CT=!f<^{G<6CBUT6+i$Scg;* z1F|ea2}+G@(Kv&Zg*-1j5fk3$DrvlJ5!5r_mGp7QOd2+h69y*Gl(ymqqjN~iz1j?? zx^VB0;pTS>P_lI1BDtXDoAgXFE(84RkIhlnc`GyKW!H1PD<~@IxvX7<8}na(YFv~$ zG1QbUdSssI3bOBzgsC!pTDFP!vA^J5W`|$=9fLhZ%SN_L@HDxC6h-5JW>*#zt(t)? zESx=$H^@^T9&vGhNh3U@`ztSw^S}HOW~^9o&aY?`ljuF1g^^b%L4p3SJR2Kl50hpu9>PPrzxsdU!a`^rBI834 zF+|t4My(!hcr+SOym)SIeREk`P4Tj&+TFe2u)aUW2~*>6u5#I&H1&K6KC-UgAnzP8 z^%?D^UcvkY#)o-NR+M>&sJ-q@P7)PbmaRZvpyz(Lt5nUbBBtKyav`DVE!Q)-g4Cna z8z>E~)s9IGx_lQk=8+G1@s5tTf?y*|j@a#X#uaK1b|V)IR$ z+fZ(jb-|DFAtQT7pr z7tg`52z0F_+)y}K_-^L2(EeU*9LIp z&3{qdq4yiR@NxG$HG$)Qm8=fGcG-)Wh5Ev{w(yM^u|tOpzjyNV-!GW;@Z<@jhYZ&4 ziiS*kAjUQ1cD+537wA=M^7xw79Z7e2`oC>*%P=R`|K?@;zFoQcGx>oF;|mt(WJ?CQ zIc9vr^uU{f{QZ2xuWc`}qLC=CIO24kDJpht~ml4cuzuu@Xo6k3SWwv^d_t3Ms_a zU2(tbnVfKP{cpxhUnA8*f4w~a5ePA+*EqPqHzGOXZ%?Vm3r#3_W9)?2v_EBHW?^cK zD{5L9MQx?5YH*k5=*BOw@HRk3;9@|<^y3M)$w)6K-LArUwAvyYn;KRznh zw=>msM`iYeyVLkfV!roh?wTXdS((uEmMwZ%_(@<-=BP;-G))Sgxgzf|FTLl(ZE0@7 z!jBKNo8QTlB}8z1jQ8GwW6{IHj~|t}+7}S<8AR5+0gE0Me%v^*C->PW)B+}CTB^6+ zeL=9+9u|I-FkBMQ5HfXH_Uu9RT?c3mtmt9kCjq~xnJc`^4S+=t3qMYDl`n{Q4H+`- z>5TkMS&#MeGB*I$VeJn|9WROb(xzn{Qv*}07*qoM6N<$ Ef?E-K#Q*>R diff --git a/SPHINXsys/logo-with-white.png b/SPHINXsys/logo-with-white.png deleted file mode 100644 index 81f84b8d755ab03e50eec3a0a7f279771a68911d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7305 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�RhNaM%H897@0 zP65Ah+4*a|dxerRg2v~z#?hgDf7C*53$!!nxrER>yDX)ZYz3cWVeUqkC(O8nSnSET zezu>L2!u@-5Rn#e_y&vo*vV4VheB63LtMlwyDkxq0do>)0-6X(TEg2Fm7~I?3Y-gI zlC*B(?Y4Zi8Cy4fd-{sg+S$5`%oCk36xmUBC!Sekp+MlDT=pWd!JSHtGI)Fggl~PY z8T)?@uYF>dEU_~MG<{h^6OE0w+xEGjsHUwcKd#R#9adwfyHlQB12KO09rLSj`$OpBh(Go*v(X#Gh z1Vh+ATliNNw_dJ%2b@Hj_Rqs4wXpyYDG5rF`Mu<$j&?GtBqXhyb2*$n_xTL041D?e zj0~>$90E$NOmFCvnz+ay!2(_Ie%lHy2-7SKBIf^m#n$=_W4er;Q$Rx}GV{{>Dw=vM zEy(64>GQ*xovvaHl8-5=yc}r>;LUNM-)=8U+d)_GMc`Ws ziJ+kJ4i_*O?_9^JOKO8?Sp>=~XT-F_`+zncG@s_pTzM|mE56T}J98vUjKCf6t%GQ| z;|!Ah5WcJoUvhO?lnBt4(`rz{9-6SBCVoMcChA;}8mZc95j#q45Ej*Dh3hJ#wNxH< z$$gwt)?T^8e%dy)7!7wL3upra+K#fjad2vi!vLN^dS_d&c5t>m(Us+@%IbK-qb$2fb zY`a7$b(y`!FELxW$=0Y;M|v2TGKAEkOs^b=x(8H?s6 zF3Os=vwjajQm37u4i>riKazwkK}bT##)r#x5_sn$46-#*NsLD`ma~-t=PfY|hXrKO z(+Vb-K;g!U_~K!b(s)UIT@@i-gOADtmuE@BlbyLE4!u9m=6klyYv#ST5aXm|3a@-v zk=&zImU^q<(VHYB7@!98aTmgMi9-OaV_676`k`_P9q{!uKfoBtw?49GeZqcdvIdjv{C$ z@d_r8dHeEV$Ptq?AfrSa&+joRq9HKCG$kEAZOB0y<+1Ek+@5GJ7#LVw+I*;@8LE?1 zpvN#m{vhemC(_1LwN1C{NeS~*{*q9Wb;2-B=F}iEe@W$RvQnw|F5=Nq+?Vuw8?JdXoT zQHWtNiEaB7yJSs8rukLr;BY8{4TFnJ#Z!6uMDm zf*5_Z*H(I^QJaBxxXc3@xIQJaM03LVVD6Gi7jXb2*?Vth`r^RUnE$xx4XyUuJ48bz c02me^8}$voVkAxpFQR~*j(aIh_Vkqh0dOTFvj6}9 diff --git a/SPHINXsys/mainpage.md b/SPHINXsys/mainpage.md deleted file mode 100644 index 01ae0075f5..0000000000 --- a/SPHINXsys/mainpage.md +++ /dev/null @@ -1,100 +0,0 @@ -SPHinXsys (pronunciation: s'finksis) -is an acronym from Smoothed Particle -Hydrodynamics for industrial compleX systems. -It provides C++ APIs for physical accurate simulation and aims to model coupled -industrial dynamic systems including fluid, solid, multi-body dynamics and -beyond with SPH (smoothed particle hydrodynamics), -a meshless computational method using particle discretization. - -Included physics ------------------ -Fluid dynamics, solid dynamics, fluid-structure interactions (FSI), -and their coupling to multi-body dynamics (with SIMBody library https://simtk.org) - -SPH method and algorithms ------------------ -SPH is a fully Lagrangian particle method, -in which the continuum media is discretized into Lagrangian particles -and the mechanics is approximated as the interaction between them -with the help of a kernel, usually a Gaussian-like function. -SPH is a mesh free method, which does not require a mesh to define -the neighboring configuration of particles, -but construct of update it according to the distance between particles. -A remarkable feature of this method is that its computational algorithm -involves a large number of common abstractions -which link to many physical systems inherently. -Due to such unique feature, -SPH have been used here for unified modeling of both fluid and solid mechanics. - -The SPH algorithms are based on the published work of the authors. -The algorithms for the discretization of the fluid dynamics equations -are based on a weakly compressible fluid formulation, -which is suitable for the problems with incompressible flows, -and compressible flows with low Mach number (less than 0.3). -The solid dynamics equations are discretized by a total Lagrangian formulation, -which is suitable to study the problems involving linear and non-linear elastic materials. -The FSI coupling algorithm is implemented in a kinematic-force fashion, -in which the solid structure surface describes the phase-interface and, -at the same time, experiences the surface forces imposed -by the fluid pressure and friction. - -Geometric models ------------------ -2D models can be built using basic shapes (polygon and circle) and full version of binary operations. -3D models can be generated by simple shapes (brick and sphere), -imported from external STL files and processed by applying simple binary operations, e.g. add and substract. - -Material models ------------------ -Newtonian fluids with isothermal linear equation of state. Non-newtonian fluids with Oldroyd-B model. -Linear elastic solid, non-linear elastic solid with Neo-Hookian model and anisotropic muscle model. - -Multi-resolution modeling ------------------ -Uniform resolution is used within each fluid or solid bodies. -However, it is allowed to use different resolutions for different bodies. -For example, one is able to using higher resolution for a solid body -which is interacting with a fluid body with lower resolution. - -Parallel Computing ------------------ -Intel Threading Building Blocks (TBB) is used for the multi-core parallelism. - -Authors ------------------ -Xiangyu Hu, Luhui Han, Chi Zhang, Shuoguo Zhang, Massoud Rezavand, Yongchuan Yu - -Project Principle Investigator ------------------ -Xiangyu Hu (xiangyu.hu@tum.de), Department of Mechanical Engineering, -Technical University of Munich - -Acknowledgements ------------------ -German Research Foundation (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 and HU1527/12-1. - -Please cite ------------------ -1. Luhui Han and Xiangyu Hu, -"SPH modeling of fluid-structure interaction", -Journal of Hydrodynamics, 2018: 30(1):62-69. - -2. Chi Zhang and Massoud Rezavand and Xiangyu Hu, -"Dual-criteria time stepping for weakly compressible smoothed particle hydrodynamics", -Journal of Computational Physics 404 (2020) 109135 - -3. Chi Zhang et al. -"SPHinXsys: An open-source meshless, multi-resolution and multi-physics library", -Software Impacts, 6 (2020) 100033 - -4. Chi Zhang, Massoud Rezavand, Xiangyu Hu, -"A multi-resolution SPH method for fluid-structure interactions", -Journal of Computational Physics, in press (2021) - -5. Chi Zhang, Yanji Wei, Frederic Dias, Xiangyu Hu, -"An efficient fully Lagrangian solver for modeling wave interaction with oscillating wave energy converter", -arXiv:2012.05323 - -6. Chi Zhang, Jianhang Wang, Massoud Rezavand, Dong Wu, Xiangyu Hu, -"An integrative smoothed particle hydrodynamics framework for modeling cardiac function", - arXiv:2009.03759 diff --git a/SPHINXsys/src/CMakeLists.txt b/SPHINXsys/src/CMakeLists.txt deleted file mode 100644 index 4779204823..0000000000 --- a/SPHINXsys/src/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -# shared build -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Dirsearch_shared) - -## prepare dirctoriesfor head and source files -HEADER_DIRECTORIES_SHARED(headdirs_shared) -SOURCE_DIRECTORIES_SHARED(sourcedirs_shared) - -SET(usefuldirs ${headdirs_shared} ${sourcedirs_shared}) -LIST(REMOVE_DUPLICATES usefuldirs) - -SET(usefulsubdirs ${usefuldirs}) -LIST(REMOVE_ITEM usefulsubdirs ${CMAKE_CURRENT_SOURCE_DIR}) - -if(DEFINED BOOST_AVAILABLE) - ADD_SUBDIRECTORY(for_2D_build) -endif() -ADD_SUBDIRECTORY(for_3D_build) -ADD_SUBDIRECTORY(shared) - -if(BUILD_WITH_IMAGE_PROCESS) -add_subdirectory(image_processing) -endif() \ No newline at end of file diff --git a/SPHINXsys/src/Readme.txt b/SPHINXsys/src/Readme.txt deleted file mode 100644 index 9849a16c94..0000000000 --- a/SPHINXsys/src/Readme.txt +++ /dev/null @@ -1,5 +0,0 @@ -Here, the source codes shared for 2d and 3d builds are in folder /shared. -The source codes for 2d build are in folder /for_2d_build. -The source codes for 3d build are in folder /for_3d_build. -Note that, in order to build both 2d and 3d codes at the same project, the make files should be arranged very carefully. - diff --git a/SPHINXsys/src/for_2D_build/CMakeLists.txt b/SPHINXsys/src/for_2D_build/CMakeLists.txt deleted file mode 100644 index df42e6d71c..0000000000 --- a/SPHINXsys/src/for_2D_build/CMakeLists.txt +++ /dev/null @@ -1,81 +0,0 @@ -## 2D build -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir - -## prepare dirctories for head and source files -HEADER_DIRECTORIES_SHARED(headdirs_shared) -SOURCE_DIRECTORIES_SHARED(sourcedirs_shared) - -include(Dirsearch_for_2D_build) -HEADER_DIRECTORIES_2D(headdirs_2D) -SOURCE_DIRECTORIES_2D(sourcedirs_2D) - -SET(usefuldirs ${headdirs_2D} ${sourcedirs_2D}) -LIST(REMOVE_DUPLICATES usefuldirs) - -SET(usefulsubdirs ${usefuldirs}) -LIST(REMOVE_ITEM usefulsubdirs ${CMAKE_CURRENT_SOURCE_DIR}) - -##Add all useful subdirectories -FOREACH(subdir_path ${usefulsubdirs}) - #message(STATUS ${subdir_path}) - ADD_SUBDIRECTORY(${subdir_path}) -ENDFOREACH() - -if(BUILD_WITH_IMAGE_PROCESS) - include(Dirsearch_for_image_process) - HEADER_DIRECTORIES_IMAGE_PROCESS(headdirs_image_process) - SOURCE_DIRECTORIES_IMAGE_PROCESS(sourcedirs_image_process) - ## combin head and souce directories - SET(headdirs ${headdirs_shared} ${headdirs_2D} ${headdirs_image_process}) - SET(sourcedirs ${sourcedirs_shared} ${sourcedirs_2D} ${sourcedirs_image_process}) -else(BUILD_WITH_IMAGE_PROCESS) - ## combin head and souce directories - SET(headdirs ${headdirs_shared} ${headdirs_2D}) - SET(sourcedirs ${sourcedirs_shared} ${sourcedirs_2D}) -endif(BUILD_WITH_IMAGE_PROCESS) - - -##Add all header dirs -FOREACH(headdir_path ${headdirs}) - #message(STATUS ${headdir_path}) - INCLUDE_DIRECTORIES("${headdir_path}") -ENDFOREACH() - -##Add all source files -set(SCR_FILES "") -FOREACH(srcdir_path ${sourcedirs}) - #message(STATUS ${srcdir_path}) - set(DIR_scrs "") - AUX_SOURCE_DIRECTORY(${srcdir_path} DIR_scrs) - list(APPEND SCR_FILES ${DIR_scrs}) -ENDFOREACH() - -#FOREACH(file1 ${SCR_FILES}) - #message(STATUS ${file1}) -#ENDFOREACH() - -ADD_LIBRARY(sphinxsys_2d SHARED ${SCR_FILES}) -ADD_LIBRARY(sphinxsys_static_2d STATIC ${SCR_FILES}) - -SET_TARGET_PROPERTIES(sphinxsys_static_2d PROPERTIES OUTPUT_NAME "sphinxsys_2d") -#SET_TARGET_PROPERTIES(sphinxsys PROPERTIES VERSION 1.0 SOVERSION 0) - -SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) - -if(MSVC) - target_link_libraries(sphinxsys_2d ${TBB_LIBRARYS} ${Simbody_LIBRARIES}) -else(MSVC) - if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - target_link_libraries(sphinxsys_2d ${TBB_LIBRARYS} ${Simbody_LIBRARIES} ${Boost_LIBRARIES} stdc++) - else(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - target_link_libraries(sphinxsys_2d ${TBB_LIBRARYS} ${Simbody_LIBRARIES} stdc++ stdc++fs) - endif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") -endif(MSVC) - -INSTALL(TARGETS sphinxsys_2d sphinxsys_static_2d -RUNTIME DESTINATION 2d_code/bin -LIBRARY DESTINATION 2d_code/lib -ARCHIVE DESTINATION 2d_code/lib) - -FILE(GLOB_RECURSE hpp_headers ${PROJECT_SOURCE_DIR}/src/shared/*.hpp ${PROJECT_SOURCE_DIR}/src/for_2D_build/*.hpp) -INSTALL(FILES ${hpp_headers} DESTINATION 2d_code/include) diff --git a/SPHINXsys/src/for_2D_build/bodies/CMakeLists.txt b/SPHINXsys/src/for_2D_build/bodies/CMakeLists.txt deleted file mode 100644 index 9ef366a60f..0000000000 --- a/SPHINXsys/src/for_2D_build/bodies/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/bodies/solid_body_supplementary.cpp b/SPHINXsys/src/for_2D_build/bodies/solid_body_supplementary.cpp deleted file mode 100644 index 5729e7e3e8..0000000000 --- a/SPHINXsys/src/for_2D_build/bodies/solid_body_supplementary.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @file base_body_supplementary.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#include "solid_body.h" -#include "solid_particles.h" - -namespace SPH -{ - //=================================================================================================// - void SolidBodyPartForSimbody::tagBodyPart() - { - BodyPartByParticle::tagBodyPart(); - - Real body_part_volume(0); - Vecd mass_center = Vecd(0); - for (size_t i = 0; i < body_part_particles_.size(); ++i) - { - size_t index_i = body_part_particles_[i]; - Real particle_volume = solid_particles_->Vol_[index_i]; - mass_center += particle_volume * solid_particles_->pos_0_[index_i]; - body_part_volume += particle_volume; - } - - mass_center /= body_part_volume; - initial_mass_center_ = Vec3d(mass_center[0], mass_center[1], 0.0); - - //computing unit intertia - Real Ix = 0.0; - Real Iy = 0.0; - Real Iz = 0.0; - for (size_t i = 0; i < body_part_particles_.size(); ++i) - { - size_t index_i = body_part_particles_[i]; - Vecd particle_position = solid_particles_->pos_0_[index_i]; - Real particle_volume = solid_particles_->Vol_[index_i]; - - Real r_x = (particle_position[1] - mass_center[1]); - Ix += particle_volume * r_x * r_x; - Real r_y = (particle_position[0] - mass_center[0]); - Iy += particle_volume * r_y * r_y; - Iz += particle_volume - * (particle_position - mass_center).normSqr(); - } - Ix /= body_part_volume; - Iy /= body_part_volume; - Iz /= body_part_volume; - - body_part_mass_properties_ - = new SimTK::MassProperties(body_part_volume * solid_body_density_, - Vec3d(0), SimTK::UnitInertia(Ix, Iy, Iz)); - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/for_2D_build/common/CMakeLists.txt b/SPHINXsys/src/for_2D_build/common/CMakeLists.txt deleted file mode 100644 index 9ef366a60f..0000000000 --- a/SPHINXsys/src/for_2D_build/common/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/common/data_type.h b/SPHINXsys/src/for_2D_build/common/data_type.h deleted file mode 100644 index ede09d2796..0000000000 --- a/SPHINXsys/src/for_2D_build/common/data_type.h +++ /dev/null @@ -1,39 +0,0 @@ - -#ifndef DATA_TYPE_2D_H -#define DATA_TYPE_2D_H - - -#include "base_data_type.h" - -namespace SPH { - - //for 2d build - using Veci = Vec2i; - using Vecu = Vec2u; - using Vecd = Vec2d; - using Matd = Mat2d; - using SymMatd = SymMat2d; - using AngularVecd = Real; - const int indexAngularVector = 0; - - - using Transformd = Transform2d; - - template - using PackageDataMatrix = std::array, ARRAY_SIZE>; - - template - using MeshDataMatrix = DataType**; - - /** only works for smoothing length ratio less or equal than 1.3*/ - constexpr int MaximumNeighborhoodSize = int(M_PI * 9); - const int Dimensions = 2; - - /** correction matrix, only works for thin structure dynamics. */ - const Matd reduced_unit_matrix = { 1, 0, 0, 0 }; - - /** initial local normal, only works for thin structure dynamics. */ - const Vecd local_pseudo_n_0 = Vecd(0.0, 1.0); -} - -#endif //DATA_TYPE_2D_H \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/common/scalar_functions_supplementary.cpp b/SPHINXsys/src/for_2D_build/common/scalar_functions_supplementary.cpp deleted file mode 100644 index 96d76c90f1..0000000000 --- a/SPHINXsys/src/for_2D_build/common/scalar_functions_supplementary.cpp +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @file scalar_functions_supplementary.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - * @version 0.1 - */ - -#include "scalar_functions.h" - -namespace SPH { - //=================================================================================================// - int SecondAxis(int axis_direction) { - return axis_direction == 1 ? 0 : 1; - } -} diff --git a/SPHINXsys/src/for_2D_build/geometries/CMakeLists.txt b/SPHINXsys/src/for_2D_build/geometries/CMakeLists.txt deleted file mode 100644 index 9ef366a60f..0000000000 --- a/SPHINXsys/src/for_2D_build/geometries/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/geometries/geometry.cpp b/SPHINXsys/src/for_2D_build/geometries/geometry.cpp deleted file mode 100644 index e0415c8e5c..0000000000 --- a/SPHINXsys/src/for_2D_build/geometries/geometry.cpp +++ /dev/null @@ -1,297 +0,0 @@ -/** - * @file geometry.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#include "geometry.h" - -using namespace boost::geometry; - -namespace SPH { - //=================================================================================================// - boost_multi_poly MultiPolygon::MultiPolygonByBooleanOps(boost_multi_poly multi_poly_in, - boost_multi_poly multi_poly_op, ShapeBooleanOps boolean_op) - { - boost_multi_poly multi_poly_tmp_in = multi_poly_in; - //out multi-poly need to be emtpy - //otherwise the operation is not valid - boost_multi_poly multi_poly_tmp_out; - - switch (boolean_op) - { - case ShapeBooleanOps::add: { - boost::geometry::union_(multi_poly_tmp_in, multi_poly_op, multi_poly_tmp_out); - break; - } - - case ShapeBooleanOps::sub: { - boost::geometry::difference(multi_poly_tmp_in, multi_poly_op, multi_poly_tmp_out); - break; - } - case ShapeBooleanOps::sym_diff: { - boost::geometry::sym_difference(multi_poly_tmp_in, multi_poly_op, multi_poly_tmp_out); - break; - } - case ShapeBooleanOps::intersect: { - boost::geometry::intersection(multi_poly_tmp_in, multi_poly_op, multi_poly_tmp_out); - break; - } - default: - { - std::cout << "\n FAILURE: the type of boolean operation is undefined!" << std::endl; - std::cout << "\n Please check the boost libraray reference." << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - break; - } - } - return multi_poly_tmp_out; - } - //=================================================================================================// - void MultiPolygon::addAMultiPolygon(MultiPolygon& multi_polygon_op, ShapeBooleanOps op) - { - multi_poly_ = MultiPolygonByBooleanOps(multi_poly_, multi_polygon_op.getBoostMultiPoly(), op); - } - //=================================================================================================// - void MultiPolygon::addABoostMultiPoly(boost_multi_poly& boost_multi_poly_op, ShapeBooleanOps op) - { - multi_poly_ = MultiPolygonByBooleanOps(multi_poly_, boost_multi_poly_op, op); - } - //=================================================================================================// - void MultiPolygon::addACircle(Vec2d center, Real radius, int resolution, ShapeBooleanOps op) - { - Vec2d buffer_center = center; - Real buffer_radius = radius; - int buffer_res = resolution; - - // Declare the point_circle strategy - strategy::buffer::join_round join_strategy; - strategy::buffer::end_round end_strategy; - strategy::buffer::side_straight side_strategy; - strategy::buffer::point_circle circle_strategy(buffer_res); - strategy::buffer::distance_symmetric circle_dist_strategy(buffer_radius); - - // Create the buffer of a multi point - model::d2::point_xy circle_center_pnt; - - boost::geometry::set<0>(circle_center_pnt, buffer_center[0]); - boost::geometry::set<1>(circle_center_pnt, buffer_center[1]); - - boost_multi_poly multi_poly_circle; - buffer(circle_center_pnt, multi_poly_circle, - circle_dist_strategy, side_strategy, - join_strategy, end_strategy, circle_strategy); - - if (!is_valid(multi_poly_circle)) { - std::cout << "\n Error: the multi ploygen is not valid." << std::endl; - std::cout << "\n The points must be in clockwise. Please check the boost libraray reference." << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - - multi_poly_ = MultiPolygonByBooleanOps(multi_poly_, multi_poly_circle, op); - } - //=================================================================================================// - void MultiPolygon::addAPolygon(std::vector& points, ShapeBooleanOps op) - { - std::vector> pts; - for (const Vecd& pnt : points) - { - pts.push_back(model::d2::point_xy(pnt[0], pnt[1])); - } - - boost_poly poly; - append(poly, pts); - if (!is_valid(poly)) { - std::cout << "\n Try to reverse the points to clockwise." << std::endl; - poly.clear(); - std::vector> pts_reverse(pts.rbegin(), pts.rend()); - append(poly, pts_reverse); - if (!is_valid(poly)) { - std::cout << "\n Error: the multi ploygen is still not valid. Please check the boost libraray reference." << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - } - - boost_multi_poly multi_poly_polygen; - convert(poly, multi_poly_polygen); - - multi_poly_ = MultiPolygonByBooleanOps(multi_poly_, multi_poly_polygen, op); - } - //=================================================================================================// - bool MultiPolygon::checkContain(Vec2d pnt, bool BOUNDARY_INCLUDED /*= true*/) - { - if (BOUNDARY_INCLUDED) - { - return covered_by(model::d2::point_xy(pnt[0], pnt[1]), multi_poly_); - } - else - { - return within(model::d2::point_xy(pnt[0], pnt[1]), multi_poly_); - } - } - //=================================================================================================// - Vec2d MultiPolygon::findClosestPoint(Vec2d& input_pnt) - { - typedef model::d2::point_xy pnt_type; - typedef model::referring_segment> seg_type; - /* - typedef model::segment> seg_type; - From the documentation on segment and referring_segment, the only difference between the two is that - referring_segment holds a reference to the points. - This is what is needed in a for each that modifies the segment since the points modified should be - reflected in the linestring. In a for each that does not modify the points, it should still take a - reference (most likely a const reference) since it reduces the amount of copying. - */ - pnt_type input_p(input_pnt[0], input_pnt[1]); - model::segment> closest_seg; - Real closest_dist_2seg = boost::numeric::bounds::highest(); - std::function findclosestsegment = [&closest_seg, &closest_dist_2seg, &input_p](seg_type seg) { - Real dist = boost::geometry::distance(input_p, seg); - if (dist < closest_dist_2seg) { - closest_dist_2seg = dist; - //closest_seg.append(seg); - Real x0 = boost::geometry::get<0, 0>(seg); - Real y0 = boost::geometry::get<0, 1>(seg); - Real x1 = boost::geometry::get<1, 0>(seg); - Real y1 = boost::geometry::get<1, 1>(seg); - boost::geometry::set<0, 0>(closest_seg, x0); - boost::geometry::set<0, 1>(closest_seg, y0); - boost::geometry::set<1, 0>(closest_seg, x1); - boost::geometry::set<1, 1>(closest_seg, y1); - } - }; - boost::geometry::for_each_segment(multi_poly_, findclosestsegment); - - Vec2d p_find(0, 0); - - Real x0 = boost::geometry::get<0, 0>(closest_seg); - Real y0 = boost::geometry::get<0, 1>(closest_seg); - Real x1 = boost::geometry::get<1, 0>(closest_seg); - Real y1 = boost::geometry::get<1, 1>(closest_seg); - Vec2d p_0(x0, y0); - Vec2d p_1(x1, y1); - Vec2d vec_v = p_1 - p_0; - Vec2d vec_w = input_pnt - p_0; - - Real c1 = dot(vec_v, vec_w); - if (c1 <= 0) { - p_find = p_0; - } - else { - Real c2 = dot(vec_v, vec_v); - if (c2 <= c1) { - p_find = p_1; - } - else { - p_find = p_0 + vec_v * c1 / c2; - } - } - - return p_find; - } - //=================================================================================================// - BoundingBox MultiPolygon::findBounds() - { - Vec2d lower_bound(0), upper_bound(0); - typedef boost::geometry::model::box> box; - lower_bound[0] = boost::geometry::return_envelope(multi_poly_).min_corner().get<0>(); - lower_bound[1] = boost::geometry::return_envelope(multi_poly_).min_corner().get<1>(); - upper_bound[0] = boost::geometry::return_envelope(multi_poly_).max_corner().get<0>(); - upper_bound[1] = boost::geometry::return_envelope(multi_poly_).max_corner().get<1>(); - return BoundingBox(lower_bound, upper_bound); - } - //=================================================================================================// - bool ComplexShape::checkContain(Vecd& input_pnt, bool BOUNDARY_INCLUDED) - { - return multi_ploygen_.checkContain(input_pnt, BOUNDARY_INCLUDED); - } - //=================================================================================================// - Vec2d ComplexShape::findClosestPoint(Vec2d& input_pnt) - { - return multi_ploygen_.findClosestPoint(input_pnt); - } - //=================================================================================================// - bool ComplexShape::checkNotFar(Vec2d& input_pnt, Real threshold) - { - return multi_ploygen_.checkContain(input_pnt) || checkNearSurface(input_pnt, threshold) ? true : false; - } - //=================================================================================================// - bool ComplexShape::checkNearSurface(Vec2d& input_pnt, Real threshold) - { - return getMaxAbsoluteElement(input_pnt - multi_ploygen_.findClosestPoint(input_pnt)) < threshold ? - true : false; - } - //=================================================================================================// - Real ComplexShape::findSignedDistance(Vec2d& input_pnt) - { - Real distance_to_surface = (findClosestPoint(input_pnt) - input_pnt).norm(); - return checkContain(input_pnt) ? -distance_to_surface : distance_to_surface; - } - //=================================================================================================// - Vec2d ComplexShape::findNormalDirection(Vec2d& input_pnt) - { - bool is_contain = checkContain(input_pnt); - Vecd displacement_to_surface = findClosestPoint(input_pnt) - input_pnt; - while(displacement_to_surface.norm() < Eps) { - Vecd jittered = input_pnt; //jittering - for (int l = 0; l != input_pnt.size(); ++l) - jittered[l] = input_pnt[l] + (((Real)rand() / (RAND_MAX)) - 0.5) * 100.0 * Eps; - if(checkContain(jittered) == is_contain) - displacement_to_surface = findClosestPoint(jittered) - jittered; - } - Vecd direction_to_surface = displacement_to_surface.normalize(); - return is_contain ? direction_to_surface : -1.0 * direction_to_surface; - } - //=================================================================================================// - BoundingBox ComplexShape::findBounds() - { - return multi_ploygen_.findBounds(); - } - //=================================================================================================// - void ComplexShape::addAMultiPolygon(MultiPolygon& multi_polygon, ShapeBooleanOps op) - { - multi_ploygen_.addAMultiPolygon(multi_polygon, op); - } - //=================================================================================================// - void ComplexShape::addABoostMultiPoly(boost_multi_poly& boost_multi_poly, ShapeBooleanOps op) - { - multi_ploygen_.addABoostMultiPoly(boost_multi_poly, op); - } - //=================================================================================================// - void ComplexShape::addAPolygon(std::vector& points, ShapeBooleanOps op) - { - multi_ploygen_.addAPolygon(points, op); - } - //=================================================================================================// - void ComplexShape:: - addAPolygonFromFile(std::string file_path_name, ShapeBooleanOps op, Vec2d translation, Real scale_factor) - { - std::fstream dataFile(file_path_name); - Vecd temp_point; - std::vector coordinates; - double temp1 = 0.0, temp2 = 0.0; - if (dataFile.fail()) - { - std::cout << "File can not open.\n" << std::endl;; - } - - while (!dataFile.fail() && !dataFile.eof()) - { - dataFile >> temp1 >> temp2; - temp_point[0] = temp1 * scale_factor + translation[0]; - temp_point[1] = temp2 * scale_factor + translation[1]; - coordinates.push_back(temp_point); - } - dataFile.close(); - - multi_ploygen_.addAPolygon(coordinates, op); - } - //=================================================================================================// - void ComplexShape::addACircle(Vec2d center, Real radius, int resolution, ShapeBooleanOps op) - { - multi_ploygen_.addACircle(center, radius, resolution, op); - } - //=================================================================================================// -} \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/geometries/geometry.h b/SPHINXsys/src/for_2D_build/geometries/geometry.h deleted file mode 100644 index 1caef867c4..0000000000 --- a/SPHINXsys/src/for_2D_build/geometries/geometry.h +++ /dev/null @@ -1,103 +0,0 @@ -/** -* @file geometry.h -* @brief Here, we define the 2D geometric algortihms. they are based on the boost library. -* @details The idea is to define complex geometry based on shapes, usually -* multi-polygon using boost library. we propose only very simple combinaton -* that the region is composed of shapes without intersection. -* That is, the shapes are those contain each other or without overlap. -* This strict requirement suggests that complex shapes should be finished -* already in modeling using related binary operations before it is included. -* @author Luhui Han, Chi ZHang and Xiangyu Hu -*/ - - -#ifndef GEOMETRY_2D_H -#define GEOMETRY_2D_H - -#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING - -//boost library -#include -#include -#include -#include -#include -#include - -#include "base_data_package.h" -#include "base_geometry.h" - -#include -#include -#include - -BOOST_GEOMETRY_REGISTER_BOOST_TUPLE_CS(cs::cartesian) - -using namespace boost::geometry; - -namespace SPH { - - /** - * @brief preclaimed classes. - */ - class Kernel; - - typedef model::polygon> boost_poly; - typedef model::multi_polygon boost_multi_poly; - - /** - * @class MultiPolygon - * @brief used to define a closed region - */ - class MultiPolygon : public Shape - { - public: - MultiPolygon() :Shape("MultiPolygon") {}; - boost_multi_poly& getBoostMultiPoly() { return multi_poly_; }; - bool checkContain(Vec2d pnt, bool BOUNDARY_INCLUDED = true); - Vec2d findClosestPoint(Vec2d& input_pnt); - virtual BoundingBox findBounds() override; - - void addAMultiPolygon(MultiPolygon& multi_polygon, ShapeBooleanOps op); - void addABoostMultiPoly(boost_multi_poly& boost_multi_poly, ShapeBooleanOps op); - void addAPolygon(std::vector& points, ShapeBooleanOps op); - void addACircle(Vec2d center, Real radius, int resolution, ShapeBooleanOps op); - - protected: - boost_multi_poly multi_poly_; - boost_multi_poly MultiPolygonByBooleanOps(boost_multi_poly multi_poly_in, - boost_multi_poly multi_poly_op, ShapeBooleanOps boolean_op); - }; - - /** - * @class ComplexShape - * @brief gives the final geomtrical definition of the SPHBody - */ - class ComplexShape : public Shape - { - Vec2d findClosestPoint(Vec2d& input_pnt); - public: - /** Default constructor. */ - ComplexShape() : Shape("ComplexShape"), multi_ploygen_() {}; - ComplexShape(std::string complex_shape_name) : Shape(complex_shape_name), multi_ploygen_() {}; - virtual ~ComplexShape() {}; - virtual BoundingBox findBounds() override; - void addAMultiPolygon(MultiPolygon& multi_polygon, ShapeBooleanOps op); - void addABoostMultiPoly(boost_multi_poly& boost_multi_poly, ShapeBooleanOps op); - void addAPolygon(std::vector& points, ShapeBooleanOps op); - void addACircle(Vec2d center, Real radius, int resolution, ShapeBooleanOps op); - void addAPolygonFromFile(std::string file_path_name, ShapeBooleanOps op, Vec2d translation = Vecd(0), Real scale_factor = 1.0); - - virtual bool checkContain(Vec2d& input_pnt, bool BOUNDARY_INCLUDED = true); - virtual bool checkNotFar(Vec2d& input_pnt, Real threshold); - virtual bool checkNearSurface(Vec2d& input_pnt, Real threshold); - /** Signed distance is negative for point within the complex shape. */ - virtual Real findSignedDistance(Vec2d& input_pnt); - /** Normal direction point toward outside of the complex shape. */ - virtual Vec2d findNormalDirection(Vec2d& input_pnt); - protected: - MultiPolygon multi_ploygen_; - }; -} - -#endif //GEOMETRY_2D_H \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/geometries/level_set_supplementary.cpp b/SPHINXsys/src/for_2D_build/geometries/level_set_supplementary.cpp deleted file mode 100644 index 2629165ab8..0000000000 --- a/SPHINXsys/src/for_2D_build/geometries/level_set_supplementary.cpp +++ /dev/null @@ -1,440 +0,0 @@ -/** - * @file level_set_supplementary.cpp - * @author Luhui Han, Chi ZHang, Yongchuan Yu and Xiangyu Hu - */ - -#include "level_set.h" - -#include "particle_adaptation.h" -#include "base_kernel.h" -#include "base_particles.h" -#include "base_body.h" - - -//=================================================================================================// -namespace SPH { - //=============================================================================================// - void LevelSetDataPackage::initializeWithUniformData(Real level_set) - { - for (int i = 0; i != PackageSize(); ++i) - for (int j = 0; j != PackageSize(); ++j) { - phi_[i][j] = level_set; - n_[i][j] = Vecd(1.0); - kernel_weight_[i][j] = level_set < 0.0 ? 0 : 1.0; - kernel_gradient_[i][j] = Vecd(0.0); - near_interface_id_[i][j] = level_set < 0.0 ? -2 : 2; - } - } - //=================================================================================================// - void LevelSetDataPackage::initializeBasicData(ComplexShape& complex_shape) - { - for (int i = 0; i != PackageSize(); ++i) - for (int j = 0; j != PackageSize(); ++j) - { - Vec2d position = data_lower_bound_ + Vec2d((Real)i * grid_spacing_, (Real)j * grid_spacing_); - phi_[i][j] = complex_shape.findSignedDistance(position); - near_interface_id_[i][j] = phi_[i][j] < 0.0 ? -2 : 2; - } - } - //=================================================================================================// - void LevelSetDataPackage::computeKernelIntegrals(LevelSet& level_set) - { - for (int i = 0; i != PackageSize(); ++i) - for (int j = 0; j != PackageSize(); ++j) - { - Vec2d position = data_lower_bound_ + Vec2d((Real)i * grid_spacing_, (Real)j * grid_spacing_); - kernel_weight_[i][j] = level_set.computeKernelIntegral(position); - kernel_gradient_[i][j] = level_set.computeKernelGradientIntegral(position); - } - } - //=================================================================================================// - void LevelSetDataPackage::stepReinitialization() - { - for (int i = AddressBufferWidth(); i != OperationUpperBound(); ++i) - for (int j = AddressBufferWidth(); j != OperationUpperBound(); ++j) - { - //only reinitialize non cut cells - if (*near_interface_id_addrs_[i][j] != 0) - { - Real phi_0 = *phi_addrs_[i][j]; - Real s = phi_0 / sqrt(phi_0 * phi_0 + grid_spacing_ * grid_spacing_); - //x direction - Real dv_xp = (*phi_addrs_[i + 1][j] - phi_0); - Real dv_xn = (phi_0 - *phi_addrs_[i - 1][j]); - Real dv_x = dv_xp; - if (s * dv_xp >= 0.0 && s * dv_xn >= 0.0) dv_x = dv_xn; - if (s * dv_xp <= 0.0 && s * dv_xn <= 0.0) dv_x = dv_xp; - if (s * dv_xp > 0.0 && s * dv_xn < 0.0) dv_x = 0.0; - if (s * dv_xp < 0.0 && s * dv_xn > 0.0) - { - Real ss = s * (fabs(dv_xp) - fabs(dv_xn)) / (dv_xp - dv_xn); - if (ss > 0.0) dv_x = dv_xn; - } - //y direction - Real dv_yp = (*phi_addrs_[i][j + 1] - phi_0); - Real dv_yn = (phi_0 - *phi_addrs_[i][j - 1]); - Real dv_y = dv_yp; - if (s * dv_yp >= 0.0 && s * dv_yn >= 0.0) dv_y = dv_yn; - if (s * dv_yp <= 0.0 && s * dv_yn <= 0.0) dv_y = dv_yp; - if (s * dv_yp > 0.0 && s * dv_yn < 0.0) dv_y = 0.0; - if (s * dv_yp < 0.0 && s * dv_yn > 0.0) - { - Real ss = s * (fabs(dv_yp) - fabs(dv_yn)) / (dv_yp - dv_yn); - if (ss > 0.0) dv_y = dv_yn; - } - //time stepping - *phi_addrs_[i][j] -= 0.5 * s * (sqrt(dv_x * dv_x + dv_y * dv_y) - grid_spacing_); - } - } - } - //=================================================================================================// - void LevelSetDataPackage::markNearInterface() - { - Real small_shift = 0.75 * grid_spacing_; - //corner averages, note that the first row and first column are not used - PackageTemporaryData corner_averages; - for (int i = 1; i != AddressSize(); ++i) - for (int j = 1; j != AddressSize(); ++j) - { - corner_averages[i][j] = CornerAverage(phi_addrs_, Veci(i, j), Veci(-1, -1)); - } - - for (int i = AddressBufferWidth(); i != OperationUpperBound(); ++i) - for (int j = AddressBufferWidth(); j != OperationUpperBound(); ++j) - { - //first assume far cells - Real phi_0 = *phi_addrs_[i][j]; - int near_interface_id = phi_0 > 0.0 ? 2 : -2; - - Real phi_average_0 = corner_averages[i][j]; - //find outer cut cells by comparing the sign of corner averages - for (int l = 0; l != 2; ++l) - for (int m = 0; m != 2; ++m) - { - int index_x = i + l; - int index_y = j + m; - Real phi_average = corner_averages[index_x][index_y]; - if ((phi_average_0 - small_shift) * (phi_average - small_shift) < 0.0) near_interface_id = 1; - if ((phi_average_0 + small_shift) * (phi_average + small_shift) < 0.0) near_interface_id = -1; - } - - //find zero cut cells by comparing the sign of corner averages - for (int l = 0; l != 2; ++l) - for (int m = 0; m != 2; ++m) - { - int index_x = i + l; - int index_y = j + m; - Real phi_average = corner_averages[index_x][index_y]; - if (phi_average_0 * phi_average < 0.0) near_interface_id = 0; - } - - //find cells between cut cells - if (fabs(phi_0) < small_shift && abs(near_interface_id) != 1) near_interface_id = 0; - - //assign this to package - *near_interface_id_addrs_[i][j] = near_interface_id; - } - } - //=================================================================================================// - bool LevelSet::isWithinCorePackage(Vecd position) - { - Vecu cell_index = CellIndexFromPosition(position); - return data_pkg_addrs_[cell_index[0]][cell_index[1]]->is_core_pkg_; - } - //=============================================================================================// - void LevelSet::initializeDataInACell(Vecu cell_index, Real dt) - { - int i = (int)cell_index[0]; - int j = (int)cell_index[1]; - - Vecd cell_position = CellPositionFromIndex(cell_index); - Real signed_distance = complex_shape_.findSignedDistance(cell_position); - Vecd normal_direction = complex_shape_.findNormalDirection(cell_position); - Real measure = getMaxAbsoluteElement(normal_direction * signed_distance); - if (measure < grid_spacing_) { - mutex_my_pool.lock(); - LevelSetDataPackage* new_data_pkg = data_pkg_pool_.malloc(); - mutex_my_pool.unlock(); - Vecd pkg_lower_bound = GridPositionFromCellPosition(cell_position); - new_data_pkg->initializePackageGeometry(pkg_lower_bound, data_spacing_); - new_data_pkg->initializeBasicData(complex_shape_); - core_data_pkgs_.push_back(new_data_pkg); - new_data_pkg->pkg_index_ = Vecu(i, j); - new_data_pkg->is_core_pkg_ = true; - data_pkg_addrs_[i][j] = new_data_pkg; - } - else { - data_pkg_addrs_[i][j] = complex_shape_.checkContain(cell_position) ? - singular_data_pkgs_addrs[0] : singular_data_pkgs_addrs[1]; - } - } - //=============================================================================================// - void LevelSet::tagACellIsInnerPackage(Vecu cell_index, Real dt) - { - int i = (int)cell_index[0]; - int j = (int)cell_index[1]; - - bool is_inner_pkg = false; - for (int l = SMAX(i - 1, 0); l <= SMIN(i + 1, int(number_of_cells_[0]) - 1); ++l) - for (int m = SMAX(j - 1, 0); m <= SMIN(j + 1, int(number_of_cells_[1]) - 1); ++m) - if (data_pkg_addrs_[l][m]->is_core_pkg_) is_inner_pkg = true; - - if (is_inner_pkg) - { - LevelSetDataPackage* current_data_pkg = data_pkg_addrs_[i][j]; - if (current_data_pkg->is_core_pkg_) { - current_data_pkg->is_inner_pkg_ = true; - inner_data_pkgs_.push_back(current_data_pkg); - } - else { - mutex_my_pool.lock(); - LevelSetDataPackage* new_data_pkg = data_pkg_pool_.malloc(); - mutex_my_pool.unlock(); - Vecd cell_position = CellPositionFromIndex(cell_index); - Vecd pkg_lower_bound = GridPositionFromCellPosition(cell_position); - new_data_pkg->initializePackageGeometry(pkg_lower_bound, data_spacing_); - new_data_pkg->initializeBasicData(complex_shape_); - new_data_pkg->pkg_index_ = Vecu(i, j); - new_data_pkg->is_inner_pkg_ = true; - inner_data_pkgs_.push_back(new_data_pkg); - data_pkg_addrs_[i][j] = new_data_pkg; - } - } - } - //=================================================================================================// - void LevelSet::redistanceInterfaceForAPackage(LevelSetDataPackage* core_data_pkg, Real dt) - { - int l = (int)core_data_pkg->pkg_index_[0]; - int m = (int)core_data_pkg->pkg_index_[1]; - - for (int i = pkg_addrs_buffer_; i != pkg_operations_; ++i) - for (int j = pkg_addrs_buffer_; j != pkg_operations_; ++j) - { - int near_interface_id = *core_data_pkg->near_interface_id_addrs_[i][j]; - if (near_interface_id == 0) - { - bool positive_band = false; - bool negative_band = false; - for (int s = -1; s < 2; ++s) - for (int t = -1; t < 2; ++t) - { - int neighbor_near_interface_id = - *core_data_pkg->near_interface_id_addrs_[i + s][j + t]; - if (neighbor_near_interface_id >= 1) positive_band = true; - if (neighbor_near_interface_id <= -1) negative_band = true; - } - if (positive_band == false) - { - Real min_distance_p = 5.0 * data_spacing_; - for (int x = -4; x != 5; ++x) - for (int y = -4; y != 5; ++y) - { - std::pair x_pair = CellShiftAndDataIndex(i + x); - std::pair y_pair = CellShiftAndDataIndex(j + y); - LevelSetDataPackage* neighbor_pkg - = data_pkg_addrs_[l + x_pair.first][m + y_pair.first]; - int neighbor_near_interface_id - = neighbor_pkg->near_interface_id_[x_pair.second][y_pair.second]; - if (neighbor_near_interface_id >= 1) - { - Real phi_p_ = neighbor_pkg->phi_[x_pair.second][y_pair.second]; - Vecd norm_to_face = neighbor_pkg->n_[x_pair.second][y_pair.second]; - min_distance_p = SMIN(min_distance_p, (Vecd((Real)x, (Real)y) * data_spacing_ + phi_p_ * norm_to_face).norm()); - } - } - *core_data_pkg->phi_addrs_[i][j] = -min_distance_p; - // this immediate switch of near interface id - // does not intervenning with the identification of unresolved interface - // based on the assumption that positive false_and negative bands are not close to each other - *core_data_pkg->near_interface_id_addrs_[i][j] = -1; - } - if (negative_band == false) - { - Real min_distance_n = 5.0 * data_spacing_; - for (int x = -4; x != 5; ++x) - for (int y = -4; y != 5; ++y) - { - std::pair x_pair = CellShiftAndDataIndex(i + x); - std::pair y_pair = CellShiftAndDataIndex(j + y); - LevelSetDataPackage* neighbor_pkg - = data_pkg_addrs_[l + x_pair.first][m + y_pair.first]; - int neighbor_near_interface_id - = neighbor_pkg->near_interface_id_[x_pair.second][y_pair.second]; - if (neighbor_near_interface_id <= -1) - { - Real phi_n_ = neighbor_pkg->phi_[x_pair.second][y_pair.second]; - Vecd norm_to_face = neighbor_pkg->n_[x_pair.second][y_pair.second]; - min_distance_n = SMIN(min_distance_n, (Vecd((Real)x, (Real)y) * data_spacing_ - phi_n_ * norm_to_face).norm()); - } - } - *core_data_pkg->phi_addrs_[i][j] = min_distance_n; - // this immediate switch of near interface id - // does not intervenning with the identification of unresolved interface - // based on the assumption that positive false_and negative bands are not close to each other - *core_data_pkg->near_interface_id_addrs_[i][j] = 1; - } - } - } - } - //=============================================================================================// - void LevelSet::writeMeshToPltFile(std::ofstream& output_file) - { - Vecu number_of_operation = total_data_points_; - - output_file << "\n"; - output_file << "title='View'" << "\n"; - output_file << "variables= " << "x, " << "y, " << "phi, " << "n_x, " << "n_y " << "near_interface_id "; - output_file << "kernel_weight, " << "kernel_gradient_x, " << "kernel_gradient_y " << "\n"; - output_file << "zone i=" << number_of_operation[0] << " j=" << number_of_operation[1] << " k=" << 1 - << " DATAPACKING=BLOCK SOLUTIONTIME=" << 0 << "\n"; - - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - Vecd data_position = DataPositionFromGlobalIndex(Vecu(i, j)); - output_file << data_position[0] << " "; - } - output_file << " \n"; - } - - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - Vecd data_position = DataPositionFromGlobalIndex(Vecu(i, j)); - output_file << data_position[1]<< " "; - } - output_file << " \n"; - } - - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - output_file << DataValueFromGlobalIndex, - &LevelSetDataPackage::phi_>(Vecu(i, j)) << " "; - } - output_file << " \n"; - } - - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - output_file << DataValueFromGlobalIndex, - &LevelSetDataPackage::n_>(Vecu(i, j))[0] << " "; - } - output_file << " \n"; - } - - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - output_file << DataValueFromGlobalIndex, - &LevelSetDataPackage::n_>(Vecu(i, j))[1] << " "; - } - output_file << " \n"; - } - - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - output_file << DataValueFromGlobalIndex, - &LevelSetDataPackage::near_interface_id_>(Vecu(i, j)) << " "; - } - output_file << " \n"; - } - - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - output_file << DataValueFromGlobalIndex, - &LevelSetDataPackage::kernel_weight_>(Vecu(i, j)) << " "; - } - output_file << " \n"; - } - - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - output_file << DataValueFromGlobalIndex, - &LevelSetDataPackage::kernel_gradient_>(Vecu(i, j))[0] << " "; - } - output_file << " \n"; - } - - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - output_file << DataValueFromGlobalIndex, - &LevelSetDataPackage::kernel_gradient_>(Vecu(i, j))[1] << " "; - } - output_file << " \n"; - } - } - //=============================================================================================// - Real LevelSet::computeKernelIntegral(const Vecd& position) - { - Real phi = probeSignedDistance(position); - Real cutoff_radius = kernel_.CutOffRadius(global_h_ratio_); - Real threshold = cutoff_radius + data_spacing_; //consider that interface's half width is the data spacing - - Real integral(0.0); - if (fabs(phi) < threshold) - { - Vecu global_index_ = DataGlobalIndexFromPosition(position); - for (int i = -3; i != 4; ++i) - for (int j = -3; j != 4; ++j) - { - Vecu neighbor_index = Vecu(global_index_[0] + i, global_index_[1] + j); - Real phi_neighbor = DataValueFromGlobalIndex, - &LevelSetDataPackage::phi_>(neighbor_index) - 0.5 * data_spacing_;; - if (phi_neighbor > -data_spacing_) { - Vecd displacement = position - DataPositionFromGlobalIndex(neighbor_index); - Real distance = displacement.norm(); - if (distance < cutoff_radius) - integral += kernel_.W(global_h_ratio_, distance, displacement) - * computeHeaviside(phi_neighbor, data_spacing_); - } - } - } - return phi > threshold ? 1.0 : integral * data_spacing_* data_spacing_; - } - //=============================================================================================// - Vecd LevelSet::computeKernelGradientIntegral(const Vecd& position) - { - Real phi = probeSignedDistance(position); - Real cutoff_radius = kernel_.CutOffRadius(global_h_ratio_); - Real threshold = cutoff_radius + data_spacing_; - - Vecd integral(0.0); - if (fabs(phi) < threshold) - { - Vecu global_index_ = DataGlobalIndexFromPosition(position); - for (int i = -3; i != 4; ++i) - for (int j = -3; j != 4; ++j) - { - Vecu neighbor_index = Vecu(global_index_[0] + i, global_index_[1] + j); - Real phi_neighbor = DataValueFromGlobalIndex, - &LevelSetDataPackage::phi_>(neighbor_index); - if (phi_neighbor > -data_spacing_) { - Vecd displacement = position - DataPositionFromGlobalIndex(neighbor_index); - Real distance = displacement.norm(); - if (distance < cutoff_radius) - integral += kernel_.dW(global_h_ratio_, distance, displacement) - * computeHeaviside(phi_neighbor, data_spacing_) * displacement / (distance + TinyReal); - } - } - } - - return integral* data_spacing_ * data_spacing_; - } - //=============================================================================================// -} -//=============================================================================================// diff --git a/SPHINXsys/src/for_2D_build/meshes/CMakeLists.txt b/SPHINXsys/src/for_2D_build/meshes/CMakeLists.txt deleted file mode 100644 index 9ef366a60f..0000000000 --- a/SPHINXsys/src/for_2D_build/meshes/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/meshes/base_mesh_supplementary.cpp b/SPHINXsys/src/for_2D_build/meshes/base_mesh_supplementary.cpp deleted file mode 100644 index 9a31411711..0000000000 --- a/SPHINXsys/src/for_2D_build/meshes/base_mesh_supplementary.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @file base_mesh_supplementary.cpp - * @author Luhui Han, Chi ZHang, Yongchuan Yu and Xiangyu Hu - */ - -#include "base_mesh.h" - -//=================================================================================================// -namespace SPH { - //=============================================================================================// - void MeshIterator(Vecu index_begin, Vecu index_end, MeshFunctor& mesh_functor, Real dt) - { - for (size_t i = index_begin[0]; i != index_end[0]; ++i) - for (size_t j = index_begin[1]; j != index_end[1]; ++j) { - mesh_functor(Vecu(i, j), dt); - } - } - //=============================================================================================// - void MeshIterator_parallel(Vecu index_begin, Vecu index_end, MeshFunctor& mesh_functor, Real dt) - { - parallel_for(blocked_range2d - (index_begin[0], index_end[0], index_begin[1], index_end[1]), - [&](const blocked_range2d& r) { - for (size_t i = r.rows().begin(); i != r.rows().end(); ++i) - for (size_t j = r.cols().begin(); j != r.cols().end(); ++j) - { - mesh_functor(Vecu(i, j), dt); - } - }, ap); - } - //=============================================================================================// - Vecu BaseMesh::transfer1DtoMeshIndex(Vecu number_of_grid_points, size_t i) - { - size_t row_size = number_of_grid_points[1]; - size_t column = i / row_size; - return Vec2u(column, i - column * row_size); - } - //=============================================================================================// - size_t BaseMesh::transferMeshIndexTo1D(Vecu number_of_grid_points, Vecu grid_index) - { - return grid_index[0] * number_of_grid_points[1] + grid_index[1]; - } - //=============================================================================================// - size_t BaseMesh::transferMeshIndexToMortonOrder(Vecu grid_index) - { - return MortonCode(grid_index[0]) | (MortonCode(grid_index[1]) << 1); - } -} -//=============================================================================================// diff --git a/SPHINXsys/src/for_2D_build/meshes/mesh_cell_linked_list.hpp b/SPHINXsys/src/for_2D_build/meshes/mesh_cell_linked_list.hpp deleted file mode 100644 index e9020be7bb..0000000000 --- a/SPHINXsys/src/for_2D_build/meshes/mesh_cell_linked_list.hpp +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @file body_relation.hpp - * @brief Here, Functions belong to BaseBody, RealBody and FictitiousBody are given. - * @author hi ZHang and Xiangyu Hu - */ - -#pragma once - -#include "base_particles.h" -#include "mesh_cell_linked_list.h" - -namespace SPH -{ - //=================================================================================================// - template - void MeshCellLinkedList::searchNeighborsByParticles(size_t total_real_particles, BaseParticles& source_particles, - ParticleConfiguration& particle_configuration, GetParticleIndex& get_particle_index, - GetSearchRange& get_search_range, GetNeighborRelation& get_neighbor_relation) - { - parallel_for(blocked_range(0, total_real_particles), - [&](const blocked_range& r) { - StdLargeVec& pos_n = source_particles.pos_n_; - for (size_t num = r.begin(); num != r.end(); ++num) { - size_t index_i = get_particle_index(num); - Vecd& particle_position = pos_n[index_i]; - int search_range = get_search_range(index_i); - Vecu target_cell_index = CellIndexFromPosition(particle_position); - int i = (int)target_cell_index[0]; - int j = (int)target_cell_index[1]; - - Neighborhood& neighborhood = particle_configuration[index_i]; - for (int l = SMAX(i - search_range, 0); l <= SMIN(i + search_range, int(number_of_cells_[0]) - 1); ++l) - for (int m = SMAX(j - search_range, 0); m <= SMIN(j + search_range, int(number_of_cells_[1]) - 1); ++m) - { - ListDataVector& target_particles = cell_linked_lists_[l][m].cell_list_data_; - for (const ListData& list_data : target_particles) - { - //displacement pointing from neighboring particle to origin particle - Vecd displacement = particle_position - list_data.second; - get_neighbor_relation(neighborhood, displacement, index_i, list_data.first); - } - } - } - }, ap); - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/for_2D_build/meshes/mesh_cell_linked_list_supplementary.cpp b/SPHINXsys/src/for_2D_build/meshes/mesh_cell_linked_list_supplementary.cpp deleted file mode 100644 index 7662cd16e2..0000000000 --- a/SPHINXsys/src/for_2D_build/meshes/mesh_cell_linked_list_supplementary.cpp +++ /dev/null @@ -1,243 +0,0 @@ -/** - * @file mesh_cell_linked_list.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#include "mesh_cell_linked_list.h" -#include "base_kernel.h" -#include "base_body.h" -#include "base_particles.h" -#include "neighbor_relation.h" - -namespace SPH { - //=================================================================================================// - CellList::CellList() - { - concurrent_particle_indexes_.reserve(12); - } - //=================================================================================================// - void MeshCellLinkedList::allocateMeshDataMatrix() - { - Allocate2dArray(cell_linked_lists_, number_of_cells_); - } - //=================================================================================================// - void MeshCellLinkedList::deleteMeshDataMatrix() - { - Delete2dArray(cell_linked_lists_, number_of_cells_); - } - //=================================================================================================// - void MeshCellLinkedList::clearCellLists() - { - parallel_for(blocked_range2d(0, number_of_cells_[0], 0, number_of_cells_[1]), - [&](const blocked_range2d& r) { - for (size_t i = r.rows().begin(); i != r.rows().end(); ++i) - for (size_t j = r.cols().begin(); j != r.cols().end(); ++j) { - cell_linked_lists_[i][j].concurrent_particle_indexes_.clear(); - cell_linked_lists_[i][j].real_particle_indexes_.clear(); - } - }, ap); - } - //=================================================================================================// - void MeshCellLinkedList::UpdateCellListData() - { - StdLargeVec& pos_n = base_particles_->pos_n_; - parallel_for(blocked_range2d(0, number_of_cells_[0], 0, number_of_cells_[1]), - [&](const blocked_range2d& r) { - for (size_t i = r.rows().begin(); i != r.rows().end(); ++i) - for (size_t j = r.cols().begin(); j != r.cols().end(); ++j) { - CellList& cell_list = cell_linked_lists_[i][j]; - cell_list.cell_list_data_.clear(); - for (size_t s = 0; s != cell_list.concurrent_particle_indexes_.size(); ++s) { - size_t particle_index = cell_list.concurrent_particle_indexes_[s]; - cell_list.cell_list_data_.emplace_back(std::make_pair(particle_index, pos_n[particle_index])); - } - } - }, ap); - } - //=================================================================================================// - void MeshCellLinkedList::updateSplitCellLists(SplitCellLists& split_cell_lists) - { - //clear the data - clearSplitCellLists(split_cell_lists); - - parallel_for(blocked_range2d(0, number_of_cells_[0], 0, number_of_cells_[1]), - [&](const blocked_range2d& r) { - for (size_t i = r.rows().begin(); i != r.rows().end(); ++i) - for (size_t j = r.cols().begin(); j != r.cols().end(); ++j) { - CellList& cell_list = cell_linked_lists_[i][j]; - size_t real_particles_in_cell = cell_list.concurrent_particle_indexes_.size(); - if (real_particles_in_cell != 0) { - for (size_t s = 0; s != real_particles_in_cell; ++s) - cell_list.real_particle_indexes_.push_back(cell_list.concurrent_particle_indexes_[s]); - split_cell_lists[transferMeshIndexTo1D(Vecu(3), Vecu(i % 3, j % 3))].push_back(&cell_linked_lists_[i][j]); - } - } - }, ap); - } - //=================================================================================================// - void MeshCellLinkedList - ::insertACellLinkedParticleIndex(size_t particle_index, Vecd particle_position) - { - Vecu cellpos = CellIndexFromPosition(particle_position); - cell_linked_lists_[cellpos[0]][cellpos[1]].concurrent_particle_indexes_.emplace_back(particle_index); - } - //=================================================================================================// - void MeshCellLinkedList - ::InsertACellLinkedListDataEntry(size_t particle_index, Vecd particle_position) - { - Vecu cellpos = CellIndexFromPosition(particle_position); - cell_linked_lists_[cellpos[0]][cellpos[1]].cell_list_data_ - .emplace_back(std::make_pair(particle_index, particle_position)); - } - //=================================================================================================// - ListData MeshCellLinkedList::findNearestListDataEntry(Vecd& position) - { - Real min_distance = Infinity; - ListData nearest_entry = std::make_pair(MaxSize_t, Vecd(Infinity)); - - Vecu cell_location = CellIndexFromPosition(position); - int i = (int)cell_location[0]; - int j = (int)cell_location[1]; - - for (int l = SMAX(i - 1, 0); l <= SMIN(i + 1, int(number_of_cells_[0]) - 1); ++l) - { - for (int m = SMAX(j - 1, 0); m <= SMIN(j + 1, int(number_of_cells_[1]) - 1); ++m) - { - ListDataVector& target_particles = cell_linked_lists_[l][m].cell_list_data_; - for (size_t n = 0; n != target_particles.size(); ++n) - { - Real distance = (position - target_particles[n].second).norm(); - if (distance < min_distance) - { - min_distance = distance; - nearest_entry = target_particles[n]; - } - } - } - } - return nearest_entry; - } - //=================================================================================================// - void MeshCellLinkedList:: - tagBodyPartByCell(CellLists& cell_lists, std::function& check_included) - { - for (int i = 0; i < (int)number_of_cells_[0]; ++i) - for (int j = 0; j < (int)number_of_cells_[1]; ++j) { - bool is_included = false; - for (int k = SMAX(i - 1, 0); k <= SMIN(i + 1, int(number_of_cells_[0]) - 1); ++k) - for (int l = SMAX(j - 1, 0); l <= SMIN(j + 1, int(number_of_cells_[1]) - 1); ++l) - { - if (check_included(CellPositionFromIndex(Vecu(k, l)), grid_spacing_)) - { - is_included = true; - } - } - if (is_included == true) cell_lists.push_back(&cell_linked_lists_[i][j]); - } - } - //=================================================================================================// - void MeshCellLinkedList:: - tagBodyDomainBoundingCells(StdVec& cell_lists, BoundingBox& body_domain_bounds, int axis) - { - int second_axis = SecondAxis(axis); - Vecu body_lower_bound_cell_ = CellIndexFromPosition(body_domain_bounds.first); - Vecu body_upper_bound_cell_ = CellIndexFromPosition(body_domain_bounds.second); - - //lower bound cells - for (size_t j = SMAX(int(body_lower_bound_cell_[second_axis]) - 1, 0); - j <= (size_t)SMIN(int(body_upper_bound_cell_[second_axis] + 1), int(number_of_cells_[second_axis] - 1)); ++j) - for (size_t i = SMAX(int(body_lower_bound_cell_[axis]) - 1, 0); - i <= (size_t)SMIN(int(body_lower_bound_cell_[axis] + 1), int(number_of_cells_[axis] - 1)); ++i) - { - Vecu cell_position(0); - cell_position[axis] = i; - cell_position[second_axis] = j; - cell_lists[0].push_back(&cell_linked_lists_[cell_position[0]][cell_position[1]]); - } - - //upper bound cells - for (size_t j = SMAX(int(body_lower_bound_cell_[second_axis]) - 1, 0); - j <= (size_t)SMIN(int(body_upper_bound_cell_[second_axis] + 1), int(number_of_cells_[second_axis] - 1)); ++j) - for (size_t i = SMAX(int(body_upper_bound_cell_[axis]) - 1, 0); - i <= (size_t)SMIN(int(body_upper_bound_cell_[axis] + 1), int(number_of_cells_[axis] - 1)); ++i) { - Vecu cell_position(0); - cell_position[axis] = i; - cell_position[second_axis] = j; - cell_lists[1].push_back(&cell_linked_lists_[cell_position[0]][cell_position[1]]); - } - } - //=================================================================================================// - void MeshCellLinkedList:: - tagMirrorBoundingCells(CellLists& cell_lists, BoundingBox& body_domain_bounds, int axis, bool positive) - { - int second_axis = SecondAxis(axis); - Vecu body_lower_bound_cell_ = CellIndexFromPosition(body_domain_bounds.first); - Vecu body_upper_bound_cell_ = CellIndexFromPosition(body_domain_bounds.second); - - if (positive) { - //upper bound cells - for (size_t j = SMAX(int(body_lower_bound_cell_[second_axis]) - 1, 0); - j < (size_t)SMIN(int(body_upper_bound_cell_[second_axis] + 2), int(number_of_cells_[second_axis])); ++j) - for (size_t i = SMAX(int(body_upper_bound_cell_[axis]) - 1, 0); - i <= (size_t)SMIN(int(body_upper_bound_cell_[axis] + 1), int(number_of_cells_[axis] - 1)); ++i) { - Vecu cell_position(0); - cell_position[axis] = i; - cell_position[second_axis] = j; - cell_lists.push_back(&cell_linked_lists_[cell_position[0]][cell_position[1]]); - } - } - else { - //lower bound cells - for (size_t j = SMAX(int(body_lower_bound_cell_[second_axis]) - 1, 0); - j < (size_t)SMIN(int(body_upper_bound_cell_[second_axis] + 2), int(number_of_cells_[second_axis])); ++j) - for (size_t i = SMAX(int(body_lower_bound_cell_[axis]) - 1, 0); - i <= (size_t)SMIN(int(body_lower_bound_cell_[axis] + 1), int(number_of_cells_[axis] - 1)); ++i) { - Vecu cell_position(0); - cell_position[axis] = i; - cell_position[second_axis] = j; - cell_lists.push_back(&cell_linked_lists_[cell_position[0]][cell_position[1]]); - } - } - } - //=============================================================================================// - void MeshCellLinkedList::writeMeshToPltFile(std::ofstream& output_file) - { - Vecu number_of_operation = number_of_cells_; - - output_file << "\n"; - output_file << "title='View'" << "\n"; - output_file << "variables= " << "x, " << "y, " << "particles_in_cell " << "\n"; - output_file << "zone i=" << number_of_operation[0] << " j=" << number_of_operation[1] << " k=" << 1 - << " DATAPACKING=BLOCK SOLUTIONTIME=" << 0 << "\n"; - - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - Vecd data_position = CellPositionFromIndex(Vecu(i, j)); - output_file << data_position[0] << " "; - } - output_file << " \n"; - } - - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - Vecd data_position = CellPositionFromIndex(Vecu(i, j)); - output_file << data_position[1] << " "; - } - output_file << " \n"; - } - - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - output_file << cell_linked_lists_[i][j].concurrent_particle_indexes_.size() << " "; - } - output_file << " \n"; - } - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/for_2D_build/meshes/mesh_with_data_packages.hpp b/SPHINXsys/src/for_2D_build/meshes/mesh_with_data_packages.hpp deleted file mode 100644 index e3c8c83b64..0000000000 --- a/SPHINXsys/src/for_2D_build/meshes/mesh_with_data_packages.hpp +++ /dev/null @@ -1,168 +0,0 @@ -/** -* @file base_mesh.hpp -* @brief This is the implementation of the template function and class for base mesh -* @author Chi ZHang and Xiangyu Hu -*/ - -#ifndef MESH_WITH_DATA_PACKAGES_2D_HPP -#define MESH_WITH_DATA_PACKAGES_2D_HPP - -#include "mesh_with_data_packages.h" - -//=================================================================================================// -namespace SPH { - //=================================================================================================// - template - template - DataType BaseDataPackage - ::probeDataPackage(PackageDataAddress& pkg_data_addrs, const Vecd& position) - { - Vecu grid_idx = CellIndexFromPosition(position); - Vecd grid_pos = GridPositionFromIndex(grid_idx); - Vecd alpha = (position - grid_pos) / grid_spacing_; - Vecd beta = Vec2d(1.0) - alpha; - - DataType bilinear - = *pkg_data_addrs[grid_idx[0]][grid_idx[1]] * beta[0] * beta[1] - + *pkg_data_addrs[grid_idx[0] + 1][grid_idx[1]] * alpha[0] * beta[1] - + *pkg_data_addrs[grid_idx[0]][grid_idx[1] + 1] * beta[0] * alpha[1] - + *pkg_data_addrs[grid_idx[0] + 1][grid_idx[1] + 1] * alpha[0] * alpha[1]; - - return bilinear; - } - //=================================================================================================// - template - template - void BaseDataPackage:: - computeGradient(PackageDataAddress& in_pkg_data_addrs, - PackageDataAddress out_pkg_data_addrs, Real dt) - { - for (int i = 1; i != PKG_SIZE + 1; ++i) - for (int j = 1; j != PKG_SIZE + 1; ++j) - { - Real dphidx = (*in_pkg_data_addrs[i + 1][j] - *in_pkg_data_addrs[i - 1][j]); - Real dphidy = (*in_pkg_data_addrs[i][j + 1] - *in_pkg_data_addrs[i][j - 1]); - *out_pkg_data_addrs[i][j] = Vecd(dphidx, dphidy); - } - } - //=================================================================================================// - template - template - void BaseDataPackage:: - computeNormalizedGradient(PackageDataAddress& in_pkg_data_addrs, - PackageDataAddress out_pkg_data_addrs, Real dt) - { - for (int i = 1; i != PKG_SIZE + 1; ++i) - for (int j = 1; j != PKG_SIZE + 1; ++j) - { - Real dphidx = (*in_pkg_data_addrs[i + 1][j] - *in_pkg_data_addrs[i - 1][j]); - Real dphidy = (*in_pkg_data_addrs[i][j + 1] - *in_pkg_data_addrs[i][j - 1]); - Vecd normal = Vecd(dphidx, dphidy); - *out_pkg_data_addrs[i][j] = normal / (normal.norm() + TinyReal); - } - } - //=================================================================================================// - template - template - void BaseDataPackage:: - initializePackageDataAddress(PackageData& pkg_data, - PackageDataAddress& pkg_data_addrs) - { - for (int i = 0; i != ADDRS_SIZE; ++i) - for (int j = 0; j != ADDRS_SIZE; ++j) - { - pkg_data_addrs[i][j] = &pkg_data[0][0]; - } - } - //=================================================================================================// - template - template - DataType BaseDataPackage:: - CornerAverage(PackageDataAddress& pkg_data_addrs, Veci addrs_index, Veci corner_direction) - { - DataType average(0); - for (int i = 0; i != 2; ++i) - for (int j = 0; j != 2; ++j) - { - int x_index = addrs_index[0] + i * corner_direction[0]; - int y_index = addrs_index[1] + j * corner_direction[1]; - average += *pkg_data_addrs[x_index][y_index]; - } - return average * 0.25; - } - //=================================================================================================// - template - template - void BaseDataPackage:: - assignPackageDataAddress(PackageDataAddress& pkg_data_addrs, Vecu& addrs_index, - PackageData& pkg_data, Vecu& data_index) - { - pkg_data_addrs[addrs_index[0]][addrs_index[1]] = &pkg_data[data_index[0]][data_index[1]]; - } - //=================================================================================================// - template - template - DataType MeshWithDataPackages:: - DataValueFromGlobalIndex(Vecu global_data_index) - { - Vecu pkg_index_(0); - Vecu local_data_index(0); - for (int n = 0; n != 2; n++) - { - size_t cell_index_in_this_direction = global_data_index[n] / pkg_size_; - pkg_index_[n] = cell_index_in_this_direction; - local_data_index[n] = global_data_index[n] - cell_index_in_this_direction * pkg_size_; - } - PackageDataType& data = data_pkg_addrs_[pkg_index_[0]][pkg_index_[1]]->*MemPtr; - return data[local_data_index[0]][local_data_index[1]]; - } - //=================================================================================================// - template - void MeshWithDataPackages::initializePackageAddressesInACell(Vecu cell_index) - { - int i = (int)cell_index[0]; - int j = (int)cell_index[1]; - - DataPackageType* data_pkg = data_pkg_addrs_[i][j]; - if (data_pkg->is_inner_pkg_) { - for (int l = 0; l != pkg_addrs_size_; ++l) - for (int m = 0; m != pkg_addrs_size_; ++m) { - std::pair x_pair = CellShiftAndDataIndex(l); - std::pair y_pair = CellShiftAndDataIndex(m); - data_pkg->assignAllPackageDataAddress(Vecu(l, m), - data_pkg_addrs_[i + x_pair.first][j + y_pair.first], - Vecu(x_pair.second, y_pair.second)); - } - } - } - //=================================================================================================// - template - void MeshWithDataPackages::allocateMeshDataMatrix() - { - Allocate2dArray(data_pkg_addrs_, BaseMeshType::number_of_cells_); - } - //=================================================================================================// - template - void MeshWithDataPackages::deleteMeshDataMatrix() - { - Delete2dArray(data_pkg_addrs_, BaseMeshType::number_of_cells_); - } - //=================================================================================================// - template - template - DataType MeshWithDataPackages::probeMesh(const Vecd& position) - { - Vecu grid_index = BaseMeshType::CellIndexFromPosition(position); - size_t i = grid_index[0]; - size_t j = grid_index[1]; - - DataPackageType* data_pkg = data_pkg_addrs_[i][j]; - PackageDataAddressType& pkg_data_addrs = data_pkg->*MemPtr; - return data_pkg->is_inner_pkg_ ? - data_pkg->DataPackageType::template probeDataPackage(pkg_data_addrs, position) - : *pkg_data_addrs[0][0]; - } - //=================================================================================================// -} -//=================================================================================================// -#endif //MESH_WITH_DATA_PACKAGES_2D_HPP \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/particle_dynamics/CMakeLists.txt b/SPHINXsys/src/for_2D_build/particle_dynamics/CMakeLists.txt deleted file mode 100644 index 9ef366a60f..0000000000 --- a/SPHINXsys/src/for_2D_build/particle_dynamics/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/particle_dynamics/solid_dynamics/CMakeLists.txt b/SPHINXsys/src/for_2D_build/particle_dynamics/solid_dynamics/CMakeLists.txt deleted file mode 100644 index 9d7cea4b86..0000000000 --- a/SPHINXsys/src/for_2D_build/particle_dynamics/solid_dynamics/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) - diff --git a/SPHINXsys/src/for_2D_build/particle_dynamics/solid_dynamics/solid_dynamics_supplementary.cpp b/SPHINXsys/src/for_2D_build/particle_dynamics/solid_dynamics/solid_dynamics_supplementary.cpp deleted file mode 100644 index bb0830cd1e..0000000000 --- a/SPHINXsys/src/for_2D_build/particle_dynamics/solid_dynamics/solid_dynamics_supplementary.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/** - * @file solid_dynamics_supplementary.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#include "all_solid_dynamics.h" -#include "solid_body.h" -#include "solid_particles.h" -#include "neighbor_relation.h" -#include "base_kernel.h" -#include "base_data_package.h" -#include "elastic_solid.h" -#include "external_force.h" -#include "mesh_cell_linked_list.h" -#include "fluid_particles.h" -#include "weakly_compressible_fluid.h" - -using namespace SimTK; - -namespace SPH -{ - namespace solid_dynamics - { - //=========================================================================================// - void UpdateElasticNormalDirection::Update(size_t index_i, Real dt) - { - Matd& F = F_[index_i]; - //deformation tensor in 2D - Real F00 = F(0, 0); - Real F01 = F(0, 1); - Real F10 = F(1, 0); - Real F11 = F(1, 1); - - //polar decomposition - Mat2d R = Mat2d(F00 + F11, F01 - F10, F10 - F01, F00 + F11); - R = R / sqrt(pow(F00 + F11, 2) + pow(F01 - F10, 2)); - - n_[index_i] = R * n_0_[index_i]; - } - //=========================================================================================// - void ConstrainSolidBodyPartBySimBody::Update(size_t index_i, Real dt) - { - Vecd& pos_0_i = pos_0_[index_i]; - Vec3 rr, pos, vel, acc; - rr(0) = pos_0_i[0] - initial_mobod_origin_location_[0]; - rr(1) = pos_0_i[1] - initial_mobod_origin_location_[1]; - rr(2) = 0.0; - mobod_.findStationLocationVelocityAndAccelerationInGround(*simbody_state_, rr, pos, vel, acc); - /** this is how we calculate the particle position in after transform of MBbody. - * const SimTK::Rotation& R_GB = mobod_.getBodyRotation(simbody_state); - * const SimTK::Vec3& p_GB = mobod_.getBodyOriginLocation(simbody_state); - * const SimTK::Vec3 r = R_GB * rr; // re-express station vector p_BS in G (15 flops) - * base_particle_data_i.pos_n_ = (p_GB + r).getSubVec<2>(0); - */ - pos_n_[index_i] = pos.getSubVec<2>(0); - vel_n_[index_i] = vel.getSubVec<2>(0); - dvel_dt_[index_i] = acc.getSubVec<2>(0); - n_[index_i] = (mobod_.getBodyRotation(*simbody_state_) - * upgradeToVector3D(n_0_[index_i])).getSubVec<2>(0); - } - //=========================================================================================// - SimTK::SpatialVec TotalForceOnSolidBodyPartForSimBody::ReduceFunction(size_t index_i, Real dt) - { - Vec3 force_from_particle(0); - force_from_particle.updSubVec<2>(0) = force_from_fluid_[index_i] + contact_force_[index_i]; - Vec3 displacement(0); - displacement.updSubVec<2>(0) = pos_n_[index_i] - - current_mobod_origin_location_.getSubVec<2>(0); - Vec3 torque_from_particle = cross(displacement, force_from_particle); - - return SimTK::SpatialVec(torque_from_particle, force_from_particle); - } - //=================================================================================================// - } -} diff --git a/SPHINXsys/src/for_2D_build/particle_generator/CMakeLists.txt b/SPHINXsys/src/for_2D_build/particle_generator/CMakeLists.txt deleted file mode 100644 index 9ef366a60f..0000000000 --- a/SPHINXsys/src/for_2D_build/particle_generator/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/particle_generator/all_particle_generators.h b/SPHINXsys/src/for_2D_build/particle_generator/all_particle_generators.h deleted file mode 100644 index 8e6229123b..0000000000 --- a/SPHINXsys/src/for_2D_build/particle_generator/all_particle_generators.h +++ /dev/null @@ -1,8 +0,0 @@ - -#ifndef ALL_PARTICLE_GENERATORS_2D_H -#define ALL_PARTICLE_GENERATORS_2D_H - - - -#include "particle_generator_lattice.h" -#endif //ALL_PARTICLE_GENERATORS_2D_H \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/particle_generator/particle_generator_lattice_supplementary.cpp b/SPHINXsys/src/for_2D_build/particle_generator/particle_generator_lattice_supplementary.cpp deleted file mode 100644 index de64fac624..0000000000 --- a/SPHINXsys/src/for_2D_build/particle_generator/particle_generator_lattice_supplementary.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @file particle_generator_lattic_supplementary.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#include "particle_generator_lattice.h" - -#include "geometry.h" -#include "base_mesh.h" -#include "base_body.h" -#include "base_particles.h" - -namespace SPH { - //=================================================================================================// - void ParticleGeneratorLattice::createBaseParticles(BaseParticles* base_particles) - { - std::unique_ptr mesh(new Mesh(domain_bounds_, lattice_spacing_, 0)); - size_t total_real_particles = 0; - Real particle_volume = lattice_spacing_ * lattice_spacing_; - Vecu number_of_lattices = mesh->NumberOfCells(); - for (size_t i = 0; i < number_of_lattices[0]; ++i) - for (size_t j = 0; j < number_of_lattices[1]; ++j) - { - Vecd particle_position = mesh->CellPositionFromIndex(Vecu(i,j)); - if (body_shape_->checkNotFar(particle_position, lattice_spacing_)) - { - if (body_shape_->checkContain(particle_position)) - { - createABaseParticle(base_particles, particle_position, particle_volume, total_real_particles); - } - } - } - base_particles->total_real_particles_ = total_real_particles; - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/for_2D_build/particles/CMakeLists.txt b/SPHINXsys/src/for_2D_build/particles/CMakeLists.txt deleted file mode 100644 index 9ef366a60f..0000000000 --- a/SPHINXsys/src/for_2D_build/particles/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/particles/solid_particles_supplementary.cpp b/SPHINXsys/src/for_2D_build/particles/solid_particles_supplementary.cpp deleted file mode 100644 index 6e1f01da34..0000000000 --- a/SPHINXsys/src/for_2D_build/particles/solid_particles_supplementary.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @file solid_particles_supplementary.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#include "solid_particles.h" -#include "base_body.h" - -namespace SPH { - //=============================================================================================// - void SolidParticles::ParticleTranslationAndRotation(Transformd& transform) - { - for (size_t i = 0; i != total_real_particles_; ++i) - { - pos_n_[i] = transform.imposeTransform(pos_n_[i]); - pos_0_[i] = transform.imposeTransform(pos_0_[i]); - } - } - //=================================================================================================// - Real ElasticSolidParticles::von_Mises_stress(size_t particle_i) - { - Real J = rho0_ / rho_n_[particle_i]; - Mat2d F = F_[particle_i]; - Mat2d stress = stress_PK1_[particle_i]; - Mat2d sigma = (stress * ~F) / J; - - Real sigmaxx = sigma(0, 0); - Real sigmayy = sigma(1, 1); - Real sigmaxy = sigma(0, 1); - - return sqrt(sigmaxx * sigmaxx + sigmayy * sigmayy - sigmaxx * sigmayy - + 3.0 * sigmaxy * sigmaxy); - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/for_3D_build/CMakeLists.txt b/SPHINXsys/src/for_3D_build/CMakeLists.txt deleted file mode 100644 index ff8c447b75..0000000000 --- a/SPHINXsys/src/for_3D_build/CMakeLists.txt +++ /dev/null @@ -1,116 +0,0 @@ -## 3D build -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Dirsearch_for_3D_build) - -## prepare dirctoriesfor head and source files -HEADER_DIRECTORIES_SHARED(headdirs_shared) -SOURCE_DIRECTORIES_SHARED(sourcedirs_shared) -HEADER_DIRECTORIES_3D(headdirs_3D) -SOURCE_DIRECTORIES_3D(sourcedirs_3D) - -SET(usefuldirs ${headdirs_3D} ${sourcedirs_3D}) -LIST(REMOVE_DUPLICATES usefuldirs) - -SET(usefulsubdirs ${usefuldirs}) -LIST(REMOVE_ITEM usefulsubdirs ${CMAKE_CURRENT_SOURCE_DIR}) - -#Add all useful subdirectories -FOREACH(subdir_path ${usefulsubdirs}) - #message(STATUS ${subdir_path}) - ADD_SUBDIRECTORY(${subdir_path}) -ENDFOREACH() - -# combin head and souce directories -SET(headdirs ${headdirs_shared} ${headdirs_3D}) -SET(sourcedirs ${sourcedirs_shared} ${sourcedirs_3D}) - -##Add all header dirs -FOREACH(headdir_path ${headdirs}) - #message(STATUS ${headdir_path}) - INCLUDE_DIRECTORIES("${headdir_path}") -ENDFOREACH() - -##Add all source files -set(SCR_FILES "") -FOREACH(srcdir_path ${sourcedirs}) - #message(STATUS ${srcdir_path}) - set(DIR_scrs "") - AUX_SOURCE_DIRECTORY(${srcdir_path} DIR_scrs) - list(APPEND SCR_FILES ${DIR_scrs}) -ENDFOREACH() - -SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) - -if(NOT SPH_ONLY_STATIC_BUILD) - ### SPHinXsys dynamic lib ### - ADD_LIBRARY(sphinxsys_3d SHARED ${SCR_FILES}) - - ADD_LIBRARY(sphinxsys_static_3d STATIC ${SCR_FILES}) - SET_TARGET_PROPERTIES(sphinxsys_static_3d PROPERTIES OUTPUT_NAME "sphinxsys_3d") - - if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") - #target_link_libraries(sphinxsys_3d) - else(${CMAKE_SYSTEM_NAME} MATCHES "Windows") - if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - target_link_libraries(sphinxsys_3d stdc++) - else(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - target_link_libraries(sphinxsys_3d stdc++ stdc++fs) - endif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - - if(DEFINED BOOST_AVAILABLE) # link Boost if available (not for Windows) - target_link_libraries(sphinxsys_3d ${Boost_LIBRARIES}) - endif() - endif(${CMAKE_SYSTEM_NAME} MATCHES "Windows") - - if(NOT BUILD_WITH_ONETBB) # link TBB if not built by the project - target_link_libraries(sphinxsys_3d ${TBB_LIBRARYS}) - else() - target_link_libraries(sphinxsys_3d TBB::tbb TBB::tbbmalloc) # TBB::tbbmalloc_proxy is not needed - endif() - if(NOT BUILD_WITH_SIMBODY) # link Simbody if not built by the project - target_link_libraries(sphinxsys_3d ${Simbody_LIBRARIES}) - endif() - ### SPHinXsys dynamic lib ### -else() - ### SPHinXsys static lib ### - ADD_LIBRARY(sphinxsys_static_3d STATIC ${SCR_FILES}) - - if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") - #target_link_libraries(sphinxsys_static_3d) - else(${CMAKE_SYSTEM_NAME} MATCHES "Windows") - if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - target_link_libraries(sphinxsys_static_3d stdc++) - else(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - target_link_libraries(sphinxsys_static_3d stdc++ stdc++fs) - endif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - - if(DEFINED BOOST_AVAILABLE) # link Boost if available (not for Windows) - target_link_libraries(sphinxsys_static_3d ${Boost_LIBRARIES}) - endif() - endif(${CMAKE_SYSTEM_NAME} MATCHES "Windows") - - if(NOT BUILD_WITH_ONETBB) # link TBB if not built by the project - target_link_libraries(sphinxsys_static_3d ${TBB_LIBRARYS}) - else() - target_link_libraries(sphinxsys_static_3d TBB::tbb TBB::tbbmalloc) # TBB::tbbmalloc_proxy is not needed - endif() - if(NOT BUILD_WITH_SIMBODY) # link Simbody if not built by the project - target_link_libraries(sphinxsys_static_3d ${Simbody_LIBRARIES}) - endif() - ### SPHinXsys static lib ### -endif() - -if(NOT SPH_ONLY_STATIC_BUILD) - INSTALL(TARGETS sphinxsys_3d sphinxsys_static_3d - RUNTIME DESTINATION 3d_code/bin - LIBRARY DESTINATION 3d_code/lib - ARCHIVE DESTINATION 3d_code/lib) -else() - INSTALL(TARGETS sphinxsys_static_3d - RUNTIME DESTINATION 3d_code/bin - LIBRARY DESTINATION 3d_code/lib - ARCHIVE DESTINATION 3d_code/lib) -endif() - -FILE(GLOB_RECURSE hpp_headers ${PROJECT_SOURCE_DIR}/src/shared/*.hpp ${PROJECT_SOURCE_DIR}/src/for_3D_build/*.hpp) -INSTALL(FILES ${hpp_headers} DESTINATION 3d_code/include) diff --git a/SPHINXsys/src/for_3D_build/bodies/CMakeLists.txt b/SPHINXsys/src/for_3D_build/bodies/CMakeLists.txt deleted file mode 100644 index fd5eae60fd..0000000000 --- a/SPHINXsys/src/for_3D_build/bodies/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/bodies/solid_body_supplementary.cpp b/SPHINXsys/src/for_3D_build/bodies/solid_body_supplementary.cpp deleted file mode 100644 index 75fe6b28b6..0000000000 --- a/SPHINXsys/src/for_3D_build/bodies/solid_body_supplementary.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/** - * @file base_body_supplementary.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#include "solid_body.h" -#include "solid_particles.h" - -namespace SPH -{ - //=================================================================================================// - void SolidBodyPartForSimbody::tagBodyPart() - { - BodyPartByParticle::tagBodyPart(); - - Real body_part_volume(0); - initial_mass_center_ = Vec3d(0); - for (size_t i = 0; i < body_part_particles_.size(); ++i) - { - size_t index_i = body_part_particles_[i]; - Vecd particle_position = solid_particles_->pos_0_[index_i]; - Real particle_volume = solid_particles_->Vol_[index_i]; - - initial_mass_center_ += particle_volume * particle_position; - body_part_volume += particle_volume; - } - - initial_mass_center_ /= body_part_volume; - - //computing unit intertia - Vec3d intertia_moments(0); - Vec3d intertia_products(0); - for (size_t i = 0; i < body_part_particles_.size(); ++i) - { - size_t index_i = body_part_particles_[i]; - Vecd particle_position = solid_particles_->pos_0_[index_i]; - Real particle_volume = solid_particles_->Vol_[index_i]; - - Vec3d displacement = (particle_position - initial_mass_center_); - intertia_moments[0] += particle_volume - * (displacement[1] * displacement[1] + displacement[2] * displacement[2]); - intertia_moments[1] += particle_volume - * (displacement[0] * displacement[0] + displacement[2] * displacement[2]); - intertia_moments[2] += particle_volume - * (displacement[0] * displacement[0] + displacement[1] * displacement[1]); - intertia_products[0] -= particle_volume * displacement[0] * displacement[1]; - intertia_products[1] -= particle_volume * displacement[0] * displacement[2]; - intertia_products[2] -= particle_volume * displacement[1] * displacement[2]; - - } - intertia_moments /= body_part_volume; - intertia_products /= body_part_volume; - - body_part_mass_properties_ - = new SimTK::MassProperties(body_part_volume * solid_body_density_, - Vec3d(0), SimTK::UnitInertia(intertia_moments, intertia_products)); - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/for_3D_build/common/CMakeLists.txt b/SPHINXsys/src/for_3D_build/common/CMakeLists.txt deleted file mode 100644 index fd5eae60fd..0000000000 --- a/SPHINXsys/src/for_3D_build/common/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/common/data_type.h b/SPHINXsys/src/for_3D_build/common/data_type.h deleted file mode 100644 index 175169e779..0000000000 --- a/SPHINXsys/src/for_3D_build/common/data_type.h +++ /dev/null @@ -1,39 +0,0 @@ - -#ifndef DATA_TYPE_3D_H -#define DATA_TYPE_3D_H - - - -#include "base_data_type.h" - -namespace SPH { - - //for 3d build - using Veci = Vec3i; - using Vecu = Vec3u; - using Vecd = Vec3d; - using Matd = Mat3d; - using SymMatd = SymMat3d; - using AngularVecd = Vec3d; - const int indexAngularVector = 1; - - using Transformd = Transform3d; - - template - using PackageDataMatrix = std::array, ARRAY_SIZE>, ARRAY_SIZE>; - - template - using MeshDataMatrix = DataType***; - - /** only works for smoothing length ratio less or equal than 1.3*/ - constexpr int MaximumNeighborhoodSize = int(1.33 * M_PI * 27); - - const int Dimensions = 3; - - /** correction matrix, only works for thin structure dynamics. */ - const Matd reduced_unit_matrix = { 1, 0, 0, 0, 1, 0, 0, 0, 0 }; - - /** initial local normal, only works for thin structure dynamics. */ - const Vecd local_pseudo_n_0 = Vecd(0.0, 0.0, 1.0); -} -#endif //DATA_TYPE_3D_H \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/common/scalar_functions_supplementary.cpp b/SPHINXsys/src/for_3D_build/common/scalar_functions_supplementary.cpp deleted file mode 100644 index c079f5efe5..0000000000 --- a/SPHINXsys/src/for_3D_build/common/scalar_functions_supplementary.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "scalar_functions.h" - -namespace SPH { - //=================================================================================================// - int SecondAxis(int axis_direction) { - return axis_direction == 2 ? 0 : axis_direction + 1; - } -} diff --git a/SPHINXsys/src/for_3D_build/geometries/CMakeLists.txt b/SPHINXsys/src/for_3D_build/geometries/CMakeLists.txt deleted file mode 100644 index fd5eae60fd..0000000000 --- a/SPHINXsys/src/for_3D_build/geometries/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/geometries/geometry.cpp b/SPHINXsys/src/for_3D_build/geometries/geometry.cpp deleted file mode 100644 index 0a57ff2bcb..0000000000 --- a/SPHINXsys/src/for_3D_build/geometries/geometry.cpp +++ /dev/null @@ -1,309 +0,0 @@ -#include "geometry.h" - -namespace SPH -{ - //=================================================================================================// - TriangleMeshShape::TriangleMeshShape(std::string filepathname, Vec3d translation, Real scale_factor) - : Shape("TriangleMeshShape") - { - if (!fs::exists(filepathname)) - { - std::cout << "\n Error: the input file:" << filepathname << " is not exists" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - SimTK::PolygonalMesh polymesh; - polymesh.loadStlFile(filepathname); - polymesh.scaleMesh(scale_factor); - triangle_mesh_ = generateTriangleMesh(polymesh.transformMesh(translation)); - } - //=================================================================================================// - TriangleMeshShape::TriangleMeshShape(Vec3d halfsize, int resolution, Vec3d translation) - : Shape("TriangleMeshShape") - { - SimTK::PolygonalMesh polymesh = SimTK::PolygonalMesh::createBrickMesh(halfsize, resolution); - triangle_mesh_ = generateTriangleMesh(polymesh.transformMesh(translation)); - } - //=================================================================================================// - TriangleMeshShape::TriangleMeshShape(Real radius, int resolution, Vec3d translation) - : Shape("TriangleMeshShape") - { - SimTK::PolygonalMesh polymesh - = SimTK::PolygonalMesh::createSphereMesh(radius, resolution); - triangle_mesh_ = generateTriangleMesh(polymesh.transformMesh(translation)); - } - //=================================================================================================// - TriangleMeshShape::TriangleMeshShape(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation) - : Shape("TriangleMeshShape") - { - SimTK::PolygonalMesh polymesh - = SimTK::PolygonalMesh::createCylinderMesh(axis, radius, halflength, resolution); - triangle_mesh_ = generateTriangleMesh(polymesh.transformMesh(translation)); - } - //=================================================================================================// - SimTK::ContactGeometry::TriangleMesh* TriangleMeshShape - ::generateTriangleMesh(SimTK::PolygonalMesh &ploy_mesh) - { - SimTK::ContactGeometry::TriangleMesh *triangle_mesh; - triangle_mesh = new SimTK::ContactGeometry::TriangleMesh(ploy_mesh); - if (!SimTK::ContactGeometry::TriangleMesh::isInstance(*triangle_mesh)) - { - std::cout << "\n Error the triangle mesh is not valid" << std::endl; - } - std::cout << "num of faces:" << triangle_mesh->getNumFaces() << std::endl; - - return triangle_mesh; - } - //=================================================================================================// - bool TriangleMeshShape::checkContain(Vec3d pnt, bool BOUNDARY_INCLUDED) - { - - SimTK::Vec2 uv_coordinate; - bool inside = false; - int face_id; - Vec3d closest_pnt = triangle_mesh_->findNearestPoint(pnt, inside, face_id, uv_coordinate); - - StdVec neighbor_face(4); - neighbor_face[0] = face_id; - /** go throught the neighbor faces. */ - for (int i = 1; i < 4; i++) { - int edge = triangle_mesh_->getFaceEdge(face_id, i - 1); - int face = triangle_mesh_->getEdgeFace(edge, 0); - neighbor_face[i] = face != face_id ? face : triangle_mesh_->getEdgeFace(edge, 1); - } - - Vec3d from_face_to_pnt = pnt - closest_pnt; - Real sum_weights = 0.0; - Real weighted_dot_product = 0.0; - for (int i = 0; i < 4; i++) { - SimTK::UnitVec3 normal_direction = triangle_mesh_->getFaceNormal(neighbor_face[i]); - Real dot_product = dot(normal_direction, from_face_to_pnt); - Real weight = dot_product * dot_product; - weighted_dot_product += weight * dot_product; - sum_weights += weight; - } - - weighted_dot_product /= sum_weights; - - bool weighted_inside = false; - if (weighted_dot_product < 0.0) weighted_inside = true; - - if (face_id < 0 && face_id > triangle_mesh_->getNumFaces()) - { - std::cout << "\n Error the nearest point is not valid" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - - return weighted_inside; - } - //=================================================================================================// - Vec3d TriangleMeshShape::findClosestPoint(Vec3d& input_pnt) - { - bool inside = false; - int face_id; - SimTK::Vec2 normal; - Vec3d closest_pnt; - closest_pnt = triangle_mesh_->findNearestPoint(input_pnt, inside,face_id, normal); - if (face_id < 0 && face_id > triangle_mesh_->getNumFaces()) - { - std::cout << "\n Error the nearest point is not valid" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - return closest_pnt; - } - //=================================================================================================// - BoundingBox TriangleMeshShape::findBounds() - { - int number_of_vertices = triangle_mesh_->getNumVertices(); - //initial reference values - Vec3d lower_bound = Vec3d(Infinity); - Vec3d upper_bound = Vec3d(-Infinity); - - for (int i = 0; i != number_of_vertices; ++i) - { - Vec3d vertex_position = triangle_mesh_->getVertexPosition(i); - for (int j = 0; j != 3; ++j) { - lower_bound[j] = SMIN(lower_bound[j], vertex_position[j]); - upper_bound[j] = SMAX(upper_bound[j], vertex_position[j]); - } - } - return BoundingBox(lower_bound, upper_bound); - } - //=================================================================================================// - bool ComplexShape::checkContain(Vec3d& input_pnt, bool BOUNDARY_INCLUDED) - { - bool exist = false; - bool inside = false; - - for (auto& each_shape : triangle_mesh_shapes_) - { - TriangleMeshShape* sp = each_shape.first; - ShapeBooleanOps operation_string = each_shape.second; - - switch (operation_string) - { - case ShapeBooleanOps::add: - { - inside = sp->checkContain(input_pnt); - exist = exist || inside; - break; - } - case ShapeBooleanOps::sub: - { - inside = sp->checkContain(input_pnt); - exist = exist && (!inside); - break; - } - default: - { - std::cout << "\n FAILURE: the boolean operation is not applicable!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - break; - } - } - } - return exist; - } - //=================================================================================================// - Vec3d ComplexShape::findClosestPoint(Vec3d& input_pnt) - { - //a big positive number - Real large_number(Infinity); - Real dist_min = large_number; - Vec3d pnt_closest(0); - Vec3d pnt_found(0); - - for (auto& each_shape : triangle_mesh_shapes_) - { - TriangleMeshShape* sp = each_shape.first; - pnt_found = sp->findClosestPoint(input_pnt); - Real dist = (input_pnt - pnt_found).norm(); - - if(dist <= dist_min) - { - dist_min = dist; - pnt_closest = pnt_found; - } - } - - return pnt_closest; - } - //=================================================================================================// - Real ComplexShape::findSignedDistance(Vec3d& input_pnt) - { - Real distance_to_surface = (input_pnt - findClosestPoint(input_pnt)).norm(); - return checkContain(input_pnt) ? -distance_to_surface : distance_to_surface; - } - //=================================================================================================// - Vec3d ComplexShape::findNormalDirection(Vec3d& input_pnt) - { - bool is_contain = checkContain(input_pnt); - Vecd displacement_to_surface = findClosestPoint(input_pnt) - input_pnt; - while (displacement_to_surface.norm() < Eps) { - Vecd jittered = input_pnt; //jittering - for (int l = 0; l != input_pnt.size(); ++l) - jittered[l] = input_pnt[l] + (((Real)rand() / (RAND_MAX)) - 0.5) * 100.0 * Eps; - if (checkContain(jittered) == is_contain) - displacement_to_surface = findClosestPoint(jittered) - jittered; - } - Vecd direction_to_surface = displacement_to_surface.normalize(); - return is_contain ? direction_to_surface : -1.0 * direction_to_surface; - } - //=================================================================================================// - void ComplexShape::addTriangleMeshShape(TriangleMeshShape* triangle_mesh_shape, ShapeBooleanOps op) - { - std::pair shape_and_op(triangle_mesh_shape, op); - triangle_mesh_shapes_.push_back(shape_and_op); - } - //=================================================================================================// - void ComplexShape::addBrick(Vec3d halfsize, int resolution, Vec3d translation, ShapeBooleanOps op) - { - TriangleMeshShape* triangle_mesh_shape = new TriangleMeshShape(halfsize, resolution, translation); - std::pair shape_and_op(triangle_mesh_shape, op); - triangle_mesh_shapes_.push_back(shape_and_op); - } - //=================================================================================================// - void ComplexShape::addSphere(Real radius, int resolution, Vec3d translation, ShapeBooleanOps op) - { - TriangleMeshShape* triangle_mesh_shape = new TriangleMeshShape(radius, resolution, translation); - std::pair shape_and_op(triangle_mesh_shape, op); - triangle_mesh_shapes_.push_back(shape_and_op); - } - //=================================================================================================// - void ComplexShape::addCylinder(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation, ShapeBooleanOps op) - { - /** Here SimTK::UnitVec3 give the direction of the cylinder, viz. SimTK::UnitVec3(0,0,1) create a cylinder in z-axis.*/ - TriangleMeshShape* triangle_mesh_shape = new TriangleMeshShape(axis, radius, halflength, resolution, translation); - std::pair shape_and_op(triangle_mesh_shape, op); - triangle_mesh_shapes_.push_back(shape_and_op); - } - //=================================================================================================// - void ComplexShape::addFormSTLFile(std::string file_path_name, Vec3d translation, Real scale_factor, ShapeBooleanOps op) - { - TriangleMeshShape* triangle_mesh_shape = new TriangleMeshShape(file_path_name, translation, scale_factor); - std::pair shape_and_op(triangle_mesh_shape, op); - triangle_mesh_shapes_.push_back(shape_and_op); - } - //=================================================================================================// - void ComplexShape::addComplexShape(ComplexShape* complex_shape, ShapeBooleanOps op) - { - switch (op) - { - case ShapeBooleanOps::add: - { - for (auto &shape_and_op : complex_shape->triangle_mesh_shapes_) - { - triangle_mesh_shapes_.push_back(shape_and_op); - } - break; - } - case ShapeBooleanOps::sub: - { - for (auto &shape_and_op : complex_shape->triangle_mesh_shapes_) - { - TriangleMeshShape *sp = shape_and_op.first; - ShapeBooleanOps operation_string = shape_and_op.second == ShapeBooleanOps::add ? ShapeBooleanOps::sub : ShapeBooleanOps::add; - std::pair substract_shape_and_op(sp, operation_string); - triangle_mesh_shapes_.push_back(substract_shape_and_op); - } - break; - } - case ShapeBooleanOps::sym_diff: - case ShapeBooleanOps::intersect: - { - break; - } - } - } - //=================================================================================================// - bool ComplexShape::checkNotFar(Vec3d& input_pnt, Real threshold) - { - return checkContain(input_pnt) || checkNearSurface(input_pnt , threshold) ? true : false; - } - //=================================================================================================// - bool ComplexShape::checkNearSurface(Vec3d& input_pnt, Real threshold) - { - return getMaxAbsoluteElement(input_pnt - findClosestPoint(input_pnt)) < threshold ? true : false; - } - //=================================================================================================// - BoundingBox ComplexShape::findBounds() - { - //initial reference values - Vec3d lower_bound = Vec3d(Infinity); - Vec3d upper_bound = Vec3d(-Infinity); - - for (size_t i = 0; i < triangle_mesh_shapes_.size(); i++) - { - BoundingBox shape_bounds = triangle_mesh_shapes_[i].first->findBounds(); - for (int j = 0; j != 3; ++j) { - lower_bound[j] = SMIN(lower_bound[j], shape_bounds.first[j]); - upper_bound[j] = SMAX(upper_bound[j], shape_bounds.second[j]); - } - } - return BoundingBox(lower_bound, upper_bound); - } - //=================================================================================================// -} \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/geometries/geometry.h b/SPHINXsys/src/for_3D_build/geometries/geometry.h deleted file mode 100644 index 94f0187f91..0000000000 --- a/SPHINXsys/src/for_3D_build/geometries/geometry.h +++ /dev/null @@ -1,113 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file geometry.h -* @brief Here, we define the 3D geometric algortihms. they are based on the polymesh. -* @details The idea is to define complex geometry by passing stl, obj or other -* polymesh files. -* @author Chi ZHang and Xiangyu Hu -*/ - -#ifndef GEOMETRY_3D_H -#define GEOMETRY_3D_H - - -#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING - -#include "base_geometry.h" -#include "simbody_middle.h" - -#include -#include -#include - -/** Macro for APPLE compilers*/ -#ifdef __APPLE__ -#include -namespace fs = boost::filesystem; -#else -#include -namespace fs = std::experimental::filesystem; -#endif - -namespace SPH { - - /** - * @brief preclaimed classes. - */ - class Kernel; - - class TriangleMeshShape : public Shape - { - public: - //constructor for load stl file from out side - TriangleMeshShape(std::string file_path_name, Vec3d translation, Real scale_factor); - // constructor for brick geometry - TriangleMeshShape(Vec3d halfsize, int resolution, Vec3d translation); - // constructor for sphere geometry - TriangleMeshShape(Real radius, int resolution, Vec3d translation); - //constructor for cylinder geometry - TriangleMeshShape(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation); - - SimTK::ContactGeometry::TriangleMesh* getTriangleMesh() { return triangle_mesh_; }; - bool checkContain(Vec3d pnt, bool BOUNDARY_INCLUDED = true); - Vec3d findClosestPoint(Vec3d& input_pnt); - virtual BoundingBox findBounds() override; - - protected: - SimTK::ContactGeometry::TriangleMesh* triangle_mesh_; - - //generate triangle mesh from polymesh - SimTK::ContactGeometry::TriangleMesh* generateTriangleMesh(SimTK::PolygonalMesh& ploy_mesh); - }; - - class ComplexShape : public Shape - { - Vec3d findClosestPoint(Vec3d& input_pnt); - public: - ComplexShape() : Shape("ComplexShape") {}; - ComplexShape(std::string complex_shape_name) : Shape(complex_shape_name) {}; - virtual ~ComplexShape() {}; - virtual BoundingBox findBounds() override; - - void addTriangleMeshShape(TriangleMeshShape* triangle_mesh_shape, ShapeBooleanOps op); - void addComplexShape(ComplexShape* complex_shape, ShapeBooleanOps op); - void addBrick(Vec3d halfsize, int resolution, Vec3d translation, ShapeBooleanOps op); - void addSphere(Real radius, int resolution, Vec3d translation, ShapeBooleanOps op); - void addCylinder(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation, ShapeBooleanOps op); - void addFormSTLFile(std::string file_path_name, Vec3d translation, Real scale_factor, ShapeBooleanOps op); - - virtual bool checkContain(Vec3d& input_pnt, bool BOUNDARY_INCLUDED = true); - virtual bool checkNotFar(Vec3d& input_pnt, Real threshold); - virtual bool checkNearSurface(Vec3d& input_pnt, Real threshold); - /** Signed distance is negative for point within the complex shape. */ - virtual Real findSignedDistance(Vec3d& input_pnt); - /** Normal direction point toward outside of the complex shape. */ - virtual Vec3d findNormalDirection(Vec3d& input_pnt); - protected: - /** shape container */ - std::vector> triangle_mesh_shapes_; - }; -} - -#endif //GEOMETRY_3D_H \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/geometries/level_set_supplementary.cpp b/SPHINXsys/src/for_3D_build/geometries/level_set_supplementary.cpp deleted file mode 100644 index 0e4d7c8cd6..0000000000 --- a/SPHINXsys/src/for_3D_build/geometries/level_set_supplementary.cpp +++ /dev/null @@ -1,476 +0,0 @@ -/** - * @file level_set_supplementary.cpp - * @author Luhui Han, Chi ZHang Yongchuan YU and Xiangyu Hu - */ - -#include "level_set.h" - -#include "base_kernel.h" -#include "base_particles.h" -#include "base_body.h" -#include "particle_adaptation.h" - -namespace SPH { - //=================================================================================================// - void LevelSetDataPackage::initializeWithUniformData(Real level_set) - { - for (int i = 0; i != PackageSize(); ++i) - for (int j = 0; j != PackageSize(); ++j) - for (int k = 0; k != PackageSize(); ++k) - { - phi_[i][j][k] = level_set; - n_[i][j][k] = Vecd(1.0); - kernel_weight_[i][j][k] = level_set < 0.0 ? 0 : 1.0; - kernel_gradient_[i][j][k] = Vecd(0.0); - near_interface_id_[i][j][k] = level_set < 0.0 ? -2 : 2; - } - } - //=================================================================================================// - void LevelSetDataPackage::initializeBasicData(ComplexShape& complex_shape) - { - for (int i = 0; i != PackageSize(); ++i) - for (int j = 0; j != PackageSize(); ++j) - for (int k = 0; k != PackageSize(); ++k) - { - Vec3d position = data_lower_bound_ - + Vec3d((Real)i * grid_spacing_, (Real)j * grid_spacing_, (Real)k * grid_spacing_); - phi_[i][j][k] = complex_shape.findSignedDistance(position); - near_interface_id_[i][j][k] = phi_[i][j][k] < 0.0 ? -2 : 2; - } - } - //=================================================================================================// - void LevelSetDataPackage::computeKernelIntegrals(LevelSet& level_set) - { - for (int i = 0; i != PackageSize(); ++i) - for (int j = 0; j != PackageSize(); ++j) - for (int k = 0; k != PackageSize(); ++k) - { - Vec3d position = data_lower_bound_ - + Vec3d((Real)i * grid_spacing_, (Real)j * grid_spacing_, (Real)k * grid_spacing_); - kernel_weight_[i][j][k] = level_set.computeKernelIntegral(position); - kernel_gradient_[i][j][k] = level_set.computeKernelGradientIntegral(position); - } - } - //=================================================================================================// - void LevelSetDataPackage::stepReinitialization() - { - for (int i = AddressBufferWidth(); i != OperationUpperBound(); ++i) - for (int j = AddressBufferWidth(); j != OperationUpperBound(); ++j) - for (int k = AddressBufferWidth(); k != OperationUpperBound(); ++k) - { - //only reinitialize non cut cells - if (*near_interface_id_addrs_[i][j][k] != 0) - { - Real phi_0 = *phi_addrs_[i][j][k]; - Real s = phi_0 / sqrt(phi_0 * phi_0 + grid_spacing_ * grid_spacing_); - //x direction - Real dv_xp = (*phi_addrs_[i + 1][j][k] - phi_0); - Real dv_xn = (phi_0 - *phi_addrs_[i - 1][j][k]); - Real dv_x = dv_xp; - if (s * dv_xp >= 0.0 && s * dv_xn >= 0.0) dv_x = dv_xn; - if (s * dv_xp <= 0.0 && s * dv_xn <= 0.0) dv_x = dv_xp; - if (s * dv_xp > 0.0 && s * dv_xn < 0.0) dv_x = 0.0; - if (s * dv_xp < 0.0 && s * dv_xn > 0.0) - { - Real ss = s * (fabs(dv_xp) - fabs(dv_xn)) / (dv_xp - dv_xn); - if (ss > 0.0) dv_x = dv_xn; - } - //y direction - Real dv_yp = (*phi_addrs_[i][j + 1][k] - phi_0); - Real dv_yn = (phi_0 - *phi_addrs_[i][j - 1][k]); - Real dv_y = dv_yp; - if (s * dv_yp >= 0.0 && s * dv_yn >= 0.0) dv_y = dv_yn; - if (s * dv_yp <= 0.0 && s * dv_yn <= 0.0) dv_y = dv_yp; - if (s * dv_yp > 0.0 && s * dv_yn < 0.0) dv_y = 0.0; - if (s * dv_yp < 0.0 && s * dv_yn > 0.0) - { - Real ss = s * (fabs(dv_yp) - fabs(dv_yn)) / (dv_yp - dv_yn); - if (ss > 0.0) dv_y = dv_yn; - } - //z direction - Real dv_zp = (*phi_addrs_[i][j][k + 1] - phi_0); - Real dv_zn = (phi_0 - *phi_addrs_[i][j][k - 1]); - Real dv_z = dv_zp; - if (s * dv_zp >= 0.0 && s * dv_zn >= 0.0) dv_z = dv_zn; - if (s * dv_zp <= 0.0 && s * dv_zn <= 0.0) dv_z = dv_zp; - if (s * dv_zp > 0.0 && s * dv_zn < 0.0) dv_z = 0.0; - if (s * dv_zp < 0.0 && s * dv_zn > 0.0) - { - Real ss = s * (fabs(dv_zp) - fabs(dv_zn)) / (dv_zp - dv_zn); - if (ss > 0.0) dv_z = dv_zn; - } - //time stepping - *phi_addrs_[i][j][k] -= - 0.3 * s * (sqrt(dv_x * dv_x + dv_y * dv_y + dv_z * dv_z) - grid_spacing_); - } - } - } - //=================================================================================================// - void LevelSetDataPackage::markNearInterface() - { - Real small_shift = 0.75 * grid_spacing_; - //corner averages, note that the first row and first column are not used - PackageTemporaryData corner_averages; - for (int i = 1; i != AddressSize(); ++i) - for (int j = 1; j != AddressSize(); ++j) - for (int k = 1; k != AddressSize(); ++k) - { - corner_averages[i][j][k] = CornerAverage(phi_addrs_, Veci(i, j, k), Veci(-1, -1, -1)); - } - - for (int i = AddressBufferWidth(); i != OperationUpperBound(); ++i) - for (int j = AddressBufferWidth(); j != OperationUpperBound(); ++j) - for (int k = AddressBufferWidth(); k != OperationUpperBound(); ++k) - { - //first assume far cells - Real phi_0 = *phi_addrs_[i][j][k]; - int near_interface_id = phi_0 > 0.0 ? 2 : -2; - - Real phi_average_0 = corner_averages[i][j][k]; - //find inner and outer cut cells - for (int l = 0; l != 2; ++l) - for (int m = 0; m != 2; ++m) - for (int n = 0; n != 2; ++n) - { - int index_x = i + l; - int index_y = j + m; - int index_z = k + n; - Real phi_average = corner_averages[index_x][index_y][index_z]; - if ((phi_average_0 - small_shift) * (phi_average - small_shift) < 0.0) near_interface_id = 1; - if ((phi_average_0 + small_shift) * (phi_average + small_shift) < 0.0) near_interface_id = -1; - } - //find zero cut cells - for (int l = 0; l != 2; ++l) - for (int m = 0; m != 2; ++m) - for (int n = 0; n != 2; ++n) - { - int index_x = i + l; - int index_y = j + m; - int index_z = k + n; - Real phi_average = corner_averages[index_x][index_y][index_z]; - if (phi_average_0 * phi_average < 0.0) near_interface_id = 0; - } - //find cells between cut cells - if (fabs(phi_0) < small_shift && abs(near_interface_id) != 1) near_interface_id = 0; - - //assign this is to package - *near_interface_id_addrs_[i][j][k] = near_interface_id; - } - } - //=================================================================================================// - bool LevelSet::isWithinCorePackage(Vecd position) - { - Vecu cell_index = CellIndexFromPosition(position); - return data_pkg_addrs_[cell_index[0]][cell_index[1]][cell_index[2]]->is_core_pkg_; - } - //=================================================================================================// - void LevelSet::initializeDataInACell(Vecu cell_index, Real dt) - { - int i = (int)cell_index[0]; - int j = (int)cell_index[1]; - int k = (int)cell_index[2]; - - Vecd cell_position = CellPositionFromIndex(cell_index); - Real signed_distance = complex_shape_.findSignedDistance(cell_position); - Vecd normal_direction = complex_shape_.findNormalDirection(cell_position); - Real measure = getMaxAbsoluteElement(normal_direction * signed_distance); - if (measure < grid_spacing_) { - mutex_my_pool.lock(); - LevelSetDataPackage* new_data_pkg = data_pkg_pool_.malloc(); - mutex_my_pool.unlock(); - Vecd pkg_lower_bound = GridPositionFromCellPosition(cell_position); - new_data_pkg->initializePackageGeometry(pkg_lower_bound, data_spacing_); - new_data_pkg->initializeBasicData(complex_shape_); - core_data_pkgs_.push_back(new_data_pkg); - new_data_pkg->pkg_index_ = Vecu(i, j, k); - new_data_pkg->is_core_pkg_ = true; - data_pkg_addrs_[i][j][k] = new_data_pkg; - } - else { - data_pkg_addrs_[i][j][k] = complex_shape_.checkContain(cell_position) ? - singular_data_pkgs_addrs[0] : singular_data_pkgs_addrs[1]; - } - } - //=================================================================================================// - void LevelSet::tagACellIsInnerPackage(Vecu cell_index, Real dt) - { - int i = (int)cell_index[0]; - int j = (int)cell_index[1]; - int k = (int)cell_index[2]; - - bool is_inner_pkg = false; - - for (int l = SMAX(i - 1, 0); l <= SMIN(i + 1, int(number_of_cells_[0]) - 1); ++l) - for (int m = SMAX(j - 1, 0); m <= SMIN(j + 1, int(number_of_cells_[1]) - 1); ++m) - for (int n = SMAX(k - 1, 0); n <= SMIN(k + 1, int(number_of_cells_[2]) - 1); ++n) - if (data_pkg_addrs_[l][m][n]->is_core_pkg_) is_inner_pkg = true; - - if (is_inner_pkg) - { - LevelSetDataPackage* current_data_pkg = data_pkg_addrs_[i][j][k]; - if (current_data_pkg->is_core_pkg_) - { - current_data_pkg->is_inner_pkg_ = true; - inner_data_pkgs_.push_back(current_data_pkg); - } - else { - mutex_my_pool.lock(); - LevelSetDataPackage* new_data_pkg = data_pkg_pool_.malloc(); - mutex_my_pool.unlock(); - Vecd cell_position = CellPositionFromIndex(cell_index); - Vecd pkg_lower_bound = GridPositionFromCellPosition(cell_position); - new_data_pkg->initializePackageGeometry(pkg_lower_bound, data_spacing_); - new_data_pkg->initializeBasicData(complex_shape_); - new_data_pkg->is_inner_pkg_ = true; - new_data_pkg->pkg_index_ = Vecu(i, j, k); - inner_data_pkgs_.push_back(new_data_pkg); - data_pkg_addrs_[i][j][k] = new_data_pkg; - } - } - } - //=================================================================================================// - void LevelSet::redistanceInterfaceForAPackage(LevelSetDataPackage* core_data_pkg, Real dt) - { - int l = (int)core_data_pkg->pkg_index_[0]; - int m = (int)core_data_pkg->pkg_index_[1]; - int n = (int)core_data_pkg->pkg_index_[2]; - - for (int i = pkg_addrs_buffer_; i != pkg_operations_; ++i) - for (int j = pkg_addrs_buffer_; j != pkg_operations_; ++j) - for (int k = pkg_addrs_buffer_; k != pkg_operations_; ++k) - { - int near_interface_id = *core_data_pkg->near_interface_id_addrs_[i][j][k]; - if (near_interface_id == 0) - { - bool positive_band = false; - bool negative_band = false; - for (int r = -1; r < 2; ++r) - for (int s = -1; s < 2; ++s) - for (int t = -1; t < 2; ++t) - { - int neighbor_near_interface_id = - *core_data_pkg->near_interface_id_addrs_[i + r][j + s][k + t]; - if (neighbor_near_interface_id >= 1) positive_band = true; - if (neighbor_near_interface_id <= -1) negative_band = true; - } - if (positive_band == false) - { - Real min_distance_p = 5.0 * data_spacing_; - for (int x = -4; x != 5; ++x) - for (int y = -4; y != 5; ++y) - for (int z = -4; z != 5; ++z) - { - std::pair x_pair = CellShiftAndDataIndex(i + x); - std::pair y_pair = CellShiftAndDataIndex(j + y); - std::pair z_pair = CellShiftAndDataIndex(k + z); - LevelSetDataPackage* neighbor_pkg - = data_pkg_addrs_[l + x_pair.first][m + y_pair.first][n + z_pair.first]; - int neighbor_near_interface_id - = neighbor_pkg->near_interface_id_[x_pair.second][y_pair.second][z_pair.second]; - if (neighbor_near_interface_id >= 1) - { - Real phi_p_ = neighbor_pkg->phi_[x_pair.second][y_pair.second][z_pair.second]; - Vecd norm_to_face = neighbor_pkg->n_[x_pair.second][y_pair.second][z_pair.second]; - min_distance_p = SMIN(min_distance_p, (Vecd((Real)x, (Real)y, Real(z)) * data_spacing_ + phi_p_ * norm_to_face).norm()); - } - } - *core_data_pkg->phi_addrs_[i][j][k] = -min_distance_p; - // this immediate switch of near interface id - // does not intervenning with the identification of unresolved interface - // based on the assumption that positive false_and negative bands are not close to each other - *core_data_pkg->near_interface_id_addrs_[i][j][k] = -1; - } - if (negative_band == false) - { - Real min_distance_n = 5.0 * data_spacing_; - for (int x = -4; x != 5; ++x) - for (int y = -4; y != 5; ++y) - for (int z = -4; z != 5; ++z) - { - std::pair x_pair = CellShiftAndDataIndex(i + x); - std::pair y_pair = CellShiftAndDataIndex(j + y); - std::pair z_pair = CellShiftAndDataIndex(k + z); - LevelSetDataPackage* neighbor_pkg - = data_pkg_addrs_[l + x_pair.first][m + y_pair.first][n + z_pair.first]; - int neighbor_near_interface_id - = neighbor_pkg->near_interface_id_[x_pair.second][y_pair.second][z_pair.second]; - if (neighbor_near_interface_id <= -1) - { - Real phi_n_ = neighbor_pkg->phi_[x_pair.second][y_pair.second][z_pair.second]; - Vecd norm_to_face = neighbor_pkg->n_[x_pair.second][y_pair.second][z_pair.second]; - min_distance_n = SMIN(min_distance_n, (Vecd((Real)x, (Real)y, Real(z)) * data_spacing_ - phi_n_ * norm_to_face).norm()); - } - } - *core_data_pkg->phi_addrs_[i][j][k] = min_distance_n; - // this immediate switch of near interface id - // does not intervenning with the identification of unresolved interface - // based on the assumption that positive false_and negative bands are not close to each other - *core_data_pkg->near_interface_id_addrs_[i][j][k] = 1; - } - } - } - } - //=================================================================================================// - void LevelSet::writeMeshToPltFile(std::ofstream& output_file) - { - Vecu number_of_operation = total_data_points_; - - output_file << "\n"; - output_file << "title='View'" << "\n"; - output_file << "variables= " << "x, " << "y, " << "z, " << "phi, " << "n_x, "<< "n_y, " << "n_z " << "\n"; - output_file << "zone i=" << number_of_operation[0] << " j=" << number_of_operation[1] << " k=" << number_of_operation[2] - << " DATAPACKING=BLOCK SOLUTIONTIME=" << 0 << "\n"; - - for (size_t k = 0; k != number_of_operation[2]; ++k) - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - Vecd data_position = DataPositionFromGlobalIndex(Vecu(i, j, k)); - output_file << data_position[0] << " "; - } - output_file << " \n"; - } - - for (size_t k = 0; k != number_of_operation[2]; ++k) - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - Vecd data_position = DataPositionFromGlobalIndex(Vecu(i, j, k)); - output_file << data_position[1] << " "; - } - output_file << " \n"; - } - - for (size_t k = 0; k != number_of_operation[2]; ++k) - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - Vecd data_position = DataPositionFromGlobalIndex(Vecu(i, j, k)); - output_file << data_position[2] << " "; - } - output_file << " \n"; - } - - for (size_t k = 0; k != number_of_operation[2]; ++k) - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - output_file << DataValueFromGlobalIndex, - &LevelSetDataPackage::phi_>(Vecu(i, j, k)) << " "; - - } - output_file << " \n"; - } - - for (size_t k = 0; k != number_of_operation[2]; ++k) - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - output_file << DataValueFromGlobalIndex, - &LevelSetDataPackage::n_>(Vecu(i, j, k))[0] << " "; - - } - output_file << " \n"; - } - - for (size_t k = 0; k != number_of_operation[2]; ++k) - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - output_file << DataValueFromGlobalIndex, - &LevelSetDataPackage::n_>(Vecu(i, j, k))[1] << " "; - - } - output_file << " \n"; - } - - for (size_t k = 0; k != number_of_operation[2]; ++k) - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - output_file << DataValueFromGlobalIndex, - &LevelSetDataPackage::n_>(Vecu(i, j, k))[2] << " "; - - } - output_file << " \n"; - } - - for (size_t k = 0; k != number_of_operation[2]; ++k) - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - output_file << DataValueFromGlobalIndex, - &LevelSetDataPackage::near_interface_id_>(Vecu(i, j, k)) << " "; - - } - output_file << " \n"; - } - } - //=============================================================================================// - Real LevelSet::computeKernelIntegral(const Vecd& position) - { - Real phi = probeSignedDistance(position); - Real cutoff_radius = kernel_.CutOffRadius(global_h_ratio_); - Real threshold = cutoff_radius + data_spacing_; - - Real integral(0.0); - if (fabs(phi) < threshold) - { - Vecu global_index_ = DataGlobalIndexFromPosition(position); - for (int i = -3; i != 4; ++i) - for (int j = -3; j != 4; ++j) - for (int k = -3; k != 4; ++k) - { - Vecu neighbor_index = Vecu(global_index_[0] + i, global_index_[1] + j, global_index_[2] + k); - Real phi_neighbor = DataValueFromGlobalIndex, - &LevelSetDataPackage::phi_>(neighbor_index); - if (phi_neighbor > -data_spacing_) { - Vecd displacement = position - DataPositionFromGlobalIndex(neighbor_index); - Real distance = displacement.norm(); - if (distance < cutoff_radius) - integral += kernel_.W(global_h_ratio_, distance, displacement) - * computeHeaviside(phi_neighbor, data_spacing_); - } - } - } - return phi > threshold ? 1.0 : integral * data_spacing_ * data_spacing_ * data_spacing_; - } - //=============================================================================================// - Vecd LevelSet::computeKernelGradientIntegral(const Vecd& position) - { - Real phi = probeSignedDistance(position); - Real cutoff_radius = kernel_.CutOffRadius(global_h_ratio_); - Real threshold = cutoff_radius + data_spacing_; - - Vecd integral(0.0); - if (fabs(phi) < threshold) - { - Vecu global_index_ = DataGlobalIndexFromPosition(position); - for (int i = -3; i != 4; ++i) - for (int j = -3; j != 4; ++j) - for (int k = -3; k != 4; ++k) - { - Vecu neighbor_index = Vecu(global_index_[0] + i, global_index_[1] + j, global_index_[2] + k); - Real phi_neighbor = DataValueFromGlobalIndex, - &LevelSetDataPackage::phi_>(neighbor_index); - if (phi_neighbor > -data_spacing_) { - Vecd displacement = position - DataPositionFromGlobalIndex(neighbor_index); - Real distance = displacement.norm(); - if (distance < cutoff_radius) - integral += kernel_.dW(global_h_ratio_, distance, displacement) - * computeHeaviside(phi_neighbor, data_spacing_) * displacement / (distance + TinyReal); - } - } - } - return integral * data_spacing_ * data_spacing_ * data_spacing_; - } - //=============================================================================================// -} diff --git a/SPHINXsys/src/for_3D_build/high_level_simulation_class/CMakeLists.txt b/SPHINXsys/src/for_3D_build/high_level_simulation_class/CMakeLists.txt deleted file mode 100644 index fd5eae60fd..0000000000 --- a/SPHINXsys/src/for_3D_build/high_level_simulation_class/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp b/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp deleted file mode 100644 index c1062e9387..0000000000 --- a/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp +++ /dev/null @@ -1,658 +0,0 @@ -#include "solid_structural_simulation_class.h" - -//////////////////////////////////////////////////// -/* global functions in StructuralSimulation */ -//////////////////////////////////////////////////// - -BodyPartByParticleTriMesh::BodyPartByParticleTriMesh(SPHBody* body, string body_part_name, TriangleMeshShape* triangle_mesh_shape) -: BodyPartByParticle(body, body_part_name) -{ - body_part_shape_ = new ComplexShape(body_part_name); - body_part_shape_->addTriangleMeshShape(triangle_mesh_shape, ShapeBooleanOps::add); - tagBodyPart(); -} - -BodyPartByParticleTriMesh::~BodyPartByParticleTriMesh() -{ - delete body_part_shape_; -} - -ImportedModel::ImportedModel(SPHSystem &system, string body_name, TriangleMeshShape* triangle_mesh_shape, ParticleAdaptation* particle_adaptation) - : SolidBody(system, body_name, particle_adaptation) -{ - ComplexShape original_body_shape; - original_body_shape.addTriangleMeshShape(triangle_mesh_shape, ShapeBooleanOps::add); - body_shape_ = new LevelSetComplexShape(this, original_body_shape, true); -} - -ImportedModel::~ImportedModel() -{ - delete body_shape_; -} - -SolidBodyForSimulation::SolidBodyForSimulation(SPHSystem &system, string body_name, TriangleMeshShape& triangle_mesh_shape, ParticleAdaptation& particle_adaptation, Real physical_viscosity, LinearElasticSolid& material_model): - imported_model_(ImportedModel(system, body_name, &triangle_mesh_shape, &particle_adaptation)), - //material_model_(material_model), - elastic_solid_particles_(ElasticSolidParticles(&imported_model_, &material_model)), - inner_body_relation_(BodyRelationInner(&imported_model_)), - - correct_configuration_(solid_dynamics::CorrectConfiguration(&inner_body_relation_)), - stress_relaxation_first_half_(solid_dynamics::StressRelaxationFirstHalf(&inner_body_relation_)), - stress_relaxation_second_half_(solid_dynamics::StressRelaxationSecondHalf(&inner_body_relation_)), - damping_random_(DampingWithRandomChoice>(&inner_body_relation_, 0.1, "Velocity", physical_viscosity)) -{} - -void ExpandBoundingBox(BoundingBox* original, BoundingBox* additional) -{ - for(int i = 0; i < original->first.size(); i++) - { - if ( additional->first[i] < original->first[i] ) - { - original->first[i] = additional->first[i]; - } - if ( additional->second[i] > original->second[i] ) - { - original->second[i] = additional->second[i]; - } - } -} - -void RelaxParticlesSingleResolution(In_Output* in_output, - bool write_particles_to_file, - ImportedModel* imported_model, - ElasticSolidParticles* imported_model_particles, - BodyRelationInner* imported_model_inner) -{ - - BodyStatesRecordingToVtu write_imported_model_to_vtu(*in_output, { imported_model }); - MeshRecordingToPlt mesh_cell_linked_list_recording(*in_output, imported_model, imported_model->mesh_cell_linked_list_); - - //---------------------------------------------------------------------- - // Methods used for particle relaxation. - //---------------------------------------------------------------------- - RandomizePartilePosition random_imported_model_particles(imported_model); - /** A Physics relaxation step. */ - relax_dynamics::SolidRelaxationStepInner relaxation_step_inner(imported_model_inner, true); - //---------------------------------------------------------------------- - // Particle relaxation starts here. - //---------------------------------------------------------------------- - random_imported_model_particles.parallel_exec(0.25); - relaxation_step_inner.surface_bounding_.parallel_exec(); - if (write_particles_to_file) - { - write_imported_model_to_vtu.writeToFile(0.0); - } - imported_model->updateCellLinkedList(); - if (write_particles_to_file) - { - mesh_cell_linked_list_recording.writeToFile(0.0); - } - //---------------------------------------------------------------------- - // Particle relaxation time stepping start here. - //---------------------------------------------------------------------- - int ite_p = 0; - while (ite_p < 500) - { - relaxation_step_inner.parallel_exec(); - ite_p += 1; - if (ite_p % 100 == 0) - { - cout << fixed << setprecision(9) << "Relaxation steps for the imported model N = " << ite_p << "\n"; - if (write_particles_to_file) - { - write_imported_model_to_vtu.writeToFile(Real(ite_p) * 1.0e-4); - } - } - } - cout << "The physics relaxation process of imported model finish !" << endl; -} - -StructuralSimulationInput::StructuralSimulationInput( - string relative_input_path, - vector imported_stl_list, - Real scale_stl, - vector translation_list, - vector resolution_list, - vector material_model_list, - Real physical_viscosity, - vector> contacting_bodies_list - ): - relative_input_path_(relative_input_path), - imported_stl_list_(imported_stl_list), - scale_stl_(scale_stl), - translation_list_(translation_list), - resolution_list_(resolution_list), - material_model_list_(material_model_list), - physical_viscosity_(physical_viscosity), - contacting_bodies_list_(contacting_bodies_list) -{ - // particle_relaxation option - particle_relaxation_list_ = {}; - for (unsigned i = 0; i < resolution_list_.size(); i++){ particle_relaxation_list_.push_back(true); } - // scale system boundaries - scale_system_boundaries_ = 1; - // boundary conditions - non_zero_gravity_ = {}; - acceleration_bounding_box_tuple_ = {}; - spring_damper_tuple_ = {}; - body_indeces_fixed_constraint_ = {}; - position_solid_body_tuple_ = {}; - position_scale_solid_body_tuple_ = {}; - translation_solid_body_tuple_ = {}; -}; - -/////////////////////////////////////// -/* StructuralSimulation members */ -/////////////////////////////////////// - -StructuralSimulation::StructuralSimulation(StructuralSimulationInput& input): - // generic input - relative_input_path_(input.relative_input_path_), - imported_stl_list_(input.imported_stl_list_), - scale_stl_(input.scale_stl_), - translation_list_(input.translation_list_), - resolution_list_(input.resolution_list_), - material_model_list_(input.material_model_list_), - physical_viscosity_(input.physical_viscosity_), - contacting_bodies_list_(input.contacting_bodies_list_), - - // default system, optional: particle relaxation, scale_system_boundaries - particle_relaxation_list_(input.particle_relaxation_list_), - system_resolution_(0.0), - system_(SPHSystem(BoundingBox(Vec3d(0), Vec3d(0)), system_resolution_)), - scale_system_boundaries_(input.scale_system_boundaries_), - in_output_(In_Output(system_)), - - // optional: boundary conditions - non_zero_gravity_(input.non_zero_gravity_), - acceleration_bounding_box_tuple_(input.acceleration_bounding_box_tuple_), - spring_damper_tuple_(input.spring_damper_tuple_), - body_indeces_fixed_constraint_(input.body_indeces_fixed_constraint_), - position_solid_body_tuple_(input.position_solid_body_tuple_), - position_scale_solid_body_tuple_(input.position_scale_solid_body_tuple_), - translation_solid_body_tuple_(input.translation_solid_body_tuple_) -{ - // scaling of translation and resolution - ScaleTranslationAndResolution(); - // set the default resolution to the max in the resolution list - SetSystemResolutionMax(); - // create the body mesh list for triangular mesh shapes storage - CreateBodyMeshList(); - // create the particle adaptions for the bodies - CreateParticleAdaptationList(); - // set up the system - CalculateSystemBoundaries(); - system_.run_particle_relaxation_ = true; - // initialize solid bodies with their properties - InitializeElasticSolidBodies(); - // contacts - InitializeAllContacts(); - - // boundary conditions - InitializeGravity(); - InitializeAccelerationForBodyPartInBoundingBox(); - InitializeSpringDamperConstraintParticleWise(); - InitializeConstrainSolidBodyRegion(); - InitializePositionSolidBody(); - InitializePositionScaleSolidBody(); - InitializeTranslateSolidBody(); - - // initialize simulation - InitSimulation(); -} - -StructuralSimulation::~StructuralSimulation() -{} - -void StructuralSimulation::ScaleTranslationAndResolution() -{ - // scale the translation_list_, system_resolution_ and resolution_list_ - for (unsigned int i = 0; i < translation_list_.size(); i++) - { - translation_list_[i] *= scale_stl_; - } - system_resolution_ *= scale_stl_; - for (unsigned int i = 0; i < resolution_list_.size(); i++) - { - resolution_list_[i] *= scale_stl_; - } -} - -void StructuralSimulation::SetSystemResolutionMax() -{ - system_resolution_ = 0.0; - for (unsigned int i = 0; i < resolution_list_.size(); i++) - { - if (system_resolution_ < resolution_list_[i]) - { - system_resolution_ = resolution_list_[i]; - } - } - system_.resolution_ref_ = system_resolution_; -} - -void StructuralSimulation::CalculateSystemBoundaries() -{ - // calculate system bounds from all bodies - for (unsigned int i = 0; i < body_mesh_list_.size(); i++) - { - BoundingBox additional = body_mesh_list_[i].findBounds(); - ExpandBoundingBox(&system_.system_domain_bounds_, &additional); - } - // scale the system bounds around the center point - Vecd center_point = (system_.system_domain_bounds_.first + system_.system_domain_bounds_.second) * 0.5; - - Vecd distance_first = system_.system_domain_bounds_.first - center_point; - Vecd distance_second = system_.system_domain_bounds_.second - center_point; - - system_.system_domain_bounds_.first = center_point + distance_first * scale_system_boundaries_; - system_.system_domain_bounds_.second = center_point + distance_second * scale_system_boundaries_; -} - -void StructuralSimulation::CreateBodyMeshList() -{ - body_mesh_list_ = {}; - for (unsigned int i = 0; i < imported_stl_list_.size(); i++) - { - string relative_input_path_copy = relative_input_path_; - body_mesh_list_.push_back(TriangleMeshShape(relative_input_path_copy.append(imported_stl_list_[i]), translation_list_[i], scale_stl_)); - } -} - -void StructuralSimulation::CreateParticleAdaptationList() -{ - particle_adaptation_list_ = {}; - for (unsigned int i = 0; i < resolution_list_.size(); i++) - { - particle_adaptation_list_.push_back(ParticleAdaptation()); - - Real system_resolution_ratio = resolution_list_[i] / system_resolution_; - particle_adaptation_list_[i].SetSystemResolutionRatio(system_resolution_ratio); - } -} - -void StructuralSimulation::InitializeElasticSolidBodies() -{ - solid_body_list_ = {}; - for (unsigned int i = 0; i < body_mesh_list_.size(); i++) - { - solid_body_list_.emplace_back(make_shared(system_, imported_stl_list_[i], body_mesh_list_[i], particle_adaptation_list_[i], physical_viscosity_, material_model_list_[i])); - if (particle_relaxation_list_[i]) - { - RelaxParticlesSingleResolution(&in_output_, false, solid_body_list_[i]->GetImportedModel(), solid_body_list_[i]->GetElasticSolidParticles(), solid_body_list_[i]->GetInnerBodyRelation()); - } - } -} - -void StructuralSimulation::InitializeContactBetweenTwoBodies(int first, int second) -{ - ImportedModel* first_body = solid_body_list_[first]->GetImportedModel(); - ImportedModel* second_body = solid_body_list_[second]->GetImportedModel(); - - SolidBodyRelationContact* first_contact = new SolidBodyRelationContact(first_body, {second_body}); - SolidBodyRelationContact* second_contact = new SolidBodyRelationContact(second_body, {first_body}); - - contact_list_.emplace_back(first_contact); - contact_list_.emplace_back(second_contact); - - contact_density_list_.emplace_back(make_shared(first_contact)); - contact_density_list_.emplace_back(make_shared(second_contact)); - - contact_force_list_.emplace_back(make_shared(first_contact)); - contact_force_list_.emplace_back(make_shared(second_contact)); -} - -void StructuralSimulation::InitializeAllContacts() -{ - contact_list_ = {}; - contact_density_list_ = {}; - contact_force_list_ = {}; - for (unsigned int i = 0; i < contacting_bodies_list_.size(); i++) - { - InitializeContactBetweenTwoBodies(contacting_bodies_list_[i][0], contacting_bodies_list_[i][1]); - } -} - -void StructuralSimulation::InitializeGravity() -{ - // collect all the body indeces with non-zero gravity - vector body_indeces_gravity = {}; - for (unsigned int i = 0; i < non_zero_gravity_.size(); i++) - { - body_indeces_gravity.push_back(non_zero_gravity_[i].first); - } - // initialize gravity - initialize_gravity_ = {}; - for (unsigned int i = 0; i < solid_body_list_.size(); i++) - { - if ( find(body_indeces_gravity.begin(), body_indeces_gravity.end(), i) != body_indeces_gravity.end() ) - { - initialize_gravity_.emplace_back(make_shared(solid_body_list_[i]->GetImportedModel(), new Gravity(non_zero_gravity_[i].second))); - } - else - { - initialize_gravity_.emplace_back(make_shared(solid_body_list_[i]->GetImportedModel())); - } - } -} - -void StructuralSimulation::InitializeAccelerationForBodyPartInBoundingBox() -{ - acceleration_bounding_box_ = {}; - for (unsigned int i = 0; i < acceleration_bounding_box_tuple_.size(); i++) - { - SolidBody* solid_body = solid_body_list_[get<0>(acceleration_bounding_box_tuple_[i])]->GetImportedModel(); - acceleration_bounding_box_.emplace_back(make_shared - (solid_body, &get<1>(acceleration_bounding_box_tuple_[i]), get<2>(acceleration_bounding_box_tuple_[i]))); - } -} - -void StructuralSimulation::InitializeSpringDamperConstraintParticleWise() -{ - spring_damper_constraint_ = {}; - for (unsigned int i = 0; i < spring_damper_tuple_.size(); i++) - { - SolidBody* solid_body = solid_body_list_[get<0>(spring_damper_tuple_[i])]->GetImportedModel(); - spring_damper_constraint_.emplace_back(make_shared(solid_body, get<1>(spring_damper_tuple_[i]), get<2>(spring_damper_tuple_[i]))); - } -} - -void StructuralSimulation::InitializeConstrainSolidBodyRegion() -{ - fixed_constraint_ = {}; - for (unsigned int i = 0; i < body_indeces_fixed_constraint_.size(); i++) - { - int body_index = body_indeces_fixed_constraint_[i]; - BodyPartByParticleTriMesh* bp = new BodyPartByParticleTriMesh(solid_body_list_[body_index]->GetImportedModel(), imported_stl_list_[body_index], &body_mesh_list_[body_index]); - fixed_constraint_.emplace_back(make_shared(solid_body_list_[body_index]->GetImportedModel(), bp)); - } -} - -void StructuralSimulation::InitializePositionSolidBody() -{ - position_solid_body_ = {}; - for (unsigned int i = 0; i < position_solid_body_tuple_.size(); i++) - { - int body_index = get<0>(position_solid_body_tuple_[i]); - Real start_time = get<1>(position_solid_body_tuple_[i]); - Real end_time = get<2>(position_solid_body_tuple_[i]); - Vecd pos_end_center = get<3>(position_solid_body_tuple_[i]); - BodyPartByParticleTriMesh* bp = new BodyPartByParticleTriMesh(solid_body_list_[body_index]->GetImportedModel(), imported_stl_list_[body_index], &body_mesh_list_[body_index]); - - position_solid_body_.emplace_back(make_shared(solid_body_list_[body_index]->GetImportedModel(), bp, start_time, end_time, pos_end_center)); - } -} - -void StructuralSimulation::InitializePositionScaleSolidBody() -{ - position_scale_solid_body_ = {}; - for (unsigned int i = 0; i < position_scale_solid_body_tuple_.size(); i++) - { - int body_index = get<0>(position_scale_solid_body_tuple_[i]); - Real start_time = get<1>(position_scale_solid_body_tuple_[i]); - Real end_time = get<2>(position_scale_solid_body_tuple_[i]); - Real scale = get<3>(position_scale_solid_body_tuple_[i]); - BodyPartByParticleTriMesh* bp = new BodyPartByParticleTriMesh(solid_body_list_[body_index]->GetImportedModel(), imported_stl_list_[body_index], &body_mesh_list_[body_index]); - - position_scale_solid_body_.emplace_back(make_shared(solid_body_list_[body_index]->GetImportedModel(), bp, start_time, end_time, scale)); - } -} - -void StructuralSimulation::InitializeTranslateSolidBody() -{ - translation_solid_body_ = {}; - for (unsigned int i = 0; i < translation_solid_body_tuple_.size(); i++) - { - int body_index = get<0>(translation_solid_body_tuple_[i]); - Real start_time = get<1>(translation_solid_body_tuple_[i]); - Real end_time = get<2>(translation_solid_body_tuple_[i]); - Vecd translation = get<3>(translation_solid_body_tuple_[i]); - BodyPartByParticleTriMesh* bp = new BodyPartByParticleTriMesh(solid_body_list_[body_index]->GetImportedModel(), imported_stl_list_[body_index], &body_mesh_list_[body_index]); - - translation_solid_body_.emplace_back(make_shared(solid_body_list_[body_index]->GetImportedModel(), bp, start_time, end_time, translation)); - } -} - -void StructuralSimulation::ExecuteCorrectConfiguration() -{ - for (unsigned int i = 0; i < solid_body_list_.size(); i++) - { - solid_body_list_[i]->GetCorrectConfiguration()->parallel_exec(); - } -} - -void StructuralSimulation::ExecuteInitializeATimeStep() -{ - for (unsigned int i = 0; i < initialize_gravity_.size(); i++) - { - initialize_gravity_[i]->parallel_exec(); - } -} - -void StructuralSimulation::ExecuteAccelerationForBodyPartInBoundingBox() -{ - for (unsigned int i = 0; i < acceleration_bounding_box_.size(); i++) - { - acceleration_bounding_box_[i]->parallel_exec(); - } -} - -void StructuralSimulation::ExecuteSpringDamperConstraintParticleWise() -{ - for (unsigned int i = 0; i < spring_damper_constraint_.size(); i++) - { - spring_damper_constraint_[i]->parallel_exec(); - } -} - -void StructuralSimulation::ExecuteContactDensitySummation() -{ - for (unsigned int i = 0; i < contact_density_list_.size(); i++) - { - contact_density_list_[i]->parallel_exec(); - } -} - -void StructuralSimulation::ExecuteContactForce() -{ - for (unsigned int i = 0; i < contact_force_list_.size(); i++) - { - contact_force_list_[i]->parallel_exec(); - } -} - -void StructuralSimulation::ExecuteStressRelaxationFirstHalf(Real dt) -{ - for (unsigned int i = 0; i < solid_body_list_.size(); i++) - { - solid_body_list_[i]->GetStressRelaxationFirstHalf()->parallel_exec(dt); - } -} - -void StructuralSimulation::ExecuteConstrainSolidBodyRegion() -{ - for (unsigned int i = 0; i < fixed_constraint_.size(); i++) - { - fixed_constraint_[i]->parallel_exec(); - } -} - -void StructuralSimulation::ExecutePositionSolidBody(Real dt) -{ - for (unsigned int i = 0; i < position_solid_body_.size(); i++) - { - position_solid_body_[i]->parallel_exec(dt); - } -} - -void StructuralSimulation::ExecutePositionScaleSolidBody(Real dt) -{ - for (unsigned int i = 0; i < position_scale_solid_body_.size(); i++) - { - position_scale_solid_body_[i]->parallel_exec(dt); - } -} - -void StructuralSimulation::ExecuteTranslateSolidBody(Real dt) -{ - for (unsigned int i = 0; i < translation_solid_body_.size(); i++) - { - translation_solid_body_[i]->parallel_exec(dt); - } -} - -void StructuralSimulation::ExecuteDamping(Real dt) -{ - for (unsigned int i = 0; i < solid_body_list_.size(); i++) - { - solid_body_list_[i]->GetDampingWithRandomChoice()->parallel_exec(dt); - } -} - -void StructuralSimulation::ExecuteStressRelaxationSecondHalf(Real dt) -{ - for (unsigned int i = 0; i < solid_body_list_.size(); i++) - { - solid_body_list_[i]->GetStressRelaxationSecondHalf()->parallel_exec(dt); - } -} - -void StructuralSimulation::ExecuteUpdateCellLinkedList() -{ - for (unsigned int i = 0; i < solid_body_list_.size(); i++) - { - solid_body_list_[i]->GetImportedModel()->updateCellLinkedList(); - } -} - -void StructuralSimulation::ExecuteContactUpdateConfiguration() -{ - for (unsigned int i = 0; i < contact_list_.size(); i++) - { - contact_list_[i]->updateConfiguration(); - } -} - -void StructuralSimulation::RunSimulationStep(int &ite, Real &dt, Real &integration_time) -{ - if (ite % 100 == 0) cout << "N=" << ite << " Time: " << GlobalStaticVariables::physical_time_ << " dt: " << dt << "\n"; - - /** ACTIVE BOUNDARY CONDITIONS */ - ExecuteInitializeATimeStep(); - ExecuteAccelerationForBodyPartInBoundingBox(); - ExecuteSpringDamperConstraintParticleWise(); - - /** CONTACT */ - ExecuteContactDensitySummation(); - ExecuteContactForce(); - - /** STRESS RELAXATOIN, DAMPING, POSITIONAL CONSTRAINTS */ - ExecuteStressRelaxationFirstHalf(dt); - - ExecuteConstrainSolidBodyRegion(); - ExecutePositionSolidBody(dt); - ExecutePositionScaleSolidBody(dt); - ExecuteTranslateSolidBody(dt); - - ExecuteDamping(dt); - - ExecuteConstrainSolidBodyRegion(); - ExecutePositionSolidBody(dt); - ExecutePositionScaleSolidBody(dt); - ExecuteTranslateSolidBody(dt); - - ExecuteStressRelaxationSecondHalf(dt); - - /** UPDATE TIME STEP SIZE, INCREMENT */ - ite++; - dt = system_.getSmallestTimeStepAmongSolidBodies(); - integration_time += dt; - GlobalStaticVariables::physical_time_ += dt; - - /** UPDATE BODIES CELL LINKED LISTS */ - ExecuteUpdateCellLinkedList(); - - /** UPDATE CONTACT CONFIGURATION */ - ExecuteContactUpdateConfiguration(); -} - -void StructuralSimulation::RunSimulation(Real end_time) -{ - BodyStatesRecordingToVtu write_states(in_output_, system_.real_bodies_); - GlobalStaticVariables::physical_time_ = 0.0; - - /** INITIALALIZE SYSTEM */ - system_.initializeSystemCellLinkedLists(); - system_.initializeSystemConfigurations(); - - /** INITIAL CONDITION */ - ExecuteCorrectConfiguration(); - - /** Statistics for computing time. */ - write_states.writeToFile(0); - int ite = 0; - Real output_period = 0.1 / 100.0; - Real dt = 0.0; - tick_count t1 = tick_count::now(); - tick_count::interval_t interval; - /** Main loop */ - while (GlobalStaticVariables::physical_time_ < end_time) - { - Real integration_time = 0.0; - while (integration_time < output_period) - { - RunSimulationStep(ite, dt, integration_time); - } - tick_count t2 = tick_count::now(); - write_states.writeToFile(); - tick_count t3 = tick_count::now(); - interval += t3 - t2; - } - tick_count t4 = tick_count::now(); - tick_count::interval_t tt; - tt = t4 - t1 - interval; - cout << "Total wall time for computation: " << tt.seconds() << " seconds." << endl; -} - -void StructuralSimulation::InitSimulation() -{ - /** INITIALALIZE SYSTEM */ - system_.initializeSystemCellLinkedLists(); - system_.initializeSystemConfigurations(); - - /** INITIAL CONDITION */ - ExecuteCorrectConfiguration(); -} - -double StructuralSimulation::RunSimulationFixedDurationJS(int number_of_steps) -{ - BodyStatesRecordingToVtu write_states(in_output_, system_.real_bodies_); - GlobalStaticVariables::physical_time_ = 0.0; - - /** Statistics for computing time. */ - write_states.writeToFile(0); - int output_period = 100; - int ite = 0; - Real dt = 0.0; - tick_count t1 = tick_count::now(); - tick_count::interval_t interval; - /** Main loop */ - while (ite < number_of_steps) - { - Real integration_time = 0.0; - int output_step = 0; - while (output_step < output_period) - { - RunSimulationStep(ite, dt, integration_time); - output_step++; - } - tick_count t2 = tick_count::now(); - write_states.writeToFile(); - tick_count t3 = tick_count::now(); - interval += t3 - t2; - } - tick_count t4 = tick_count::now(); - tick_count::interval_t tt; - tt = t4 - t1 - interval; - return tt.seconds(); -} \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.h b/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.h deleted file mode 100644 index ad72a1c799..0000000000 --- a/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.h +++ /dev/null @@ -1,212 +0,0 @@ -/** -* @file solid_structural_simulation_class.h -* @brief solid structural simulation class definition -* @details solid structural simulation class for general structural simulations -* @author Bence Z. Rochlitz -*/ - -#ifndef SOLID_STRUCTURAL_SIMULATION_CLASS_H -#define SOLID_STRUCTURAL_SIMULATION_CLASS_H - -#include "sphinxsys.h" -#include -#include - -using namespace SPH; -using namespace std; -using GravityPair = pair; -using AccelTuple = tuple; -using SpringDamperTuple = tuple; -using PositionSolidBodyTuple = tuple; -using PositionScaleSolidBodyTuple = tuple; -using TranslateSolidBodyTuple = tuple; - -class BodyPartByParticleTriMesh : public BodyPartByParticle -{ -public: - BodyPartByParticleTriMesh(SPHBody* body, string body_part_name, TriangleMeshShape* triangle_mesh_shape); - ~BodyPartByParticleTriMesh(); -}; - -class ImportedModel : public SolidBody -{ -public: - ImportedModel(SPHSystem &system, string body_name, TriangleMeshShape* triangle_mesh_shape, ParticleAdaptation* particle_adaptation); - ~ImportedModel(); -}; - -class SolidBodyForSimulation -{ -private: - ImportedModel imported_model_; - //LinearElasticSolid material_model_; - ElasticSolidParticles elastic_solid_particles_; - BodyRelationInner inner_body_relation_; - - solid_dynamics::CorrectConfiguration correct_configuration_; - solid_dynamics::StressRelaxationFirstHalf stress_relaxation_first_half_; - solid_dynamics::StressRelaxationSecondHalf stress_relaxation_second_half_; - DampingWithRandomChoice> damping_random_; - -public: - SolidBodyForSimulation(SPHSystem &system, string body_name, TriangleMeshShape& triangle_mesh_shape, ParticleAdaptation& particle_adaptation, Real physical_viscosity, LinearElasticSolid& material_model); - ~SolidBodyForSimulation(){}; - - ImportedModel* GetImportedModel() { return &imported_model_; }; - //LinearElasticSolid* GetMaterialModel() { return &material_model_; }; - ElasticSolidParticles* GetElasticSolidParticles() { return &elastic_solid_particles_; }; - BodyRelationInner* GetInnerBodyRelation() { return &inner_body_relation_; }; - - solid_dynamics::CorrectConfiguration* GetCorrectConfiguration() { return &correct_configuration_; }; - solid_dynamics::StressRelaxationFirstHalf* GetStressRelaxationFirstHalf() { return &stress_relaxation_first_half_; }; - solid_dynamics::StressRelaxationSecondHalf* GetStressRelaxationSecondHalf() { return &stress_relaxation_second_half_; }; - DampingWithRandomChoice>* GetDampingWithRandomChoice() { return &damping_random_; }; -}; - -void ExpandBoundingBox(BoundingBox* original, BoundingBox* additional); - -void RelaxParticlesSingleResolution(In_Output* in_output, - bool write_particles_to_file, - ImportedModel* imported_model, - ElasticSolidParticles* imported_model_particles, - BodyRelationInner* imported_model_inner); - - -class StructuralSimulationInput -{ -public: - string relative_input_path_; - vector imported_stl_list_; - Real scale_stl_; - vector translation_list_; - vector resolution_list_; - vector material_model_list_; - Real physical_viscosity_; - vector> contacting_bodies_list_; - // scale system boundaries - Real scale_system_boundaries_; - // particle relaxation - vector particle_relaxation_list_; - // boundary conditions - vector non_zero_gravity_; - vector acceleration_bounding_box_tuple_; - vector spring_damper_tuple_; - vector body_indeces_fixed_constraint_; - vector position_solid_body_tuple_; - vector position_scale_solid_body_tuple_; - vector translation_solid_body_tuple_; - - StructuralSimulationInput( - string relative_input_path, - vector imported_stl_list, - Real scale_stl, - vector translation_list, - vector resolution_list, - vector material_model_list, - Real physical_viscosity, - vector> contacting_bodies_list - ); -}; - -class StructuralSimulation - { - protected: - // mandatory input - string relative_input_path_; - vector imported_stl_list_; - Real scale_stl_; - vector translation_list_; - vector resolution_list_; - vector material_model_list_; - Real physical_viscosity_; - vector> contacting_bodies_list_; - vector particle_relaxation_list_; // optional: particle relaxation - - // internal members - Real system_resolution_; - SPHSystem system_; - Real scale_system_boundaries_; - In_Output in_output_; - - vector body_mesh_list_; - vector particle_adaptation_list_; - vector> solid_body_list_; - - vector> contact_list_; - vector> contact_density_list_; - vector> contact_force_list_; - - // for InitializeATimeStep - vector> initialize_gravity_; - vector non_zero_gravity_; - // for AccelerationForBodyPartInBoundingBox - vector> acceleration_bounding_box_; - vector acceleration_bounding_box_tuple_; - // for SpringDamperConstraintParticleWise - vector> spring_damper_constraint_; - vector spring_damper_tuple_; - // for ConstrainSolidBodyRegion - vector> fixed_constraint_; - vector body_indeces_fixed_constraint_; - // for PositionSolidBody - vector> position_solid_body_; - vector position_solid_body_tuple_; - // for PositionScaleSolidBody - vector> position_scale_solid_body_; - vector position_scale_solid_body_tuple_; - // for TranslateSolidBody - vector> translation_solid_body_; - vector translation_solid_body_tuple_; - - // for constructor, the order is important - void ScaleTranslationAndResolution(); - void SetSystemResolutionMax(); - void CreateBodyMeshList(); - void CreateParticleAdaptationList(); - void CalculateSystemBoundaries(); - void InitializeElasticSolidBodies(); - void InitializeContactBetweenTwoBodies(int first, int second); - void InitializeAllContacts(); - - // for InitializeBoundaryConditions - void InitializeGravity(); - void InitializeAccelerationForBodyPartInBoundingBox(); - void InitializeSpringDamperConstraintParticleWise(); - void InitializeConstrainSolidBodyRegion(); - void InitializePositionSolidBody(); - void InitializePositionScaleSolidBody(); - void InitializeTranslateSolidBody(); - - // for RunSimulation, the order is important - void ExecuteCorrectConfiguration(); - void ExecuteInitializeATimeStep(); - void ExecuteAccelerationForBodyPartInBoundingBox(); - void ExecuteSpringDamperConstraintParticleWise(); - void ExecuteContactDensitySummation(); - void ExecuteContactForce(); - void ExecuteStressRelaxationFirstHalf(Real dt); - void ExecuteConstrainSolidBodyRegion(); - void ExecutePositionSolidBody(Real dt); - void ExecutePositionScaleSolidBody(Real dt); - void ExecuteTranslateSolidBody(Real dt); - void ExecuteDamping(Real dt); - void ExecuteStressRelaxationSecondHalf(Real dt); - void ExecuteUpdateCellLinkedList(); - void ExecuteContactUpdateConfiguration(); - void RunSimulationStep(int &ite, Real &dt, Real &integration_time); - - // Initialize simulation - void InitSimulation(); - - public: - StructuralSimulation(StructuralSimulationInput& input); - ~StructuralSimulation(); - - //For c++ - void RunSimulation(Real end_time); - - //For JS - double RunSimulationFixedDurationJS(int number_of_steps); - }; - -#endif //SOLID_STRUCTURAL_SIMULATION_CLASS_H \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/meshes/CMakeLists.txt b/SPHINXsys/src/for_3D_build/meshes/CMakeLists.txt deleted file mode 100644 index fd5eae60fd..0000000000 --- a/SPHINXsys/src/for_3D_build/meshes/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/meshes/base_mesh_supplementary.cpp b/SPHINXsys/src/for_3D_build/meshes/base_mesh_supplementary.cpp deleted file mode 100644 index 34c56e7881..0000000000 --- a/SPHINXsys/src/for_3D_build/meshes/base_mesh_supplementary.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @file base_mesh_supplementary.cpp - * @author Luhui Han, Chi ZHang Yongchuan YU and Xiangyu Hu - */ - -#include "base_mesh.h" - -namespace SPH { - //=================================================================================================// - void MeshIterator(Vec3u index_begin, Vec3u index_end, MeshFunctor& mesh_functor, Real dt) - { - for (size_t i = index_begin[0]; i != index_end[0]; ++i) - for (size_t j = index_begin[1]; j != index_end[1]; ++j) - for (size_t k = index_begin[2]; k != index_end[2]; ++k) - { - mesh_functor(Vecu(i, j, k), dt); - } - } - //=================================================================================================// - void MeshIterator_parallel(Vecu index_begin, Vecu index_end, MeshFunctor& mesh_functor, Real dt) - { - parallel_for(blocked_range3d - (index_begin[0], index_end[0], index_begin[1], index_end[1], index_begin[2], index_end[2]), - [&](const blocked_range3d& r) { - for (size_t i = r.pages().begin(); i != r.pages().end(); ++i) - for (size_t j = r.rows().begin(); j != r.rows().end(); ++j) - for (size_t k = r.cols().begin(); k != r.cols().end(); ++k) - { - mesh_functor(Vecu(i, j, k), dt); - } - }, ap); - } - //=================================================================================================// - Vecu BaseMesh::transfer1DtoMeshIndex(Vecu number_of_grid_points, size_t i) - { - size_t row_times_column_size = number_of_grid_points[1] * number_of_grid_points[2]; - size_t page = i / row_times_column_size; - size_t left_over = (i - page * row_times_column_size); - size_t row_size = number_of_grid_points[2]; - size_t column = left_over / row_size; - return Vecu(page, column, left_over - column * row_size); - } - //=================================================================================================// - size_t BaseMesh::transferMeshIndexTo1D(Vecu number_of_grid_points, Vecu grid_index) - { - return grid_index[0] * number_of_grid_points[1] * number_of_grid_points[2] - + grid_index[1] * number_of_grid_points[2] - + grid_index[2]; - } - //=================================================================================================// - size_t BaseMesh::transferMeshIndexToMortonOrder(Vecu grid_index) - { - return MortonCode(grid_index[0]) | (MortonCode(grid_index[1]) << 1) - | (MortonCode(grid_index[2]) << 2); - } -} diff --git a/SPHINXsys/src/for_3D_build/meshes/mesh_cell_linked_list.hpp b/SPHINXsys/src/for_3D_build/meshes/mesh_cell_linked_list.hpp deleted file mode 100644 index 5e1d733b14..0000000000 --- a/SPHINXsys/src/for_3D_build/meshes/mesh_cell_linked_list.hpp +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @file body_relation.hpp - * @brief Here, Functions belong to BaseBody, RealBody and FictitiousBody are given. - * @author hi ZHang and Xiangyu Hu - */ - -#pragma once - -#include "body_relation.h" -#include "base_particles.h" -#include "base_kernel.h" -#include "mesh_cell_linked_list.h" - -namespace SPH -{ - //=================================================================================================// - template - void MeshCellLinkedList::searchNeighborsByParticles(size_t total_real_particles, BaseParticles& source_particles, - ParticleConfiguration& particle_configuration, GetParticleIndex& get_particle_index, - GetSearchRange& get_search_range, GetNeighborRelation& get_neighbor_relation) - { - parallel_for(blocked_range(0, total_real_particles), - [&](const blocked_range& r) { - StdLargeVec& pos_n = source_particles.pos_n_; - for (size_t num = r.begin(); num != r.end(); ++num) { - size_t index_i = get_particle_index(num); - Vecd& particle_position = pos_n[index_i]; - int search_range = get_search_range(index_i); - Vecu target_cell_index = CellIndexFromPosition(particle_position); - int i = (int)target_cell_index[0]; - int j = (int)target_cell_index[1]; - int k = (int)target_cell_index[2]; - - Neighborhood& neighborhood = particle_configuration[index_i]; - for (int l = SMAX(i - search_range, 0); l <= SMIN(i + search_range, int(number_of_cells_[0]) - 1); ++l) - for (int m = SMAX(j - search_range, 0); m <= SMIN(j + search_range, int(number_of_cells_[1]) - 1); ++m) - for (int q = SMAX(k - search_range, 0); q <= SMIN(k + search_range, int(number_of_cells_[2]) - 1); ++q) - { - ListDataVector& target_particles = cell_linked_lists_[l][m][q].cell_list_data_; - for (const ListData& list_data : target_particles) - { - //displacement pointing from neighboring particle to origin particle - Vecd displacement = particle_position - list_data.second; - get_neighbor_relation(neighborhood, displacement, index_i, list_data.first); - } - } - } - }, ap); - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/for_3D_build/meshes/mesh_cell_linked_list_supplementary.cpp b/SPHINXsys/src/for_3D_build/meshes/mesh_cell_linked_list_supplementary.cpp deleted file mode 100644 index 052aadc28d..0000000000 --- a/SPHINXsys/src/for_3D_build/meshes/mesh_cell_linked_list_supplementary.cpp +++ /dev/null @@ -1,312 +0,0 @@ -#include "mesh_cell_linked_list.h" -#include "base_kernel.h" -#include "base_body.h" -#include "base_particles.h" -#include "neighbor_relation.h" - -namespace SPH { - //=================================================================================================// - CellList::CellList() - { - concurrent_particle_indexes_.reserve(36); - } - //=================================================================================================// - void MeshCellLinkedList - ::allocateMeshDataMatrix() - { - Allocate3dArray(cell_linked_lists_, number_of_cells_); - } - //=================================================================================================// - void MeshCellLinkedList - ::deleteMeshDataMatrix() - { - Delete3dArray(cell_linked_lists_, number_of_cells_); - } - //=================================================================================================// - void MeshCellLinkedList::clearCellLists() - { - parallel_for(blocked_range3d(0, number_of_cells_[0], 0, number_of_cells_[1], 0, number_of_cells_[2]), - [&](const blocked_range3d& r) { - for (size_t i = r.pages().begin(); i != r.pages().end(); ++i) - for (size_t j = r.rows().begin(); j != r.rows().end(); ++j) - for (size_t k = r.cols().begin(); k != r.cols().end(); ++k) - { - cell_linked_lists_[i][j][k].concurrent_particle_indexes_.clear(); - cell_linked_lists_[i][j][k].real_particle_indexes_.clear(); - - } - }, ap); - } - //=================================================================================================// - void MeshCellLinkedList::UpdateCellListData() - { - StdLargeVec& pos_n = base_particles_->pos_n_; - parallel_for(blocked_range3d(0, number_of_cells_[0], 0, number_of_cells_[1], 0, number_of_cells_[2]), - [&](const blocked_range3d& r) { - for (size_t i = r.pages().begin(); i != r.pages().end(); ++i) - for (size_t j = r.rows().begin(); j != r.rows().end(); ++j) - for (size_t k = r.cols().begin(); k != r.cols().end(); ++k) - { - CellList& cell_list = cell_linked_lists_[i][j][k]; - cell_list.cell_list_data_.clear(); - for (size_t s = 0; s != cell_list.concurrent_particle_indexes_.size(); ++s) { - size_t particle_index = cell_list.concurrent_particle_indexes_[s]; - cell_list.cell_list_data_.emplace_back(std::make_pair(particle_index, pos_n[particle_index])); - } - } - }, ap); - } - //=================================================================================================// - void MeshCellLinkedList::updateSplitCellLists(SplitCellLists& split_cell_lists) - { - //clear the data - clearSplitCellLists(split_cell_lists); - - parallel_for(blocked_range3d(0, number_of_cells_[0], 0, number_of_cells_[1], 0, number_of_cells_[2]), - [&](const blocked_range3d& r) { - for (size_t i = r.pages().begin(); i != r.pages().end(); ++i) - for (size_t j = r.rows().begin(); j != r.rows().end(); ++j) - for (size_t k = r.cols().begin(); k != r.cols().end(); ++k) - { - CellList& cell_list = cell_linked_lists_[i][j][k]; - size_t real_particles_in_cell = cell_list.concurrent_particle_indexes_.size(); - if (real_particles_in_cell != 0) { - for (size_t s = 0; s != real_particles_in_cell; ++s) - cell_list.real_particle_indexes_.push_back(cell_list.concurrent_particle_indexes_[s]); - split_cell_lists[transferMeshIndexTo1D(Vecu(3), Vecu(i % 3, j % 3, k % 3))] - .push_back(&cell_linked_lists_[i][j][k]); - } - } - }, ap); - } - //=================================================================================================// - void MeshCellLinkedList - ::insertACellLinkedParticleIndex(size_t particle_index, Vecd particle_position) - { - Vecu cellpos = CellIndexFromPosition(particle_position); - cell_linked_lists_[cellpos[0]][cellpos[1]][cellpos[2]].concurrent_particle_indexes_.emplace_back(particle_index); - } - //=================================================================================================// - void MeshCellLinkedList - ::InsertACellLinkedListDataEntry(size_t particle_index, Vecd particle_position) - { - Vecu cellpos = CellIndexFromPosition(particle_position); - cell_linked_lists_[cellpos[0]][cellpos[1]][cellpos[2]].cell_list_data_ - .emplace_back(std::make_pair(particle_index, particle_position)); - } - //=================================================================================================// - ListData MeshCellLinkedList::findNearestListDataEntry(Vecd& position) - { - Real min_distance = Infinity; - ListData nearest_entry = std::make_pair(MaxSize_t, Vecd(Infinity)); - - Vecu cell_location = CellIndexFromPosition(position); - int i = (int)cell_location[0]; - int j = (int)cell_location[1]; - int k = (int)cell_location[2]; - - for (int l = SMAX(i - 1, 0); l <= SMIN(i + 1, int(number_of_cells_[0]) - 1); ++l) - { - for (int m = SMAX(j - 1, 0); m <= SMIN(j + 1, int(number_of_cells_[1]) - 1); ++m) - { - for (int q = SMAX(k - 1, 0); q <= SMIN(k + 1, int(number_of_cells_[2]) - 1); ++q) - { - ListDataVector& target_particles = cell_linked_lists_[l][m][q].cell_list_data_; - for (size_t n = 0; n != target_particles.size(); ++n) - { - Real distance = (position - target_particles[n].second).norm(); - if(distance < min_distance) - { - min_distance = distance; - nearest_entry = target_particles[n]; - } - } - } - } - } - return nearest_entry; - } - //=================================================================================================// - void MeshCellLinkedList:: - tagBodyPartByCell(CellLists& cell_lists, std::function& check_included) - { - for (int i = 0; i < (int)number_of_cells_[0]; ++i) - for (int j = 0; j < (int)number_of_cells_[1]; ++j) - for (int k = 0; k < (int)number_of_cells_[2]; ++k) - { - bool is_included = false; - for (int l = SMAX(i - 1, 0); l <= SMIN(i + 1, int(number_of_cells_[0]) - 1); ++l) - for (int m = SMAX(j - 1, 0); m <= SMIN(j + 1, int(number_of_cells_[1]) - 1); ++m) - for (int n = SMAX(k - 1, 0); n <= SMIN(k + 1, int(number_of_cells_[2]) - 1); ++n) - { - //all cells near or contained by the body part shape are inlcuded - if (check_included(CellPositionFromIndex(Vecu(l, m, n)), grid_spacing_)) - { - is_included = true; - } - } - if (is_included == true) cell_lists.push_back(&cell_linked_lists_[i][j][k]); - } - } - //=================================================================================================// - void MeshCellLinkedList:: - tagBodyDomainBoundingCells(StdVec& cell_lists, BoundingBox& body_domain_bounds, int axis) - { - int second_axis = SecondAxis(axis); - int third_axis = ThirdAxis(axis); - Vecu body_lower_bound_cell_ = CellIndexFromPosition(body_domain_bounds.first); - Vecu body_upper_bound_cell_ = CellIndexFromPosition(body_domain_bounds.second); - - //lower bound cells - for (size_t k = SMAX(int(body_lower_bound_cell_[third_axis]) - 1, 0); - k < (size_t)SMIN(int(body_upper_bound_cell_[third_axis] + 2), int(number_of_cells_[third_axis])); ++k) - { - - for (size_t j = SMAX(int(body_lower_bound_cell_[second_axis]) - 1, 0); - j < (size_t)SMIN(int(body_upper_bound_cell_[second_axis] + 2), int(number_of_cells_[second_axis])); ++j) - { - - for (size_t i = SMAX(int(body_lower_bound_cell_[axis]) - 1, 0); - i <= (size_t)SMIN(int(body_lower_bound_cell_[axis] + 1), int(number_of_cells_[axis] - 1)); ++i) - { - Vecu cell_position(0); - cell_position[axis] = i; - cell_position[second_axis] = j; - cell_position[third_axis] = k; - cell_lists[0].push_back(&cell_linked_lists_[cell_position[0]][cell_position[1]][cell_position[2]]); - } - } - } - - //upper bound cells - for (size_t k = SMAX(int(body_lower_bound_cell_[third_axis]) - 1, 0); - k < (size_t)SMIN(int(body_upper_bound_cell_[third_axis] + 2), int(number_of_cells_[third_axis])); ++k) - { - - for (size_t j = SMAX(int(body_lower_bound_cell_[second_axis]) - 1, 0); - j < (size_t)SMIN(int(body_upper_bound_cell_[second_axis] + 2), int(number_of_cells_[second_axis])); ++j) - { - - for (size_t i = SMAX(int(body_upper_bound_cell_[axis]) - 1, 0); - i <= (size_t)SMIN(int(body_upper_bound_cell_[axis] + 1), int(number_of_cells_[axis] - 1)); ++i) - { - Vecu cell_position(0); - cell_position[axis] = i; - cell_position[second_axis] = j; - cell_position[third_axis] = k; - cell_lists[1].push_back(&cell_linked_lists_[cell_position[0]][cell_position[1]][cell_position[2]]); - } - } - } - } - //=================================================================================================// - void MeshCellLinkedList:: - tagMirrorBoundingCells(CellLists& cell_lists, BoundingBox& body_domain_bounds, int axis, bool positive) - { - int second_axis = SecondAxis(axis); - int third_axis = ThirdAxis(axis); - Vecu body_lower_bound_cell_ = CellIndexFromPosition(body_domain_bounds.first); - Vecu body_upper_bound_cell_ = CellIndexFromPosition(body_domain_bounds.second); - - if (positive) { - //upper bound cells - for (size_t k = SMAX(int(body_lower_bound_cell_[third_axis]) - 1, 0); - k < (size_t)SMIN(int(body_upper_bound_cell_[third_axis] + 2), int(number_of_cells_[third_axis])); ++k) - { - - for (size_t j = SMAX(int(body_lower_bound_cell_[second_axis]) - 1, 0); - j < (size_t)SMIN(int(body_upper_bound_cell_[second_axis] + 2), int(number_of_cells_[second_axis])); ++j) - { - - for (size_t i = SMAX(int(body_upper_bound_cell_[axis]) - 1, 0); - i <= (size_t)SMIN(int(body_upper_bound_cell_[axis] + 1), int(number_of_cells_[axis] - 1)); ++i) - { - Vecu cell_position(0); - cell_position[axis] = i; - cell_position[second_axis] = j; - cell_position[third_axis] = k; - cell_lists.push_back(&cell_linked_lists_[cell_position[0]][cell_position[1]][cell_position[2]]); - } - } - } - } - else { - //lower bound cells - for (size_t k = SMAX(int(body_lower_bound_cell_[third_axis]) - 1, 0); - k < (size_t)SMIN(int(body_upper_bound_cell_[third_axis] + 2), int(number_of_cells_[third_axis])); ++k) - { - - for (size_t j = SMAX(int(body_lower_bound_cell_[second_axis]) - 1, 0); - j < (size_t)SMIN(int(body_upper_bound_cell_[second_axis] + 2), int(number_of_cells_[second_axis])); ++j) - { - - for (size_t i = SMAX(int(body_lower_bound_cell_[axis]) - 1, 0); - i <= (size_t)SMIN(int(body_lower_bound_cell_[axis] + 1), int(number_of_cells_[axis] - 1)); ++i) - { - Vecu cell_position(0); - cell_position[axis] = i; - cell_position[second_axis] = j; - cell_position[third_axis] = k; - cell_lists.push_back(&cell_linked_lists_[cell_position[0]][cell_position[1]][cell_position[2]]); - } - } - } - } - } - //=================================================================================================// - void MeshCellLinkedList::writeMeshToPltFile(std::ofstream& output_file) - { - Vecu number_of_operation = number_of_cells_; - - output_file << "\n"; - output_file << "title='View'" << "\n"; - output_file << "variables= " << "x, " << "y, " << "z, " << "particles_in_cell " << "\n"; - output_file << "zone i=" << number_of_operation[0] << " j=" << number_of_operation[1] << " k=" << number_of_operation[2] - << " DATAPACKING=BLOCK SOLUTIONTIME=" << 0 << "\n"; - - for (size_t k = 0; k != number_of_operation[2]; ++k) - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - Vecd data_position = CellPositionFromIndex(Vecu(i, j, k)); - output_file << data_position[0] << " "; - } - output_file << " \n"; - } - - for (size_t k = 0; k != number_of_operation[2]; ++k) - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - Vecd data_position = CellPositionFromIndex(Vecu(i, j, k)); - output_file << data_position[1] << " "; - } - output_file << " \n"; - } - - for (size_t k = 0; k != number_of_operation[2]; ++k) - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - Vecd data_position = CellPositionFromIndex(Vecu(i, j, k)); - output_file << data_position[2] << " "; - } - output_file << " \n"; - } - - for (size_t k = 0; k != number_of_operation[2]; ++k) - for (size_t j = 0; j != number_of_operation[1]; ++j) - { - for (size_t i = 0; i != number_of_operation[0]; ++i) - { - output_file << cell_linked_lists_[i][j][k].concurrent_particle_indexes_.size() << " "; - - } - output_file << " \n"; - } - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/for_3D_build/meshes/mesh_with_data_packages.hpp b/SPHINXsys/src/for_3D_build/meshes/mesh_with_data_packages.hpp deleted file mode 100644 index 939ee52845..0000000000 --- a/SPHINXsys/src/for_3D_build/meshes/mesh_with_data_packages.hpp +++ /dev/null @@ -1,185 +0,0 @@ -/** -* @file base_mesh.hpp -* @brief This is the implementation of the template function and class for base mesh -* @author Chi ZHang and Xiangyu Hu -*/ - -#ifndef MESH_WITH_DATA_PACKAGES_3D_HPP -#define MESH_WITH_DATA_PACKAGES_3D_HPP - -#include "mesh_with_data_packages.h" - -//=================================================================================================// -namespace SPH { - //=================================================================================================// - template - template - DataType BaseDataPackage - ::probeDataPackage(PackageDataAddress& pkg_data_addrs, const Vecd& position) - { - Vec3u grid_idx = CellIndexFromPosition(position); - Vec3d grid_pos = GridPositionFromIndex(grid_idx); - Vec3d alpha = (position - grid_pos) / grid_spacing_; - Vec3d beta = Vec3d(1.0) - alpha; - - DataType bilinear_1 - = *pkg_data_addrs[grid_idx[0]][grid_idx[1]][grid_idx[2]] * beta[0] * beta[1] - + *pkg_data_addrs[grid_idx[0] + 1][grid_idx[1]][grid_idx[2]] * alpha[0] * beta[1] - + *pkg_data_addrs[grid_idx[0]][grid_idx[1] + 1][grid_idx[2]] * beta[0] * alpha[1] - + *pkg_data_addrs[grid_idx[0] + 1][grid_idx[1] + 1][grid_idx[2]] * alpha[0] * alpha[1]; - DataType bilinear_2 - = *pkg_data_addrs[grid_idx[0]][grid_idx[1]][grid_idx[2] + 1] * beta[0] * beta[1] - + *pkg_data_addrs[grid_idx[0] + 1][grid_idx[1]][grid_idx[2] + 1] * alpha[0] * beta[1] - + *pkg_data_addrs[grid_idx[0]][grid_idx[1] + 1][grid_idx[2] + 1] * beta[0] * alpha[1] - + *pkg_data_addrs[grid_idx[0] + 1][grid_idx[1] + 1][grid_idx[2] + 1] * alpha[0] * alpha[1]; - return bilinear_1 * beta[2] + bilinear_2 * alpha[2]; - } - //=================================================================================================// - template - template - void BaseDataPackage:: - computeGradient(PackageDataAddress& in_pkg_data_addrs, - PackageDataAddress out_pkg_data_addrs, Real dt) - { - for (int i = 1; i != PKG_SIZE + 1; ++i) - for (int j = 1; j != PKG_SIZE + 1; ++j) - for (int k = 1; k != PKG_SIZE + 1; ++k) - { - Real dphidx = (*in_pkg_data_addrs[i + 1][j][k] - *in_pkg_data_addrs[i - 1][j][k]); - Real dphidy = (*in_pkg_data_addrs[i][j + 1][k] - *in_pkg_data_addrs[i][j - 1][k]); - Real dphidz = (*in_pkg_data_addrs[i][j][k + 1] - *in_pkg_data_addrs[i][j][k - 1]); - *out_pkg_data_addrs[i][j][k] = Vecd(dphidx, dphidy, dphidz); - } - } - //=================================================================================================// - template - template - void BaseDataPackage:: - computeNormalizedGradient(PackageDataAddress& in_pkg_data_addrs, - PackageDataAddress out_pkg_data_addrs, Real dt) - { - for (int i = 1; i != PKG_SIZE + 1; ++i) - for (int j = 1; j != PKG_SIZE + 1; ++j) - for (int k = 1; k != PKG_SIZE + 1; ++k) - { - Real dphidx = (*in_pkg_data_addrs[i + 1][j][k] - *in_pkg_data_addrs[i - 1][j][k]); - Real dphidy = (*in_pkg_data_addrs[i][j + 1][k] - *in_pkg_data_addrs[i][j - 1][k]); - Real dphidz = (*in_pkg_data_addrs[i][j][k + 1] - *in_pkg_data_addrs[i][j][k - 1]); - Vecd normal = Vecd(dphidx, dphidy, dphidz); - *out_pkg_data_addrs[i][j][k] = normal / (normal.norm() + TinyReal); - } - } - //=================================================================================================// - template - template - void BaseDataPackage:: - initializePackageDataAddress(PackageData& pkg_data, - PackageDataAddress& pkg_data_addrs) - { - for (int i = 0; i != ADDRS_SIZE; ++i) - for (int j = 0; j != ADDRS_SIZE; ++j) - for (int k = 0; k != ADDRS_SIZE; ++k) - { - pkg_data_addrs[i][j][k] = &pkg_data[0][0][0]; - } - } - //=================================================================================================// - template - template - void BaseDataPackage:: - assignPackageDataAddress(PackageDataAddress& pkg_data_addrs, Vecu& addrs_index, - PackageData& pkg_data, Vecu& data_index) - { - pkg_data_addrs[addrs_index[0]][addrs_index[1]][addrs_index[2]] - = &pkg_data[data_index[0]][data_index[1]][data_index[2]]; - } - //=================================================================================================// - template - template - DataType BaseDataPackage:: - CornerAverage(PackageDataAddress& pkg_data_addrs, Veci addrs_index, Veci corner_direction) - { - DataType average(0); - for (int i = 0; i != 2; ++i) - for (int j = 0; j != 2; ++j) - for (int k = 0; k != 2; ++k) - { - int x_index = addrs_index[0] + i * corner_direction[0]; - int y_index = addrs_index[1] + j * corner_direction[1]; - int z_index = addrs_index[2] + k * corner_direction[2]; - average += *pkg_data_addrs[x_index][y_index][z_index]; - } - return average * 0.125; - } - //=================================================================================================// - template - template - DataType MeshWithDataPackages:: - DataValueFromGlobalIndex(Vecu global_data_index) - { - Vecu pkg_index_(0); - Vecu local_data_index(0); - for (int n = 0; n != 3; n++) - { - size_t cell_index_in_this_direction = global_data_index[n] / pkg_size_; - pkg_index_[n] = cell_index_in_this_direction; - local_data_index[n] = global_data_index[n] - cell_index_in_this_direction * pkg_size_; - } - PackageDataType& data = data_pkg_addrs_[pkg_index_[0]][pkg_index_[1]][pkg_index_[2]]->*MemPtr; - return data[local_data_index[0]][local_data_index[1]][local_data_index[2]]; - } - //=================================================================================================// - template - void MeshWithDataPackages::initializePackageAddressesInACell(Vecu cell_index) - { - int i = (int)cell_index[0]; - int j = (int)cell_index[1]; - int k = (int)cell_index[2]; - - DataPackageType* data_pkg = data_pkg_addrs_[i][j][k]; - if (data_pkg->is_inner_pkg_) { - for (int l = 0; l != pkg_addrs_size_; ++l) - for (int m = 0; m != pkg_addrs_size_; ++m) - for (int n = 0; n != pkg_addrs_size_; ++n) { - std::pair x_pair = CellShiftAndDataIndex(l); - std::pair y_pair = CellShiftAndDataIndex(m); - std::pair z_pair = CellShiftAndDataIndex(n); - - data_pkg->assignAllPackageDataAddress(Vecu(l, m, n), - data_pkg_addrs_[i + x_pair.first][j + y_pair.first][k + z_pair.first], - Vecu(x_pair.second, y_pair.second, z_pair.second)); - } - } - } - //=================================================================================================// - template - void MeshWithDataPackages::allocateMeshDataMatrix() - { - Allocate3dArray(data_pkg_addrs_, BaseMeshType::number_of_cells_); - } - //=================================================================================================// - template - void MeshWithDataPackages::deleteMeshDataMatrix() - { - Delete3dArray(data_pkg_addrs_, BaseMeshType::number_of_cells_); - } - //=================================================================================================// - template - template - DataType MeshWithDataPackages::probeMesh(const Vecd& position) - { - Vecu grid_index = BaseMeshType::CellIndexFromPosition(position); - size_t i = grid_index[0]; - size_t j = grid_index[1]; - size_t k = grid_index[2]; - - DataPackageType* data_pkg = data_pkg_addrs_[i][j][k]; - PackageDataAddressType& pkg_data_addrs = data_pkg->*MemPtr; - return data_pkg->is_inner_pkg_ ? - data_pkg->DataPackageType::template probeDataPackage(pkg_data_addrs, position) - : *pkg_data_addrs[0][0][0]; - } - //=================================================================================================// -} -//=================================================================================================// -#endif //MESH_WITH_DATA_PACKAGES_3D_HPP \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/particle_dynamics/CMakeLists.txt b/SPHINXsys/src/for_3D_build/particle_dynamics/CMakeLists.txt deleted file mode 100644 index fd5eae60fd..0000000000 --- a/SPHINXsys/src/for_3D_build/particle_dynamics/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/CMakeLists.txt b/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/CMakeLists.txt deleted file mode 100644 index 9d7cea4b86..0000000000 --- a/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) - diff --git a/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3.h b/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3.h deleted file mode 100644 index 05adb8db99..0000000000 --- a/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3.h +++ /dev/null @@ -1,68 +0,0 @@ -// MIT License -// -// Copyright (c) 2017 Martin Bisson -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#ifndef __POLAR_DECOMPOSITION_3X3_H__ -#define __POLAR_DECOMPOSITION_3X3_H__ - - -// Inclusion of the actual implementation of the algorithm. -#include "polar_decomposition_3x3_impl.h" - - - - -namespace polar -{ - - - // Compute the polar decomposition of the input matrix. - // - // This method decomposes the input matrix into one orthonormal (rotation) matrix - // and a remaining positive semi-definite (scale) matrix. - // - // Matrices are represented as 3x3 matrices in column-major representation. - // - // A [in] : Matrix to decompose. - // Q [out] : Rotation part of the polar decomposition. - // H [out] : Symmetric matrix represention the scale part of the polar decomposition. - template - void polar_decomposition(TReal* Q, TReal* H, const TReal* A) - { - // Convert parameters for the algorithm implementation. - typedef detail::matrix TMatrix; - - // Depending on the implementation, this part might involve a copy, - // but here the memory layout requirement is for matrices to be 3x3, - // column-major, so we can just cast directly. - TMatrix* matrixQ = reinterpret_cast(Q); - TMatrix* matrixH = reinterpret_cast(H); - const TMatrix* matrixA = reinterpret_cast(A); - - detail::run_algorithm_3_5(*matrixQ, *matrixH, *matrixA); - } - - -}; // End of namespace polar. - - - - -#endif // __POLAR_DECOMPOSITION_3X3_H__ diff --git a/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3_impl.h b/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3_impl.h deleted file mode 100644 index a2a34d571d..0000000000 --- a/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3_impl.h +++ /dev/null @@ -1,1373 +0,0 @@ -// MIT License -// -// Copyright (c) 2017 Martin Bisson -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#ifndef __POLAR_DECOMPOSITION_3X3_IMPL_H__ -#define __POLAR_DECOMPOSITION_3X3_IMPL_H__ - - - - -#include "polar_decomposition_3x3_matrix.h" - -#include - - -namespace polar -{ - - - // This part implements portions of the algorithms that are tailored - // to just the specifics of what is needed. - namespace detail - { - - - // This method is based on the Matlab implementation. - // It computes the determinant of B matrix from an LU factorization with partial pivoting, - // except that it actually computes it from the values of A, (B matrix is a function of A). - template - inline TReal compute_b_determinant_from_a_matrix_lu_partial(const matrix& A) - { - TReal b = 0; - { - TReal temp; - temp = (A(1,1)*A(2,2)-A(2,1)*A(1,2)); b += (temp * temp); - temp = (A(0,1)*A(2,2)-A(2,1)*A(0,2)); b += (temp * temp); - temp = (A(0,1)*A(1,2)-A(1,1)*A(0,2)); b += (temp * temp); - temp = (A(0,0)*A(1,2)-A(1,0)*A(0,2)); b += (temp * temp); - temp = (A(0,0)*A(2,2)-A(2,0)*A(0,2)); b += (temp * temp); - temp = (A(1,0)*A(2,2)-A(2,0)*A(1,2)); b += (temp * temp); - temp = (A(1,0)*A(2,1)-A(2,0)*A(1,1)); b += (temp * temp); - temp = (A(0,0)*A(2,1)-A(2,0)*A(0,1)); b += (temp * temp); - temp = (A(0,0)*A(1,1)-A(1,0)*A(0,1)); b += (temp * temp); - b = -4 * b + 1; - } - return b; - } - - - // This method is based on the Matlab implementation. - // It computes the determinant of A matrix from an LU factorization with partial pivoting. - template - inline TReal compute_determinant_lu_partial(const matrix& A, TReal& d) - { - TReal dd; // Determinant, d is the sign of the determinant. - // ANSME: Do we really need to keep track of the sign, or can't we just check it at the end? - - matrix AA; - const TReal absA00 = math_utils::fabs(A(0,0)); - const TReal absA01 = math_utils::fabs(A(0,1)); - const TReal absA02 = math_utils::fabs(A(0,2)); - if (absA01 > absA02) - { - if (absA00 > absA01) - { - AA = A; - dd = 1; - } - else - { - AA(0,0) = A(0,1); - AA(1,0) = A(1,1); - AA(2,0) = A(2,1); - AA(0,1) = A(0,0); - AA(1,1) = A(1,0); - AA(2,1) = A(2,0); - AA(0,2) = A(0,2); - AA(1,2) = A(1,2); - AA(2,2) = A(2,2); - dd = -1; - } - } - else - { - if (absA00 > absA02) - { - AA = A; - dd = 1; - } - else - { - AA(0,0) = A(0,2); - AA(1,0) = A(1,2); - AA(2,0) = A(2,2); - AA(0,1) = A(0,1); - AA(1,1) = A(1,1); - AA(2,1) = A(2,1); - AA(0,2) = A(0,0); - AA(1,2) = A(1,0); - AA(2,2) = A(2,0); - dd = -1; - } - } - - d = dd; - vector U; - U(0) = AA(0, 0); - if (U(0) < 0) - d = -d; - - const TReal m1 = AA(1,0) / AA(0,0); - const TReal m2 = AA(2,0) / AA(0,0); - const TReal AA00 = AA(1,1) - AA(0,1) * m1; - const TReal AA10 = AA(2,1) - AA(0,1) * m2; - const TReal AA01 = AA(1,2) - AA(0,2) * m1; - const TReal AA11 = AA(2,2) - AA(0,2) * m2; - - if (math_utils::fabs(AA00) < math_utils::fabs(AA01)) - { - U(1) = AA01; - U(2) = AA10 - AA00 * AA11 / AA01; - dd = -dd; - d = -d; - if (U(1) < 0) - d = -d; - if (U(2) < 0) - d = -d; - } - else if (AA00 == 0) - { - U(1) = 0; - U(2) = 0; - } - else - { - U(1) = AA00; - U(2) = AA11 - AA01 * AA10 / AA00; - if (U(1) < 0) - d = -d; - if (U(2) < 0) - d = -d; - } - - dd = dd * U(0) * U(1) * U(2); - if (d == 0) - d = 1; - - assert(((d < 0) && (dd < 0)) || (dd >= 0)); - assert((d == 1) || (d == -1)); - - return dd; - } - - - // Swap rows. - template - inline void swap_rows(matrix& m, const int row0, const int row1) - { - assert(row0 != row1); - std::swap(m(0,row0), m(0,row1)); - std::swap(m(1,row0), m(1,row1)); - std::swap(m(2,row0), m(2,row1)); - } - template - inline void swap_rows(matrix& m, const int row0, const int row1) - { - assert(row0 != row1); - std::swap(m(0,row0), m(0,row1)); - std::swap(m(1,row0), m(1,row1)); - std::swap(m(2,row0), m(2,row1)); - std::swap(m(3,row0), m(3,row1)); - } - - // Swap columns. - template - inline void swap_columns(matrix& m, const int column0, const int column1) - { - assert(column0 != column1); - std::swap(m(column0,0), m(column1,0)); - std::swap(m(column0,1), m(column1,1)); - std::swap(m(column0,2), m(column1,2)); - } - template - inline void swap_columns(matrix& m, const int column0, const int column1) - { - assert(column0 != column1); - std::swap(m(column0,0), m(column1,0)); - std::swap(m(column0,1), m(column1,1)); - std::swap(m(column0,2), m(column1,2)); - std::swap(m(column0,3), m(column1,3)); - } - - - // This method is based on the Matlab implementation. - // It computes an LDL^T factorization with diagonal pivoting, P^T Bs P = L D L^T. - // This method modifies the Bs matrix. - // Note: The method does not fill the whole L matrix, just the lower left part. - // The caller should assume: - // - L(i,i) == 1 - // - L(i,j) == 0 for i > j - template - inline void compute_ldlt_factorization_diagonal(matrix& L, vector& D, vector& p, matrix& Bs) - { - p(0) = 0; - p(1) = 1; - p(2) = 2; - p(3) = 3; - - // ANSME: Should we compare absolute values to pick which row to pivot? - // This whole code could be refactored and improved to be more coherent - // from one step to another. - - // First step. - { - int r = 3; - if (Bs(3,3) < Bs(2,2)) - r = 2; - if (Bs(r,r) < Bs(1,1)) - r = 1; - - if (Bs(r,r) > Bs(0,0)) - { - std::swap(p(0), p(r)); - swap_rows(Bs, 0, r); - swap_columns(Bs, 0, r); - } - - D(0) = Bs(0,0); - L(0,1) = Bs(0,1) / D(0); - L(0,2) = Bs(0,2) / D(0); - L(0,3) = Bs(0,3) / D(0); - - Bs(1,1) = Bs(1,1) - L(0,1) * Bs(1,0); - Bs(1,2) = Bs(1,2) - L(0,1) * Bs(2,0); - Bs(2,1) = Bs(1,2); - Bs(1,3) = Bs(1,3) - L(0,1) * Bs(3,0); - Bs(3,1) = Bs(1,3); - - Bs(2,2) = Bs(2,2) - L(0,2) * Bs(2,0); - Bs(2,3) = Bs(2,3) - L(0,2) * Bs(3,0); - Bs(3,2) = Bs(2,3); - - Bs(3,3) = Bs(3,3) - L(0,3) * Bs(3,0); - } - - // Second step. - { - int r = 3; - if (Bs(3,3) < Bs(2,2)) - r = 2; - - if (Bs(r,r) > Bs(1,1)) - { - std::swap(p(1), p(r)); - swap_rows(Bs, 1, r); - swap_columns(Bs, 1, r); - #if 0 - swap_rows(L, 1, r); - swap_columns(L, 1, r); - #else - // Here, only the first column has been written, so we can swap just that. - // swap(1,2) swap(1,3) - // | 1 0 0 0 | | 1 0 0 0 | | 1 0 0 0 | - // | a 1 0 0 | | b 1 0 0 | | c 1 0 0 | - // | b 0 1 0 | | a 0 1 0 | | b 0 1 0 | - // | c 0 0 1 | | c 0 0 1 | | a 0 0 1 | - std::swap(L(0,1), L(0,r)); - #endif - } - - D(1) = Bs(1,1); - L(1,2) = Bs(1,2) / D(1); - L(1,3) = Bs(1,3) / D(1); - - Bs(2,2) = Bs(2,2) - L(1,2) * Bs(2,1); - Bs(2,3) = Bs(2,3) - L(1,2) * Bs(3,1); - Bs(3,2) = Bs(2,3); - - Bs(3,3) = Bs(3,3) - L(1,3) * Bs(3,1); - } - - // Third step. - { - if (Bs(2,2) < Bs(3,3)) - { - D(2) = Bs(3,3); - std::swap(p(2), p(3)); - swap_rows(Bs, 2, 3); - swap_columns(Bs, 2, 3); - #if 0 - swap_rows(L, 2, 3); - swap_columns(L, 2, 3); - #else - // Here, only the first two columns has been written, so we can swap just that. - // swap(2,3) - // | 1 0 0 0 | | 1 0 0 0 | - // | a 1 0 0 | | a 1 0 0 | - // | b e 1 0 | | c f 1 0 | - // | c f 0 1 | | b e 0 1 | - std::swap(L(0,2), L(0,3)); - std::swap(L(1,2), L(1,3)); - #endif - } - else - { - D(2) = Bs(2,2); - } - - L(2,3) = Bs(2,3) / D(2); - } - } - - - // This method is based on the Matlab implementation. - // It computes the determinant of A matrix from an LU factorization with complete pivoting. - template - inline TReal compute_determinant_lu_complete(const matrix& A, TReal& d, TReal& u22) - { - TReal dd; // Determinant, d is the sign of the determinant. - // ANSME: Do we really need to keep track of the sign, or can't we just check it at the end? - - matrix AA = A; - { - int r = 0; - int c = 0; - dd = 1; - if (math_utils::fabs(A(0,1)) > math_utils::fabs(A(0,0))) - { - r = 1; - } - if (math_utils::fabs(A(0,2)) > math_utils::fabs(A(c,r))) - { - r = 2; - } - if (math_utils::fabs(A(1,0)) > math_utils::fabs(A(c,r))) - { - r = 0; - c = 1; - } - if (math_utils::fabs(A(1,1)) > math_utils::fabs(A(c,r))) - { - r = 1; - c = 1; - } - if (math_utils::fabs(A(1,2)) > math_utils::fabs(A(c,r))) - { - r = 2; - c = 1; - } - if (math_utils::fabs(A(2,0)) > math_utils::fabs(A(c,r))) - { - r = 0; - c = 2; - } - if (math_utils::fabs(A(2,1)) > math_utils::fabs(A(c,r))) - { - r = 1; - c = 2; - } - if (math_utils::fabs(A(2,2)) > math_utils::fabs(A(c,r))) - { - r = 2; - c = 2; - } - - if (r > 0) - { - swap_rows(AA, 0, r); - dd = -1; - } - if (c > 0) - { - swap_columns(AA, 0, c); - dd = -dd; - } - } - - vector U; - U(0) = AA(0,0); - - matrix< TReal, 2, 2> AA_; - // In case the whole matrix is 0, we add a small value. - const TReal m1 = AA(1,0) / (AA(0,0) + (std::numeric_limits::min)()); - const TReal m2 = AA(2,0) / (AA(0,0) + (std::numeric_limits::min)()); - AA_(0,0) = AA(1,1) - AA(0,1) * m1; - AA_(1,0) = AA(2,1) - AA(0,1) * m2; - AA_(0,1) = AA(1,2) - AA(0,2) * m1; - AA_(1,1) = AA(2,2) - AA(0,2) * m2; - - { - int r = 0; - int c = 0; - if (math_utils::fabs(AA_(0,1)) > math_utils::fabs(AA_(0,0))) - { - r = 1; - } - if (math_utils::fabs(AA_(1,0)) > math_utils::fabs(AA_(c,r))) - { - r = 0; - c = 1; - } - if (math_utils::fabs(AA_(1,1)) > math_utils::fabs(AA_(c,r))) - { - r = 1; - c = 1; - } - - if (r > 0) - { - dd = -dd; - } - if (c > 0) - { - dd = -dd; - } - U(1) = AA_(c,r); - if (U(1) == 0) - { - U(2) = 0; - } - else - { - U(2) = AA_(1-c,1-r) - AA_(1-c,r) * AA_(c,1-r) / U(1); - } - } - - d = dd; - dd = dd * U(0) * U(1) * U(2); - if (U(0) < 0) - d = -d; - if (U(1) < 0) - d = -d; - if (U(2) < 0) - d = -d; - - u22 = U(1); - - assert(((d < 0) && (dd < 0)) || (dd >= 0)); - assert((d == 1) || (d == -1)); - - return dd; - } - - - // This method is based on the Matlab implementation. - // It computes an LDL^T factorization by block LDL^T factorization with Bunch-Parlett pivoting. - // This method modifies the Bs matrix. - // Note: The method does not fill the whole L matrix, just the lower left part. - // The caller should assume: - // - L(i,i) == 1 - // - L(i,j) == 0 for i > j - // - L(2,3) == 0 - // FIXME: Not sure we actually need a 4x4 matrix for D. - template - inline void compute_ldlt_factorization_bunch_parlett(matrix& L, matrix& D, vector& p, matrix& Bs) - { - p(0) = 0; - p(1) = 1; - p(2) = 2; - p(3) = 3; - - // ANSME: Should we compare absolute values to pick which row to pivot? - // This whole code could be refactored and improved to be more coherent - // from one step to another. - - // First step. - { - int r = 3; - if (Bs(3,3) < Bs(2,2)) - r = 2; - if (Bs(r,r) < Bs(1,1)) - r = 1; - - if (Bs(r,r) > Bs(0,0)) - { - std::swap(p(0), p(r)); - swap_rows(Bs, 0, r); - swap_columns(Bs, 0, r); - } - - D(0,0) = Bs(0,0); - L(0,1) = Bs(0,1) / D(0,0); - L(0,2) = Bs(0,2) / D(0,0); - L(0,3) = Bs(0,3) / D(0,0); - - Bs(1,1) = Bs(1,1) - L(0,1) * Bs(1,0); - Bs(1,2) = Bs(1,2) - L(0,1) * Bs(2,0); - Bs(2,1) = Bs(1,2); - Bs(1,3) = Bs(1,3) - L(0,1) * Bs(3,0); - Bs(3,1) = Bs(1,3); - - Bs(2,2) = Bs(2,2) - L(0,2) * Bs(2,0); - Bs(2,3) = Bs(2,3) - L(0,2) * Bs(3,0); - Bs(3,2) = Bs(2,3); - - Bs(3,3) = Bs(3,3) - L(0,3) * Bs(3,0); - } - - // Second step. - { - int r = 2; - if (Bs(2,2) < Bs(1,1)) - r = 1; - - if (Bs(r,r) > Bs(0,0)) - { - std::swap(p(1), p(r)); - swap_rows(Bs, 1, r); - swap_columns(Bs, 1, r); - #if 0 - swap_rows(L, 1, r); - swap_columns(L, 1, r); - #else - // Here, only the first column has been written, so we can swap just that. - // swap(1,2) swap(1,3) - // | 1 0 0 0 | | 1 0 0 0 | | 1 0 0 0 | - // | a 1 0 0 | | b 1 0 0 | | c 1 0 0 | - // | b 0 1 0 | | a 0 1 0 | | b 0 1 0 | - // | c 0 0 1 | | c 0 0 1 | | a 0 0 1 | - std::swap(L(0,1), L(0,r)); - #endif - } - - D(1,1) = Bs(1,1); - L(1,2) = Bs(1,2) / D(1,1); - L(1,3) = Bs(1,3) / D(1,1); - - D(2,2) = Bs(2,2) - L(1,2) * Bs(2,1); - D(2,3) = Bs(2,3) - L(1,2) * Bs(3,1); - D(3,2) = D(2,3); - - D(3,3) = Bs(3,3) - L(1,3) * Bs(3,1); - } - } - - - // This computes the null space of the matrix, knowing that it is symmetric: - // - // | a b | - // | b c | - template - inline vector compute_null_space(const TReal a, const TReal b, const TReal c) - { - // We assume that the matrix determinant is 0. - assert(0 == a * c - b * b); - - vector nullSpace; - if (a != 0) - { - // Transform: R1 = R1 / a - // - // | 1 b / a | - // | b c | - // - // R2 = R2 - b R1 - // - // | 1 b / a | - // | 0 c - b * b / a | - // - // Because a != 0 and a * c - b * b == 0, we have - // 1 * x1 + b / a * x2 = 0 - // a x1 + b x2 = 0 - // - // solution: [ b -a ]^T - nullSpace(0) = b; - nullSpace(1) = -a; - } - else - { - // Since a == 0 and the determinant is null we have: - // - // 0 * c - b * b = 0 - // b = 0 - // - // If b == 0 - // - // We have: - // - // | 0 0 | - // | b c | - // - // b x1 + c x2 = 0 - // solution: [ c -b ]^T - // - // (With b == 0, [ c 0 ]^T). - // - assert(a == 0); - assert(b == 0); - assert(c != 0); - nullSpace(0) = c; - nullSpace(1) = -b; - } - - return nullSpace; - } - - - // These methods are used for optimized operations in reverse iteration with LDL^T. - template - inline vector multiply_il_v( - const TReal IL01, const TReal IL02, const TReal IL03, const TReal IL12, const TReal IL13, - const vector& v - ) - { - // {{1,0,0,0},{m_12,1,0,0},{m_13,m_23,1,0},{m_14,m_24,0,1}} * {{v_1},{v_2},{v_3},{v_4}} - // - // | 1 0 0 0 | | v1 | | v1 | - // | IL01 1 0 0 | * | v2 | = | v1 * IL01 + v | - // | IL02 IL12 1 0 | | v3 | | v1 * IL02 + v2 * IL12 + v3 | - // | IL03 IL13 0 1 | | v4 | | v1 * IL03 + v2 * IL13 + v4 | - vector result; - result(0) = v(0); - result(1) = v(0) * IL01 + v(1); - result(2) = v(0) * IL02 + v(1) * IL12 + v(2); - result(3) = v(0) * IL03 + v(1) * IL13 + v(3); - return result; - } - - template - inline vector multiply_id_v( - const TReal ID00, const TReal ID11, const matrix& ID, - const vector& v - ) - { - // {{a_11,0,0,0},{0,a_22,0,0},{0,0,b_11,b_21},{0,0,b_12,b_22}} * {{v_1},{v_2},{v_3},{v_4}} - // - // | ID00 0 0 0 | | v1 | | v1 * ID00 | - // | 0000 ID11 0 0 | * | v2 | = | v2 * ID11 | - // | 0 0 ID(0,0) ID(1,0) | | v3 | | v3 * ID(0,0) + v4 * ID(1,0) | - // | 0 0 ID(0,1) ID(1,1) | | v4 | | v3 * ID(0,1) + v4 * ID(1,1) | - vector result; - result(0) = v(0) * ID00; - result(1) = v(1) * ID11; - result(2) = v(2) * ID(0,0) + v(3) * ID(1,0); - result(3) = v(2) * ID(0,1) + v(3) * ID(1,1); - return result; - } - - template - inline vector multiply_v_il( - const vector& v, - const TReal IL01, const TReal IL02, const TReal IL03, const TReal IL12, const TReal IL13 - ) - { - // Transpose[{{1,0,0,0},{m_12,1,0,0},{m_13,m_23,1,0},{m_14,m_24,0,1}}] * {{v_1},{v_2},{v_3},{v_4}} - // {v_1,v_2,v_3,v_4} * {{1,0,0,0},{m_12,1,0,0},{m_13,m_23,1,0},{m_14,m_24,0,1}} - // - // | v1 |^T * | 1 0 0 0 | | v1 + v2 * IL01 + v3 * IL02 + v4 * IL03 |^T - // | v2 | | IL01 1 0 0 | = | v2 + v3 * IL12 + v4 * IL13 | - // | v3 | | IL02 IL12 1 0 | | v3 | - // | v4 | | IL03 IL13 0 1 | | v4 | - vector result; - result(0) = v(0) + v(1) * IL01 + v(2) * IL02 + v(3) * IL03; - result(1) = v(1) + v(2) * IL12 + v(3) * IL13; - result(2) = v(2); - result(3) = v(3); - return result; - } - - template - inline vector multiply_minus_v_d( - const vector& v, - const matrix& D - ) - { - // -{v1,v2,v3,v4} * {{D_11,0,0,0},{0,D_22,0,0},{0,0,D_33,D_43},{0,0,D_34,D_44}} - // - // - | v1 |^T * | D(0,0) 0 0 0 | | -v1 * D(0,0) |^T - // | v2 | | 0 D(1,1) 0 0 | = | -v2 * D(1,1) | - // | v3 | | 0 0 D(2,2) D(3,2) | | -v3 * D(2,2) - v4 * D(2,3) | - // | v4 | | 0 0 D(2,3) D(3,3) | | -v4 * D(3,2) - v4 * D(3,3) | - vector result; - result(0) = -v(0) * D(0,0); - result(1) = -v(1) * D(1,1); - result(2) = -v(2) * D(2,2) - v(3) * D(2,3); - result(3) = -v(2) * D(3,2) - v(3) * D(3,3); - return result; - } - - - // Orthonormalization of matrices of the type: - // - // | v00 v10 | - // | v01 v11 | - // | 1 0 | - // | 0 1 | - template - inline void orthonormalize_v_with_qr( - vector& v0, - vector& v1, - const TReal v00, const TReal v10, const TReal v01, const TReal v11 - ) - { - // The factorization was obtained symbolically by WolframAlpha - // by running the following query: - // - // QRDecomposition[{{v_11,v_21},{v_12,v_22},{1,0},{0,1}}] - v0(0) = v00; - v0(1) = v01; - v0(2) = 1; - v0(3) = 0; - normalize(v0); - - // OPTME: We could reuse some of the multiplications. - v1(0) = v10 + v01 * v01 * v10 - v00 * v01 * v11; - v1(1) = v11 - v00 * v01 * v10 + v00 * v00 * v11; - v1(2) = -v00 * v10 - v01 * v11; - v1(3) = v00 * v00 + v01 * v01 + 1; - normalize(v1); - } - - - template - inline void orthonormalize_v_with_qr( - vector& v0, - vector& v1 - ) - { - // The factorization was obtained symbolically by WolframAlpha - // by running the following query: - // - // QRDecomposition[{{a,e},{b,f},{c,g},{d,h}}] - - normalize(v0); - - // To avoid numerical stability issues when multiplying too big values in the solution, - // we scale down the second vector. - TReal factor = v1(0); - if (factor < math_utils::fabs(v1(1))) - factor = math_utils::fabs(v1(1)); - if (factor < math_utils::fabs(v1(2))) - factor = math_utils::fabs(v1(2)); - if (factor < math_utils::fabs(v1(3))) - factor = math_utils::fabs(v1(3)); - factor = 1 / (factor + (std::numeric_limits::min)()); - - const TReal a = v0(0); - const TReal b = v0(1); - const TReal c = v0(2); - const TReal d = v0(3); - const TReal e = v1(0) * factor; - const TReal f = v1(1) * factor; - const TReal g = v1(2) * factor; - const TReal h = v1(3) * factor; - - // OPTME: We could reuse some of the multiplications. - // ANSME: Is there a more numerically stable way to do this? - v1(0) = b*b*e + c*c*e + d*d*e - a*b*f - a*c*g - a*d*h; - v1(1) = -a*b*e + a*a*f + c*c*f + d*d*f - b*c*g - b*d*h; - v1(2) = -a*c*e - b*c*f + a*a*g + b*b*g + d*d*g - c*d*h; - v1(3) = -a*d*e - b*d*f - c*d*g + a*a*h + b*b*h + c*c*h; - normalize(v1); - } - - - }; // End of namespace detail. - - - - - namespace detail - { - - - // "Hard-coded" constants used by this algorithm. - // - // For the time being, they are same whether single or double precision - // is used. - template - struct constants - { - // Tolerance for determinant of matrix B. - static inline TReal get_tau2(); - // Tolerance for third of determinant of matrix B. - static inline TReal get_tau1(); - // Tolerance for Newton iterations. - static inline TReal get_newton_tolerance(); - // Threshold for sub-space iterations. - static inline TReal get_subspace_threshold(); - }; - - template - inline TReal constants::get_tau2() - { - return static_cast(1.0e-4); - } - - template - inline TReal constants::get_tau1() - { - return static_cast(1.0e-4); - } - - template - inline TReal constants::get_newton_tolerance() - { - // The paper mentions 10^-15, but the Matlab implementation uses 10^-12. - return static_cast(1.0e-12); - } - - template - inline TReal constants::get_subspace_threshold() - { - // This constant is used in the test: - // if log10 |u22| > -7.18 - // This implies that u22 > 10^-7.18 ~= 6.607e-8 - // However, this constant was determined using the machine error on - // floating-point representation, so it should probably be different - // between the single and double precision versions. - return static_cast(6.607e-8); - } - - - }; // End of namespace detail. - - - - - // Implementation of 3x3 polar decomposition based on: - // - // "An algorithm to compute the polar decomposition of a 3x3 matrix" - // Nicholas J Higham and Vanni Noferini - // July 2015 - // - // The paper is available at: - // http://eprints.ma.man.ac.uk/2352/01/covered/MIMS_ep2015_66.pdf - // - // It is now available at: - // http://link.springer.com/article/10.1007%2Fs11075-016-0098-7 - // - // This C++ implementation is also extensively based on the Matlab - // implementation of this algorithm available at: - // https://github.com/higham/polar-decomp-3by3 - // - // It is worth noting that Matlab uses a (row,column) matrix indexing, - // but this implementation uses (column,row) indexing. Also, Matlab is - // 1-index based, but this implementation is 0-index based. - // - // This implementation has very specific goals that drive implementation - // choices: - // - It tries to avoid dependencies to third-party libraries. Therefore, - // it reimplements basic operations (matrix operations such as multiplication, - // transposition, etc.). These are straight-forward to implement and - // are kept separate to the actual algorithm so that using an actual - // linear algebra library would make the implementation more straight-forward. - // - It tries to be as efficient as possible. Therefore it potentially combines - // multiple operations into one (for instance, combine matrix transposition - // with multiplication) to minimize runtime cost. While this might reduce - // code simplicity, we favor runtime efficiency while trying to make those - // optimizations as easy to read as possible. - // - // It is also worth noting that this implementation relies on two major sources: - // - The algorithm as described in the paper - // - The algorithm as implemented in the Matlab implementation. - // - // This implementation tries to highlight its references to both the paper and - // the source code. - // - // The algorithms described in the paper are referred to using ### comments. - // For instance, specific lines such as the beginning of algorithm 3.5 will - // be highlighted: - // - // ### Algorithm 3.5 - // - // So that references to the paper are obvious. References to the Matlab - // implementation of the algorithm will be more textual. - namespace detail - { - - - template - inline TReal run_algorithm_3_3(const TReal absDetA, const TReal detB); - - template - inline TReal run_algorithm_3_4(const TReal absDetA, const TReal detB); - - // Implementation of algorithm 3.2. - template - inline void run_algorithm_3_2( - vector& v, - vector& p, - const matrix& A, - const matrix& B, - const TReal detB - ) - { - // ### 1. Form B in R4? from A via (2.5). - - // ### 2. Compute b = det B from an LU factorization with partial pivoting. - // Already done. - const TReal b = detB; - - // ### 3. Compute d = det A from an LU factorization with partial pivoting. - // Sign of the determinant. - TReal d; - // Determinant. - TReal dd = compute_determinant_lu_partial(A, d); - assert((d == 1) || (d == -1)); - - // ### 4. if d < 0, B = -B, d = -d, end - // We use the Bs matrix since we will need it anyways. Bs matrix is formed from - // minus B matrix. - matrix Bs = B; - multiply(Bs, -d); - dd *= d; - - // ### 5. Estimate lambda1, a dominant eigenvalue of B, via Algorithm 3.3. - const TReal lambda1 = run_algorithm_3_3(dd, b); - - // ### 6. Bs = lambda1 I - B - // Bs already holds -B. - Bs(0,0) += lambda1; - Bs(1,1) += lambda1; - Bs(2,2) += lambda1; - Bs(3,3) += lambda1; - - // ### 7. Compute an LDLT factorization with diagonal pivoting, P^T Bs P = L D L^T. - matrix L; - vector D; - compute_ldlt_factorization_diagonal(L, D, p, Bs); - - // ### 8. v = PL^-T e4 / ||L^-T e4||2 - // Normalization will be done in the common part. - v(0) = L(0,1) * L(1,3) + L(0,2) * L(2,3) - L(0,1) * L(2,3) * L(1,2) - L(0,3); - v(1) = L(2,3) * L(1,2) - L(1,3); - v(2) = -L(2,3); - v(3) = 1; - - // ### 9. Form the matrix Q using (2.7). - // ### 10. Compute the upper triangle of H = Q^T A and set the lower triangle equal to - // the upper triangle. - // Both are done at the end of algorithm 3.5, along with v normalization. - } - - - // Implementation of algorithm 3.3. - template - inline TReal run_algorithm_3_3( - const TReal absDetA, - const TReal detB - ) - { - TReal lambda1; - - const TReal& dd = absDetA; - const TReal& b = detB; - - // ### 1. tau1 = 10^4 % Tolerance. - static const TReal kTau1 = constants::get_tau1(); - - // ### 2. if b + 1/3 > 1 - if (b > kTau1 - 1 / static_cast(3)) - { - // ### 3. c = 8d - const TReal c = 8 * dd; - // ### 4. delta0 = 1 + 3b - const TReal delta0 = 1 + 3 * b; - // ### 5. delta1 = -1 + (27/16)c^2 + 9b - const TReal delta1 = -1 + (27 / static_cast(16)) * c * c + 9 * b; - // ### 6. phi = delta1/delta0^(3/2) - TReal phi = delta1 / (delta0 * math_utils::sqrt(delta0)); - // This was not in the original algorithm, but clamp to [-1,1] in case of rounding errors. - phi = math_utils::clamp(phi, -1, 1); - // ### 7. z = (4/3)(1 + delta0^(1/2)cos(arccos(alpha)/3)) - const TReal z = (4 / static_cast(3)) * (1 + math_utils::sqrt(delta0) * math_utils::cos(math_utils::acos(phi) / 3)); - // ### 8. s = z^0.5/2 - const TReal s = math_utils::sqrt(z) / 2; - // ### 9. lambda1 = s + (max(0, 4 - z + c/s))^(1/2)/2. - lambda1 = s; - const TReal temp = 4 - z + c / s; - if (temp > 0) - lambda1 += math_utils::sqrt(temp) / 2; - } - // ### 10. else - else - { - // ### 11. Use Newton's method (Algorithm 3.4) to approximate - lambda1 = run_algorithm_3_4(dd, b); - } - // ### 12. end - - return lambda1; - } - - - // Implementation of algorithm 3.4. - template - inline TReal run_algorithm_3_4( - const TReal absDetA, - const TReal detB - ) - { - const TReal& dd = absDetA; - const TReal& b = detB; - - // ### 1. x = sqrt(3) - TReal x = math_utils::sqrt(3); - // ### 2. xold = 3 - TReal xold = 3; - // ### 3. while xold - x > 10^-15 - static const TReal kNewtonTolerance = constants::get_newton_tolerance(); - while (xold - x > kNewtonTolerance) - { - // ### 4. xold = x - xold = x; - // ### 5. Evaluate p = p(x) = det(xI - B) by Horner's method. - const TReal c = 8 * dd; - const TReal px = x * (x * (x * x - 2) - c) + b; - // ### 6. Evaluate pd = p0(x) by Horner's method. - const TReal dpx = x * (4 * x * x - 4) - c; - // ### 7. x = x - p/pd - x = x - px / dpx; - } - // ### 8. end - const TReal lambda1 = x; - - return lambda1; - } - - - // Implementation of algorithm 3.5. - template - inline void run_algorithm_3_5( - matrix& paramQ, - matrix& paramH, - const matrix& paramA - ) - { - // First make sure the input matrix is normalized. - matrix A = paramA; - normalize(A); - - // ### Algorithm 3.5 - - // ### 1. tau2 = 10^-4 % Tolerance. - static const TReal kTau2 = constants::get_tau2(); - - // ### 2. Form B in R4? from A via (2.5). - matrix B; - // Computation of the matrix as described in the paper's formula. - // Note: In the author's Matlab implementation, the computation - // is slightly different. It first computes the trace as the - // sum of the diagonal and then computes the diagonal elements - // of B derived from this. This can create numerical differences - // for which the algorithm should be tolerant, but depending on - // whether single or double precision is used, different paths - // in the algorithm might be used. However, they should all yield - // satisfactory solutions as the algorithm should be numerically stable - // in both cases. - // - // OPTME: B matrix is symmetric, we could store it in a different way. - B(0,0) = A(0,0) + A(1,1) + A(2,2); - B(1,1) = A(0,0) - A(1,1) - A(2,2); - B(2,2) = A(1,1) - A(0,0) - A(2,2); - B(3,3) = A(2,2) - A(0,0) - A(1,1); - B(0,1) = B(1,0) = A(2,1) - A(1,2); - B(1,2) = B(2,1) = A(1,0) + A(0,1); - B(2,3) = B(3,2) = A(2,1) + A(1,2); - B(0,2) = B(2,0) = A(0,2) - A(2,0); - B(1,3) = B(3,1) = A(2,0) + A(0,2); - B(0,3) = B(3,0) = A(1,0) - A(0,1); - - // ### 3. Compute b = det B from an LU factorization with partial pivoting. - // It actually computes B determinant from A matrix is B is a function of A. - const TReal b = compute_b_determinant_from_a_matrix_lu_partial(A); - - vector v; - vector p; - - // ### 4. if b < 1 - tau2 - if (b < 1 - kTau2) - { - // ### % Dominant eigenvalue of B is well separated. - // ### 5. Call Algorithm 3.2. - run_algorithm_3_2(v, p, A, B, b); - } - // ### 6. else - else - { - // ### 7. Compute d = detA using an LU factorization with complete pivoting. - // Also keep the second U value of the factorization. - TReal u22; - // Sign of the determinant. - TReal d; - // Determinant. - TReal dd = compute_determinant_lu_complete(A, d, u22); - assert((d == 1) || (d == -1)); - - // ### 8. If d < 0, B = -B, end - // We use the Bs matrix since we will need it anyways. Bs matrix is formed from - // minus B matrix. - matrix Bs = B; - multiply(Bs, -d); - dd *= d; - - // ### 9. Estimate lambda1 using Algorithm 3.3. - const TReal lambda1 = run_algorithm_3_3(dd, b); - - // ### 10. Bs = lambda1 I - B - // Bs already holds -B. - Bs(0, 0) += lambda1; - Bs(1, 1) += lambda1; - Bs(2, 2) += lambda1; - Bs(3, 3) += lambda1; - - // Compute Bs = LDL^T by block LDL^T factorization with Bunch-Parlett pivoting - // This will be used in both following cases. - matrix L; - matrix D; - compute_ldlt_factorization_bunch_parlett(L, D, p, Bs); - assert(D(2,3) == D(3,2)); - - const TReal DD = D(2,2) * D(3,3) - D(3,2) * D(3,2); - if (DD == 0) - { - // Treat this case specially. It is not really mentioned in the paper's algorithm, - // but it is part of the Matlab implementation. - const bool allZero = (D(2,2) == 0) && (D(3,3) == 0) && (D(3,2) == 0); - if (allZero) - { - // This is the equivalent of choosing a null space of (0,1) and do - // the same calculation as the other case. - v(0) = L(0,1) * L(1,3) - L(0,3); - v(1) = -L(1,3); - v(2) = 0; - v(3) = 1; - } - else - { - // ANSME: A more robust way might be to get into this case for determinant close to 0, - // instead of exactly equal to zero, and then use something more robust such as taking - // the vector associated with the smallest singular value (0 if there is actually a - // null space), which could probably be computed efficiently for 2x2 matrices. - const vector nullSpace = compute_null_space(D(2,2), D(2,3), D(3,3)); - - // Since L is diagonal, we can solve v = L^-T * [0 0 a b]^T. - // We also know, from compute_ldlt_factorization_bunch_parlett, that L(2,3) is 0. - // So v can be computed symbolically by WolframAlpha by running the following query: - // Inverse[Transpose[{{1,0,0,0},{l_12,1,0,0},{l_13,l_23,1,0},{l_14,l_24,0,1}}]] * {{0},{0},{a},{b}} - // - // We have L^-T = | 1 -L(0,1) L(0,1)*L(1,2) - L(0,2) -L(0,3) + L(0,2)*L(2,3) + L(0,1)*(L(1,3) - L(1,2)*L(2,3)) | - // | 0 1 -L(1,2) L(1,2)*L(2,3) - L(1,3) | - // | 0 0 1 -L(2,3) | - // | 0 0 0 1 | - // - // Using L(2,3) == 0 we get: - // - // We have L^-T = | 1 -L(0,1) L(0,1)*L(1,2) - L(0,2) L(0,1)*L(1,3) -L(0,3) | - // | 0 1 -L(1,2) -L(1,3) | - // | 0 0 1 0 | - // | 0 0 0 1 | - // - // Multiplied by [0 0 a b]^T: - // - // | a * (L(0,1)*L(1,2) - L(0,2)) + b * (L(0,1)*L(1,3) - L(0,3)) | - // | -a * L(1,2) - b * L(1,3) | - // | a | - // | b | - - v(0) = nullSpace(0) * (L(0,1) * L(1,2) - L(0,2)) + nullSpace(1) * (L(0,1) * L(1,3) - L(0,3)); - v(1) = -nullSpace(0) * L(1,2) - nullSpace(1) * L(1,3); - v(2) = nullSpace(0); - v(3) = nullSpace(1); - } - } - else - { - // Compute inverse of L. - // See above for explanation of L^-1 computation (and assumptions about which values are 0 and 1). - const TReal IL01 = -L(0,1); - const TReal IL02 = L(0,1) * L(1,2) - L(0,2); - const TReal IL12 = -L(1,2); - const TReal IL03 = L(0,1) * L(1,3) - L(0,3); - const TReal IL13 = -L(1,3); - - // Compute inverse of D. - // Inverse[{{d_11,0,0,0},{0,d_22,0,0},{0,0,d_33,d_43},{0,0,d_43,d_44}}] - const TReal ID00 = 1 / D(0,0); - const TReal ID11 = 1 / D(1,1); - matrix ID; - ID(0,0) = D(3,3); - ID(0,1) = -D(3,2); - ID(1,0) = -D(3,2); - ID(1,1) = D(2,2); - multiply(ID, 1 / DD); - - // ### 11. if log10 |u22| > -7.18 - // This implies that u22 > 10^-7.18 ~= 6.607e-8 - static const TReal kSubspaceThreshold = constants::get_subspace_threshold(); - const TReal AU = math_utils::fabs(u22); - if (AU > kSubspaceThreshold) - { - // ### 12. nit = ceil(15/(16.86 + 2 log10 |u22|)) - const int nit = static_cast( - math_utils::ceil(15 / (static_cast(16.8) + 2 * math_utils::log10(AU))) - ); - - // ### 13. Compute Bs = LDL^T by block LDL^T factorization with Bunch-Parlett pivoting - // Already done. - - // ### 14. v = L^-T e4 / ||L^-T e4|| % Initial guess. - // - // | 1 IL01 IL02 IL03 | | 0 | | IL03 | - // | 0 1 IL12 IL13 | * | 0 | = | IL13 | - // | 0 0 1 0 | | 0 | | 0 | - // | 0 0 0 1 | | 1 | | 1 | - v(0) = IL03; - v(1) = IL13; - v(2) = 0; - v(3) = 1; - // Normalization will happen in the loop. - - // ### 15. for i = 1 : nit - for (int i = 0; i < nit; ++i) - { - normalize(v); - - // ### 16. Update v using one step of inverse iteration with LDL^T - // OPTME: Maybe some of these operations could be combined to be optimized...? - - // v = L^-1 * v = IL * v; - v = multiply_il_v(IL01, IL02, IL03, IL12, IL13, v); - - // v = D^-1 = ID * v; - v = multiply_id_v(ID00, ID11, ID, v); - - // v = L^-T * v = IL^T * v = v * IL; - v = multiply_v_il(v, IL01, IL02, IL03, IL12, IL13); - } - // ### 17. end - // The last normalization of v will be done at the end. - } - // ### 18. else - else - { - // ### 19. Compute Bs = LDL^T by block LDL^T factorization with Bunch-Parlett pivoting - // Already done. - - // ### 20. V = L^-T [e3 e4] % Initial guess. - // - // | 1 IL01 IL02 IL03 | | 0 0 | | IL02 IL03 | - // | 0 1 IL12 IL13 | * | 0 0 | = | IL12 IL13 | - // | 0 0 1 0 | | 1 0 | | 1 0 | - // | 0 0 0 1 | | 0 1 | | 0 1 | - const TReal v00 = IL02; - const TReal v10 = IL03; - const TReal v01 = IL12; - const TReal v11 = IL13; - - // ### 21. for i = 1:2 - // ### 22. Orthonormalize V via QR factorization. - // ### 23. Update V using one step of inverse subspace iteration with LDL^T. - vector v0, v1; - orthonormalize_v_with_qr(v0, v1, v00, v10, v01, v11); - - for (int i = 0; i < 2; ++i) - { - v0 = multiply_il_v(IL01, IL02, IL03, IL12, IL13, v0); - v1 = multiply_il_v(IL01, IL02, IL03, IL12, IL13, v1); - - v0 = multiply_id_v(ID00, ID11, ID, v0); - v1 = multiply_id_v(ID00, ID11, ID, v1); - - v0 = multiply_v_il(v0, IL01, IL02, IL03, IL12, IL13); - v1 = multiply_v_il(v1, IL01, IL02, IL03, IL12, IL13); - } - // ### 24. end - - // ### 25. Orthonormalize V via QR factorization. - orthonormalize_v_with_qr(v0, v1); - - // ### 26. Bp = V^T Bs V in R2x2 - // ### 27. Find w, eigenvector of smallest eigenvalue of Bp, by analytic formula. - // ### 28. v = V w - // L has the same form as IL, so we can use the same function to multiply them. - const vector v0_temp = multiply_v_il(v0, L(0,1), L(0,2), L(0,3), L(1,2), L(1,3)); - const vector v1_temp = multiply_v_il(v1, L(0,1), L(0,2), L(0,3), L(1,2), L(1,3)); - const vector H0 = multiply_minus_v_d(v0_temp, D); - const vector H1 = multiply_minus_v_d(v1_temp, D); - const TReal H00 = dot(H0, v0_temp); - const TReal H10 = dot(H0, v1_temp); - const TReal H11 = dot(H1, v1_temp); - if (math_utils::fabs(H10) < static_cast(1.0e-15)) - { - if (H00 > H10) - v = v0; - else - v = v1; - } - else - { - const TReal r = (H00 - H11) / (2 * H10); - const int s = (H10 < 0 ? -1 : 1); - const TReal f = r + s * math_utils::sqrt(1 + r * r); - v(0) = v0(0) * f + v1(0); - v(1) = v0(1) * f + v1(1); - v(2) = v0(2) * f + v1(2); - v(3) = v0(3) * f + v1(3); - } - } - // ### 29. end - } - - // ### 30. Form the matrix Q from v as in Theorem 2.5. - // ### 31. Compute H = Q^T A. - // Both are done at the end of the function. - } - - // Compute rotation from dominant eigen vector v. - normalize(v); - vector vtemp = v; - v(p(0)) = vtemp(0); - v(p(1)) = vtemp(1); - v(p(2)) = vtemp(2); - v(p(3)) = vtemp(3); - - const TReal v12 = 2 * v(0) * v(1); - const TReal v13 = 2 * v(0) * v(2); - const TReal v14 = 2 * v(0) * v(3); - const TReal v22 = 2 * v(1) * v(1); - const TReal v23 = 2 * v(1) * v(2); - const TReal v24 = 2 * v(1) * v(3); - const TReal v33 = 2 * v(2) * v(2); - const TReal v34 = 2 * v(2) * v(3); - const TReal v44 = 2 * v(3) * v(3); - - paramQ(0,0) = 1 - (v33 + v44); - paramQ(0,1) = v23 - v14; - paramQ(0,2) = v24 + v13; - paramQ(1,0) = v23 + v14; - paramQ(1,1) = 1 - (v22 + v44); - paramQ(1,2) = v34 - v12; - paramQ(2,0) = v24 - v13; - paramQ(2,1) = v34 + v12; - paramQ(2,2) = 1 - (v22 + v33); - - // The Matlab implementation returns the opposite of the matrix if det A < 0. - // We don't do that because we want a right-handed rotation. - - // Compute scale. - transpose_multiply(paramH, paramQ, paramA); - - // The Matlab implementation suggests averaging the top and lower part of the - // matrix to ensure symmetry, but we don't do it. - } - - - }; // End of namespace detail. - - -}; // End of namespace polar. - - - - -#endif // __POLAR_DECOMPOSITION_3X3_IMPL_H__ diff --git a/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3_matrix.h b/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3_matrix.h deleted file mode 100644 index 14fa8c3bd3..0000000000 --- a/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3_matrix.h +++ /dev/null @@ -1,359 +0,0 @@ -// MIT License -// -// Copyright (c) 2017 Martin Bisson -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#ifndef __POLAR_DECOMPOSITION_3X3_MATRIX_H__ -#define __POLAR_DECOMPOSITION_3X3_MATRIX_H__ - - - - -#include -#include -#include - - -namespace polar -{ - - - namespace detail - { - - - // Helper methods for standard mathematical function calls. - template - struct math_utils - { - static inline TReal sqrt(const TReal x); - static inline TReal fabs(const TReal x); - static inline TReal clamp(const TReal x, const TReal a, const TReal b); - static inline TReal cos(const TReal x); - static inline TReal acos(const TReal x); - static inline TReal ceil(const TReal x); - static inline TReal log10(const TReal x); - }; - - template <> - inline float math_utils::sqrt(const float x) - { - return ::sqrtf(x); - } - template <> - inline double math_utils::sqrt(const double x) - { - return ::sqrt(x); - } - - template <> - inline float math_utils::fabs(const float x) - { - return ::fabs(x); - } - template <> - inline double math_utils::fabs(const double x) - { - return ::fabs(x); - } - - template - inline TReal math_utils::clamp(const TReal x, const TReal a, const TReal b) - { - assert(a <= b); - if (x < a) - return a; - else if (x > b) - return b; - else - return x; - } - - template <> - inline float math_utils::cos(const float x) - { - return ::cosf(x); - } - template <> - inline double math_utils::cos(const double x) - { - return ::cos(x); - } - - template <> - inline float math_utils::acos(const float x) - { - return ::acosf(x); - } - template <> - inline double math_utils::acos(const double x) - { - return ::acos(x); - } - - template <> - inline float math_utils::ceil(const float x) - { - return ::ceilf(x); - } - template <> - inline double math_utils::ceil(const double x) - { - return ::ceil(x); - } - - template <> - inline float math_utils::log10(const float x) - { - return ::log10f(x); - } - template <> - inline double math_utils::log10(const double x) - { - return ::log10(x); - } - - - }; // End of namespace detail. - - - - - // Implementation of the basic matrix type used in the algorithm. - namespace detail - { - - - // Definition of the column-major matrix type. - template - class matrix - { - public: - inline TReal operator()(int columnIndex, int rowIndex) const; - inline TReal& operator()(int columnIndex, int rowIndex); - inline TReal operator()(int index) const; - inline TReal& operator()(int index); - - private: - TReal data[NbColumns * NbRows]; - }; - - - // We rely on the compiler to be efficient in inlining the calls to this - // method, and especially optimize the double-indices access and convert them - // to single access ones. - template - inline TReal matrix::operator()(int columnIndex, int rowIndex) const - { - assert(0 <= rowIndex); - assert(rowIndex < NbRows); - assert(0 <= columnIndex); - assert(columnIndex < NbColumns); - return data[columnIndex * NbRows + rowIndex]; - } - - template - inline TReal& matrix::operator()(int columnIndex, int rowIndex) - { - assert(0 <= rowIndex); - assert(rowIndex < NbRows); - assert(0 <= columnIndex); - assert(columnIndex < NbColumns); - return data[columnIndex * NbRows + rowIndex]; - } - - template - inline TReal matrix::operator()(int index) const - { - assert(0 <= index); - assert(index < NbRows * NbColumns); - return data[index]; - } - - template - inline TReal& matrix::operator()(int index) - { - assert(0 <= index); - assert(index < NbRows * NbColumns); - return data[index]; - } - - - // Definition of the vector type. - template - class vector - { - public: - inline TReal operator()(int index) const; - inline TReal& operator()(int index); - - private: - TReal data[NbElements]; - }; - - - template - inline TReal vector::operator()(int index) const - { - assert(0 <= index); - assert(index < NbElements); - return data[index]; - } - - template - inline TReal& vector::operator()(int index) - { - assert(0 <= index); - assert(index < NbElements); - return data[index]; - } - - - // - // The following re-implements matrix operations that are very common in - // most linear algebra packages such as GLM or Eigen. However, by design - // this library does not want to depend on such packages, so the simple - // matrix operations are re-implemented here. - // - // Most methods are not implemented in the most generic way; they are - // specialized for the ways they are used in this algorithm specific - // implementation. - // - // They definitely could be implemented in a "cleaner", shorter way - // using the proper libraries. - // - - template - inline void normalize(matrix& m) - { - TReal length = m(0) * m(0); - length += m(1) * m(1); - length += m(2) * m(2); - length += m(3) * m(3); - length += m(4) * m(4); - length += m(5) * m(5); - length += m(6) * m(6); - length += m(7) * m(7); - length += m(8) * m(8); - - // Add small numerical value to make sure we are ok with 0-length. - const TReal factor = 1 / (math_utils::sqrt(length) + (std::numeric_limits::min)()); - - m(0) *= factor; - m(1) *= factor; - m(2) *= factor; - m(3) *= factor; - m(4) *= factor; - m(5) *= factor; - m(6) *= factor; - m(7) *= factor; - m(8) *= factor; - } - - template - inline void normalize(vector& m) - { - TReal length = m(0) * m(0); - length += m(1) * m(1); - length += m(2) * m(2); - length += m(3) * m(3); - - // Add small numerical value to make sure we are ok with 0-length. - const TReal factor = 1 / (math_utils::sqrt(length) + (std::numeric_limits::min)()); - - m(0) *= factor; - m(1) *= factor; - m(2) *= factor; - m(3) *= factor; - } - - - template - inline void transpose_multiply( - matrix& result, - const matrix& a, - const matrix& b - ) - { - // Perform transposition at the same time as the multiplication. - result(0,0) = a(0,0) * b(0,0) + a(0,1) * b(0,1) + a(0,2) * b(0,2); - result(0,1) = a(1,0) * b(0,0) + a(1,1) * b(0,1) + a(1,2) * b(0,2); - result(0,2) = a(2,0) * b(0,0) + a(2,1) * b(0,1) + a(2,2) * b(0,2); - result(1,0) = a(0,0) * b(1,0) + a(0,1) * b(1,1) + a(0,2) * b(1,2); - result(1,1) = a(1,0) * b(1,0) + a(1,1) * b(1,1) + a(1,2) * b(1,2); - result(1,2) = a(2,0) * b(1,0) + a(2,1) * b(1,1) + a(2,2) * b(1,2); - result(2,0) = a(0,0) * b(2,0) + a(0,1) * b(2,1) + a(0,2) * b(2,2); - result(2,1) = a(1,0) * b(2,0) + a(1,1) * b(2,1) + a(1,2) * b(2,2); - result(2,2) = a(2,0) * b(2,0) + a(2,1) * b(2,1) + a(2,2) * b(2,2); - } - - - template - inline void multiply( - matrix& result, - const TReal factor - ) - { - result(0) *= factor; - result(1) *= factor; - result(2) *= factor; - result(3) *= factor; - result(4) *= factor; - result(5) *= factor; - result(6) *= factor; - result(7) *= factor; - result(8) *= factor; - result(9) *= factor; - result(10) *= factor; - result(11) *= factor; - result(12) *= factor; - result(13) *= factor; - result(14) *= factor; - result(15) *= factor; - } - - template - inline void multiply( - matrix& result, - const TReal factor - ) - { - result(0) *= factor; - result(1) *= factor; - result(2) *= factor; - result(3) *= factor; - } - - - template - inline TReal dot(const vector& a, const vector& b) - { - return a(0)*b(0) + a(1)*b(1) + a(2)*b(2) + a(3)*b(3); - } - - - }; // End of namespace detail. - - -}; // End of namespace polar. - - - - -#endif // __POLAR_DECOMPOSITION_3X3_MATRIX_H__ diff --git a/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/solid_dynamics_supplementary.cpp b/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/solid_dynamics_supplementary.cpp deleted file mode 100644 index 617290b50e..0000000000 --- a/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/solid_dynamics_supplementary.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "all_solid_dynamics.h" -#include "solid_body.h" -#include "solid_particles.h" -#include "neighbor_relation.h" -#include "base_kernel.h" -#include "base_data_package.h" -#include "elastic_solid.h" -#include "external_force.h" -#include "mesh_cell_linked_list.h" -#include "fluid_particles.h" -#include "weakly_compressible_fluid.h" -#include "polar_decomposition_3x3.h" - -using namespace polar; -using namespace SimTK; - -namespace SPH -{ - namespace solid_dynamics - { - //=========================================================================================// - void UpdateElasticNormalDirection::Update(size_t index_i, Real dt) - { - Matd& F = F_[index_i]; - Mat3d R; - Real Q[9], H[9], A[9]; - for (int i = 0; i < 3; i++) - for (int j = 0; j < 3; j++) - A[i * 3 + j] = F(i, j); - - polar::polar_decomposition(Q, H, A); - //this decomposition has the form A = Q*H, where Q is orthogonal and H is symmetric positive semidefinite. - //Ref. "An algorithm to compute the polar decomposition of a 3*3 matrix, Nicholas J. Higham et al. Numer Algor(2016) " - for (int i = 0; i < 3; i++) - for (int j = 0; j < 3; j++) - R(i, j) = Q[i * 3 + j]; - n_[index_i] = R * n_0_[index_i]; - } - //=================================================================================================// - void ConstrainSolidBodyPartBySimBody::Update(size_t index_i, Real dt) - { - Vec3 rr, pos, vel, acc; - rr = pos_0_[index_i] - initial_mobod_origin_location_; - mobod_.findStationLocationVelocityAndAccelerationInGround(*simbody_state_, rr, pos, vel, acc); - /** this is how we calculate the particle position in after transform of MBbody. - * const SimTK::Rotation& R_GB = mobod_.getBodyRotation(simbody_state); - * const SimTK::Vec3& p_GB = mobod_.getBodyOriginLocation(simbody_state); - * const SimTK::Vec3 r = R_GB * rr; // re-express station vector p_BS in G (15 flops) - * base_particle_data_i.pos_n_ = (p_GB + r); - */ - pos_n_[index_i] = pos; - vel_n_[index_i] = vel; - dvel_dt_[index_i] = acc; - n_[index_i] = (mobod_.getBodyRotation(*simbody_state_) * n_0_[index_i]); - } - //=================================================================================================// - SimTK::SpatialVec TotalForceOnSolidBodyPartForSimBody - ::ReduceFunction(size_t index_i, Real dt) - { - Vec3 force_from_particle = force_from_fluid_[index_i] + contact_force_[index_i]; - Vec3 displacement(0); - displacement = pos_n_[index_i] - current_mobod_origin_location_; - Vec3 torque_from_particle = cross(displacement, force_from_particle); - - return SimTK::SpatialVec(torque_from_particle, force_from_particle); - } - //=================================================================================================// - } -} \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/particle_generator/CMakeLists.txt b/SPHINXsys/src/for_3D_build/particle_generator/CMakeLists.txt deleted file mode 100644 index fd5eae60fd..0000000000 --- a/SPHINXsys/src/for_3D_build/particle_generator/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/particle_generator/all_particle_generators.h b/SPHINXsys/src/for_3D_build/particle_generator/all_particle_generators.h deleted file mode 100644 index ada6310d3a..0000000000 --- a/SPHINXsys/src/for_3D_build/particle_generator/all_particle_generators.h +++ /dev/null @@ -1,9 +0,0 @@ - -#ifndef ALL_PARTICLE_GENERATORS_3D_H -#define ALL_PARTICLE_GENERATORS_3D_H - - - -#include "particle_generator_lattice.h" -#include "particle_generator_network.h" -#endif //ALL_PARTICLE_GENERATORS_3D_H \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_lattice_supplementary.cpp b/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_lattice_supplementary.cpp deleted file mode 100644 index 603cceecab..0000000000 --- a/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_lattice_supplementary.cpp +++ /dev/null @@ -1,33 +0,0 @@ -//common functions used by 3d buildings only - -#include "particle_generator_lattice.h" - -#include "geometry.h" -#include "base_mesh.h" -#include "base_body.h" -#include "base_particles.h" - -namespace SPH { - //=================================================================================================// - void ParticleGeneratorLattice::createBaseParticles(BaseParticles* base_particles) - { - std::unique_ptr mesh(new Mesh(domain_bounds_, lattice_spacing_, 0)); - size_t total_real_particles = 0; - Real particle_volume = lattice_spacing_ * lattice_spacing_ * lattice_spacing_; - Vecu number_of_lattices = mesh->NumberOfCells(); - for (size_t i = 0; i < number_of_lattices[0]; ++i) - for (size_t j = 0; j < number_of_lattices[1]; ++j) - for (size_t k = 0; k < number_of_lattices[2]; ++k) { - Vecd particle_position = mesh->CellPositionFromIndex(Vecu(i, j, k)); - if (body_shape_->checkNotFar(particle_position, lattice_spacing_)) - { - if (body_shape_->checkContain(particle_position)) - { - createABaseParticle(base_particles, particle_position, particle_volume, total_real_particles); - } - } - } - base_particles->total_real_particles_ = total_real_particles; - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.cpp b/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.cpp deleted file mode 100644 index f753c13088..0000000000 --- a/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.cpp +++ /dev/null @@ -1,239 +0,0 @@ -/** - * @file particle_generator_network.cpp - * @author Chi ZHang and Xiangyu Hu - */ -#include "sph_system.h" -#include "particle_generator_network.h" -#include "mesh_cell_linked_list.h" -#include "level_set.h" -#include "base_body.h" -#include "base_particles.h" -#include "in_output.h" - //=================================================================================================// -namespace SPH -{ - //=================================================================================================// - ParticleGeneratorNetwork:: - ParticleGeneratorNetwork(Vecd starting_pnt, Vecd second_pnt, int iterator, Real grad_factor) - : ParticleGenerator(), starting_pnt_(starting_pnt), second_pnt_(second_pnt), - n_it_(iterator), fascicles_(true), segments_in_branch_(10), segment_length_(0), - grad_factor_(grad_factor), body_shape_(nullptr), tree_(nullptr){} - //=================================================================================================// - void ParticleGeneratorNetwork::initialize(SPHBody* sph_body) - { - sph_body_ = sph_body; - mesh_cell_linked_list_ = dynamic_cast(sph_body)->mesh_cell_linked_list_; - segment_length_ = sph_body_->particle_adaptation_->ReferenceSpacing(); - body_shape_ = sph_body_->body_shape_; - tree_ = new GenerativeTree(sph_body_); - sph_body_->generative_structure_ = tree_; - Vecd displacement = second_pnt_ - starting_pnt_; - Vecd end_direction = displacement / (displacement.norm() + TinyReal); - //add particle to the first branch of the tree - tree_->growAParticleOnBranch(tree_->root_, starting_pnt_, end_direction); - mesh_cell_linked_list_->InsertACellLinkedListDataEntry(0, tree_->pos_n_[0]); - } - //=================================================================================================// - Vecd ParticleGeneratorNetwork::getGradientFromNearestPoints(Vecd pt, Real delta) - { - Vecd upgrad(0), downgrad(0); - Vecd shift(delta); - for (int i = 0; i < pt.size(); i++) { - Vecd upwind = pt; - Vecd downwind = pt; - upwind[i] -= shift[i]; - downwind[i] += shift[i]; - ListData up_nearest_list = mesh_cell_linked_list_->findNearestListDataEntry(upwind); - ListData down_nearest_list = mesh_cell_linked_list_->findNearestListDataEntry(downwind); - upgrad[i] = up_nearest_list.first != MaxSize_t ? (upwind - up_nearest_list.second).norm() / 2.0 * delta : 1.0; - downgrad[i] = down_nearest_list.first != MaxSize_t ? (downwind - down_nearest_list.second).norm() / 2.0 * delta : 1.0; - } - return downgrad - upgrad; - } - //=================================================================================================// - Vecd ParticleGeneratorNetwork::createATentativeNewBranchPoint(Vecd init_point, Vecd dir) - { - Vecd pnt_to_project = init_point + dir * segment_length_; - - Real phi = body_shape_->findSignedDistance(pnt_to_project); - Vecd unit_normal = body_shape_->findNormalDirection(pnt_to_project); - unit_normal /= unit_normal.norm() + TinyReal; - Vecd new_point = pnt_to_project - phi * unit_normal; - return new_point; - } - //=================================================================================================// - bool ParticleGeneratorNetwork:: - isCollision(Vecd& new_point, ListData& nearest_neighbor, size_t parent_id) - { - bool collision = false; - bool is_family = false; - - collision = extraCheck(new_point); - - size_t edge_location = tree_->BranchLocation(nearest_neighbor.first); - if (edge_location == parent_id) is_family = true; - IndexVector& brother_branches = tree_->branches_[parent_id]->out_edge_; - for (size_t i = 0; i < brother_branches.size(); i++) - { - if (edge_location == brother_branches[i]) is_family = true; - } - - if (!is_family) - { - Real min_distance = (new_point - nearest_neighbor.second).norm(); - if (min_distance < 5.0 * segment_length_) collision = true; - } - - return collision; - } - //=================================================================================================// - bool ParticleGeneratorNetwork:: - createABranchIfValid(SPHBody* sph_body, size_t parent_id, Real angle, - Real repulsivity, size_t number_segments) - { - bool is_valid = false; - GenerativeTree::Branch* parent_branch = tree_->branches_[parent_id]; - IndexVector& parent_elements = parent_branch->inner_particles_; - StdLargeVec &tree_points = tree_->pos_n_; - - Vecd init_point = tree_points[parent_elements.back()]; - Vecd init_direction = parent_branch->end_direction_; - - - Vecd surface_norm = body_shape_->findNormalDirection(init_point); - surface_norm /= surface_norm.norm() + TinyReal; - Vecd in_plane = - SimTK::cross(init_direction, surface_norm); - - Real delta = grad_factor_ * segment_length_; - Vecd grad = getGradientFromNearestPoints(init_point, delta); - Vecd dir = cos(angle) * init_direction + sin(angle) * in_plane; - dir /= dir.norm() + TinyReal; - Vecd end_direction = (repulsivity * grad + dir) / ((repulsivity * grad + dir).norm() + TinyReal); - Vecd end_point = init_point; - - Vecd new_point = createATentativeNewBranchPoint(end_point, end_direction); - ListData nearest_neighbor = mesh_cell_linked_list_->findNearestListDataEntry(new_point); - if (!isCollision(new_point, nearest_neighbor, parent_id)) - { - is_valid = true; - GenerativeTree::Branch* new_branch = new GenerativeTree::Branch(parent_id, tree_); - tree_->growAParticleOnBranch(new_branch, new_point, end_direction); - - for (size_t i = 1; i < number_segments; i++) - { - surface_norm = body_shape_->findNormalDirection(new_point); - surface_norm /= surface_norm.norm() + TinyReal; - /** Project grad to surface. */ - grad = getGradientFromNearestPoints(new_point, delta); - grad -= dot(grad, surface_norm) * surface_norm; - dir = (repulsivity * grad + end_direction) / ((repulsivity * grad + end_direction).norm() + TinyReal); - end_direction = dir; - end_point = new_point; - - new_point = createATentativeNewBranchPoint(end_point, end_direction); - ListData nearest_neighbor = mesh_cell_linked_list_->findNearestListDataEntry(new_point); - if (isCollision(new_point, nearest_neighbor, parent_id)) - { - new_branch->is_terminated_ = true; - std::cout << "Branch Collision Detected, Break! " << std::endl; - break; - } - /** This constraint imposed to avoid too small time step size. */ - if((new_point - end_point).norm() < 0.5 * segment_length_) - { - new_branch->is_terminated_ = true; - std::cout << "New branch point is too close, Break! " << std::endl; - break; - } - tree_->growAParticleOnBranch(new_branch, new_point, end_direction); - - } - - IndexVector& new_branch_points = new_branch->inner_particles_; - for (size_t i = 0; i != new_branch_points.size(); i++) - { - size_t particle_idx = new_branch_points[i]; - mesh_cell_linked_list_->InsertACellLinkedListDataEntry(particle_idx, tree_points[particle_idx]); - } - } - - return is_valid; - } - //=================================================================================================// - void ParticleGeneratorNetwork::createBaseParticles(BaseParticles* base_particles) - { - In_Output* in_output = sph_body_->getSPHSystem().in_output_; - BodyStatesRecordingToVtu write_states(*in_output, { sph_body_ }); - - std::cout << "Now creating Particles on network... " << "\n" << std::endl; - - //the second branch - bool is_valid = createABranchIfValid(sph_body_, 0, 0.0, 0.0, segments_in_branch_); - - size_t ite = 0; - sph_body_->setNewlyUpdated(); - base_particles->total_real_particles_ = base_particles->pos_n_.size(); - write_states.writeToFile(0); - - IndexVector branches_to_grow; - IndexVector new_branches_to_grow; - if (is_valid) branches_to_grow.push_back(tree_->last_branch_id_); - - if (fascicles_) - { - /** Set vertices in family branch. */ - branches_to_grow.clear(); - for (int i = 0; i < 2; i++) - { - /** Creating a new branch. */ - Real angle_to_use = fascicle_angles_[i]; - size_t fascicles_segments = int(fascicle_ratio_ * segments_in_branch_); - bool is_valid = createABranchIfValid(sph_body_, 1, angle_to_use, 0.0, fascicles_segments); - if (is_valid) branches_to_grow.push_back(tree_->last_branch_id_); - } - - ite++; - sph_body_->setNewlyUpdated(); - base_particles->total_real_particles_ = base_particles->pos_n_.size(); - write_states.writeToFile(ite); - - } - - for(size_t i = 0; i != n_it_; i++) - { - new_branches_to_grow.clear(); - random_shuffle(branches_to_grow.begin(), branches_to_grow.end()); - for(size_t j = 0; j != branches_to_grow.size(); j++) - { - size_t grow_id = branches_to_grow[j]; - Real rand_num = ((double)rand() / (RAND_MAX)) - 0.5; - Real angle_to_use = angle_ + rand_num * 0.05; - for(size_t k = 0; k != 2; k++) - { - /** Creating a new edge. */ - size_t random_number_segments = segments_in_branch_;// + rand() % 10 + 1; - bool is_valid = createABranchIfValid(sph_body_, grow_id, angle_to_use, repulsivity_, - random_number_segments); - - if(is_valid && !tree_->LastBranch()->is_terminated_) - { - new_branches_to_grow.push_back(tree_->last_branch_id_); - } - - angle_to_use *= -1.0; - } - } - branches_to_grow = new_branches_to_grow; - - ite++; - sph_body_->setNewlyUpdated(); - base_particles->total_real_particles_ = base_particles->pos_n_.size(); - write_states.writeToFile(ite); - } - - base_particles->total_real_particles_ = base_particles->pos_n_.size(); - std::cout << base_particles->total_real_particles_ << " Particles has been successfully created!" << "\n" << std::endl; - } - //=================================================================================================// -} -//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.h b/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.h deleted file mode 100644 index 1019c9e3f4..0000000000 --- a/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.h +++ /dev/null @@ -1,116 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file particle_generator_network.h - * @brief This is a class of particle generator, which generates particles - * with in network or tree form. - * @author Chi ZHang and Xiangyu Hu - */ - -#ifndef PARTICLE_GENERATOR_NETWORK_H -#define PARTICLE_GENERATOR_NETWORK_H - - -#include "sph_data_conainers.h" -#include "base_particle_generator.h" -#include "generative_structures.h" - -namespace SPH -{ - class BaseLevelSet; - class BaseMeshCellLinkedList; - class ComplexShape; - - /** - * @class ParticleGeneratorNetwork - * @brief Generate a tree-shape network for the conduction system of a heart with particles. - */ - class ParticleGeneratorNetwork : public ParticleGenerator - { - public: - ParticleGeneratorNetwork(Vecd starting_pnt, Vecd second_pnt, int iterator, Real grad_factor); - virtual ~ParticleGeneratorNetwork() {}; - /** - *@brief Parameters initialization. - *@param[in] sph_body*(SPHBody) SPHBody to whom it generate particles. - */ - virtual void initialize(SPHBody* sph_body) override; - /** - *@brief Created base particles based on edges in branch. - *@param[in] base_particles(BaseParticles) Pointer to baseparticle link to a SPHBody. - */ - virtual void createBaseParticles(BaseParticles* base_particles) override; - protected: - Vecd starting_pnt_; /**< Starting point for net work. */ - Vecd second_pnt_; /**< Second point, approximate the growing direction. */ - size_t n_it_; /**< Number of iterations (generations of branch. */ - bool fascicles_; /**< Create fascicles? */ - size_t segments_in_branch_; /**< approximated number of segments in a branch. */ - Real segment_length_; /**< segment length of the branch. */ - Real angle_ = 0.3; /**< angle with respect to the direction of the previous edge and the new edge. */ - Real repulsivity_ = 0.175; /**< repulsivity parameter. */ - Real grad_factor_; /**< Factor for computing gradient from nearest node. */ - std::vector fascicle_angles_ = {-1.25, 0.75}; /**< angles with respect to the initial edge of the fascicles.*/ - Real fascicle_ratio_ = 15.0; /**< ratio of length of the fascicles. Include one per fascicle to include.*/ - ComplexShape* body_shape_; - BaseMeshCellLinkedList* mesh_cell_linked_list_; - GenerativeTree *tree_; - /** - *@brief Get the gradient from nearest points, for imposing repulsive force. - *@param[in] pt(Vecd) Inquiry point. - *@param[in] delta(Real) parameter for gradient calculation. - */ - Vecd getGradientFromNearestPoints(Vecd pt, Real delta); - /** - *@brief Create a new branch if it is valid. - *@param[in] sph_body(SPHBody) The SPHBody to whom the tree belongs. - *@param[in] parent_id(size_t) Id of parent branch. - *@param[in] angle(Real) The angle for growing new points. - *@param[in] repulsivity(Real) The repulsivity for creating new points. - *@param[in] number_segments(size_t) Number of segments in this branch. - */ - bool createABranchIfValid(SPHBody* sph_body, size_t parent_id, Real angle, - Real repulsivity, size_t number_segments); - /** - *@brief Functions that creates a new node in the mesh surface and it to the queue is it lies in the surface. - *@param[in] init_node vector that contains the coordinates of the last node added in the branch. - * vector that contains the coordinates of the last node added in the branch. - *@param[in] dir a vector that contains the direction from the init_node to the node to project. - *@param[out] end point of the created segment. - */ - Vecd createATentativeNewBranchPoint(Vecd init_point, Vecd dir); - /** - *@brief Check if the new point has collision with the existing points. - *@param[in] new_point(Vecd) The enquiry point. - *@param[in] nearest_neighbor(ListData) The nearest point of the existing points. - *@param[in] parent_id(size_t) Id of parent branch - */ - bool isCollision(Vecd& new_point, ListData& nearest_neighbor, size_t parent_id); - /** - *@brief Check if the new point is valid according to extra constraint. - *@param[in] new_point(Vecd) The enquiry point. - */ - virtual bool extraCheck(Vecd& new_point){return false;}; - }; -} -#endif //PARTICLE_GENERATOR_NETWORK_H \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/particles/CMakeLists.txt b/SPHINXsys/src/for_3D_build/particles/CMakeLists.txt deleted file mode 100644 index fd5eae60fd..0000000000 --- a/SPHINXsys/src/for_3D_build/particles/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/particles/solid_particles_supplementary.cpp b/SPHINXsys/src/for_3D_build/particles/solid_particles_supplementary.cpp deleted file mode 100644 index 3d44f71ce1..0000000000 --- a/SPHINXsys/src/for_3D_build/particles/solid_particles_supplementary.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "solid_particles.h" -#include "base_body.h" - -#include - -namespace SPH { - //=============================================================================================// - void SolidParticles::ParticleTranslationAndRotation(Transformd& transform) - { - std::cout << "\n Error: the function ParticleTranslationAndRotation in 3d is not defined!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - //=================================================================================================// - Real ElasticSolidParticles::von_Mises_stress(size_t particle_i) - { - Real J = rho0_ / rho_n_[particle_i]; - Mat3d F = F_[particle_i]; - Mat3d stress = stress_PK1_[particle_i]; - Mat3d sigma = (stress * ~F) / J; - - Real sigmaxx = sigma(0, 0); - Real sigmayy = sigma(1, 1); - Real sigmazz = sigma(2, 2); - Real sigmaxy = sigma(0, 1); - Real sigmaxz = sigma(0, 2); - Real sigmayz = sigma(1, 2); - - return sqrt(sigmaxx * sigmaxx + sigmayy * sigmayy + sigmazz * sigmazz - - sigmaxx * sigmayy - sigmaxx * sigmazz - sigmayy * sigmazz - + 3.0 * (sigmaxy * sigmaxy + sigmaxz * sigmaxz + sigmayz * sigmayz)); - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/CMakeLists.txt b/SPHINXsys/src/shared/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/bodies/CMakeLists.txt b/SPHINXsys/src/shared/bodies/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/bodies/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/bodies/all_bodies.h b/SPHINXsys/src/shared/bodies/all_bodies.h deleted file mode 100644 index 0947695d6a..0000000000 --- a/SPHINXsys/src/shared/bodies/all_bodies.h +++ /dev/null @@ -1,14 +0,0 @@ - -#ifndef ALL_BODIES_H -#define ALL_BODIES_H - - -/** @file -This is the header file that user code should include to pick up all -bodies used in SPHinXsys. **/ - -#pragma once - -#include "fluid_body.h" -#include "solid_body.h" -#endif //ALL_BODIES_H diff --git a/SPHINXsys/src/shared/bodies/base_body.cpp b/SPHINXsys/src/shared/bodies/base_body.cpp deleted file mode 100644 index 713e065f85..0000000000 --- a/SPHINXsys/src/shared/bodies/base_body.cpp +++ /dev/null @@ -1,276 +0,0 @@ -/** - * @file base_body.cpp - * @brief Here, Functions belong to BaseBody, RealBody and FictitiousBody are given. - * @author Chi ZHang and Xiangyu Hu - */ -#include "base_body.h" - -#include "sph_system.h" -#include "base_particles.h" -#include "body_relation.h" -#include "mesh_cell_linked_list.h" - -namespace SPH -{ - //=================================================================================================// - SPHBody::SPHBody(SPHSystem &sph_system, std::string body_name, - ParticleAdaptation *particle_adaptation, ParticleGenerator *particle_generator) - : sph_system_(sph_system), body_name_(body_name), newly_updated_(true), - body_domain_bounds_(0, 0), is_domain_bounds_determined_(false), - particle_adaptation_(particle_adaptation), particle_generator_(particle_generator), - body_shape_(nullptr), generative_structure_(nullptr) - { - sph_system_.addABody(this); - particle_adaptation_->initialize(this); - } - //=================================================================================================// - BoundingBox SPHBody::getSPHSystemBounds() - { - return sph_system_.system_domain_bounds_; - } - //=================================================================================================// - std::string SPHBody::getBodyName() - { - return body_name_; - } - //=================================================================================================// - SPHSystem &SPHBody::getSPHSystem() - { - return sph_system_; - } - //=================================================================================================// - void SPHBody::useParticleGeneratorReload() - { - particle_generator_->~ParticleGenerator(); - particle_generator_ = new ParticleGeneratorReload(sph_system_.in_output_, body_name_); - } - //=================================================================================================// - void SPHBody::assignBaseParticles(BaseParticles *base_particles) - { - base_particles_ = base_particles; - } - //=================================================================================================// - void SPHBody::allocateConfigurationMemoriesForBufferParticles() - { - for (size_t i = 0; i < body_relations_.size(); i++) - { - body_relations_[i]->updateConfigurationMemories(); - } - } - //=================================================================================================// - void SPHBody::setBodyDomainBounds(BoundingBox body_domain_bounds) - { - body_domain_bounds_ = body_domain_bounds; - is_domain_bounds_determined_ = true; - }; - //=================================================================================================// - BoundingBox SPHBody::getBodyDomainBounds() - { - if (!is_domain_bounds_determined_) - { - body_domain_bounds_ = body_shape_->findBounds(); - is_domain_bounds_determined_ = true; - } - return body_domain_bounds_; - } - //=================================================================================================// - void SPHBody::writeParticlesToVtuFile(std::ofstream &output_file) - { - base_particles_->writeParticlesToVtuFile(output_file); - newly_updated_ = false; - } - //=================================================================================================// - void SPHBody::writeParticlesToPltFile(std::ofstream &output_file) - { - if (newly_updated_) - base_particles_->writeParticlesToPltFile(output_file); - newly_updated_ = false; - } - //=================================================================================================// - void SPHBody::writeParticlesToXmlForRestart(std::string &filefullpath) - { - base_particles_->writeParticlesToXmlForRestart(filefullpath); - } - //=================================================================================================// - void SPHBody::readParticlesFromXmlForRestart(std::string &filefullpath) - { - base_particles_->readParticleFromXmlForRestart(filefullpath); - } - //=================================================================================================// - void SPHBody::writeToXmlForReloadParticle(std::string &filefullpath) - { - base_particles_->writeToXmlForReloadParticle(filefullpath); - } - //=================================================================================================// - void SPHBody::readFromXmlForReloadParticle(std::string &filefullpath) - { - base_particles_->readFromXmlForReloadParticle(filefullpath); - } - //=================================================================================================// - RealBody::RealBody(SPHSystem &sph_system, std::string body_name, - ParticleAdaptation *particle_adaptation, ParticleGenerator *particle_generator) - : SPHBody(sph_system, body_name, particle_adaptation, particle_generator), - particle_sorting_(this) - { - sph_system.addARealBody(this); - mesh_cell_linked_list_ = particle_adaptation_->createMeshCellLinkedList(); - size_t number_of_split_cell_lists = powerN(3, Vecd(0).size()); - split_cell_lists_.resize(number_of_split_cell_lists); - } - //=================================================================================================// - void RealBody::assignBaseParticles(BaseParticles *base_particles) - { - SPHBody::assignBaseParticles(base_particles); - particle_sorting_.assignBaseParticles(base_particles); - mesh_cell_linked_list_->assignBaseParticles(base_particles); - } - //=================================================================================================// - void RealBody::sortParticleWithMeshCellLinkedList() - { - StdLargeVec &sequence = base_particles_->sequence_; - size_t size = base_particles_->total_real_particles_; - mesh_cell_linked_list_->computingSequence(sequence); - particle_sorting_.sortingParticleData(sequence.data(), size); - } - //=================================================================================================// - void RealBody::updateCellLinkedList() - { - mesh_cell_linked_list_->UpdateCellLists(); - } - FictitiousBody:: - FictitiousBody(SPHSystem &system, std::string body_name, - ParticleAdaptation *particle_adaptation, ParticleGenerator *particle_generator) - : SPHBody(system, body_name, particle_adaptation, particle_generator) - { - system.addAFictitiousBody(this); - } - //=================================================================================================// - BodyPartByShape::BodyPartByShape(SPHBody *body, std::string body_part_name) - : BodyPart(body, body_part_name), body_part_shape_(nullptr) {} - //=================================================================================================// - BoundingBox BodyPartByShape::BodyPartBounds() - { - return body_part_shape_->findBounds(); - } - //=================================================================================================// - void BodyPartByParticle::tagAParticle(size_t particle_index) - { - body_part_particles_.push_back(particle_index); - } - //=================================================================================================// - void BodyPartByParticle::tagBodyPart() - { - BaseParticles *base_particles = body_->base_particles_; - for (size_t i = 0; i < base_particles->total_real_particles_; ++i) - { - if (body_part_shape_->checkContain(base_particles->pos_n_[i])) - tagAParticle(i); - } - } - //=================================================================================================// - ShapeSurface::ShapeSurface(SPHBody *body) - : BodyPartByParticle(body, "Surface"), - particle_spacing_min_(body->particle_adaptation_->MinimumSpacing()) - { - tagBodyPart(); - } - //=================================================================================================// - void ShapeSurface::tagBodyPart() - { - BaseParticles *base_particles = body_->base_particles_; - for (size_t i = 0; i < base_particles->total_real_particles_; ++i) - { - Real phi = body_->body_shape_->findSignedDistance(base_particles->pos_n_[i]); - if (fabs(phi) < particle_spacing_min_) - tagAParticle(i); - } - std::cout << "Number of surface particles : " << body_part_particles_.size() << std::endl; - } - //=================================================================================================// - ShapeSurfaceLayer::ShapeSurfaceLayer(SPHBody *body, Real layer_thickness) - : BodyPartByParticle(body, "InnerLayers"), - thickness_threshold_(body->particle_adaptation_->ReferenceSpacing() * layer_thickness) - { - tagBodyPart(); - } - //=================================================================================================// - void ShapeSurfaceLayer::tagBodyPart() - { - BaseParticles *base_particles = body_->base_particles_; - for (size_t i = 0; i < base_particles->total_real_particles_; ++i) - { - Vecd position_i = base_particles->pos_n_[i]; - Real distance = fabs(body_->body_shape_->findSignedDistance(position_i)); - if (distance < thickness_threshold_) - tagAParticle(i); - } - std::cout << "Number of inner layers particles : " << body_part_particles_.size() << std::endl; - } - //=================================================================================================// - BodyPartByCell::BodyPartByCell(RealBody *real_body, std::string body_part_name) - : BodyPartByShape(real_body, body_part_name), real_body_(real_body), - checkIncluded_(std::bind(&BodyPartByCell::checkIncluded, this, _1, _2)) {} - //=================================================================================================// - bool BodyPartByCell::checkIncluded(Vecd cell_position, Real threshold) - { - return body_part_shape_->checkNotFar(cell_position, threshold); - } - //=================================================================================================// - void BodyPartByCell::tagBodyPart() - { - real_body_->mesh_cell_linked_list_->tagBodyPartByCell(body_part_cells_, checkIncluded_); - } - //=================================================================================================// - NearShapeSurface:: - NearShapeSurface(RealBody *real_body, ComplexShape *complex_shape, std::string body_part_name) - : BodyPartByCell(real_body, body_part_name) - { - level_set_complex_shape_ = new LevelSetComplexShape(real_body, *complex_shape, true); - body_part_shape_ = level_set_complex_shape_; - tagBodyPart(); - } - //=================================================================================================// - NearShapeSurface::NearShapeSurface(RealBody *real_body) - : BodyPartByCell(real_body, "NearShapeSurface") - { - body_part_shape_ = real_body->body_shape_; - level_set_complex_shape_ = dynamic_cast(body_part_shape_); - if (level_set_complex_shape_ == nullptr) - { - std::cout << "\n FAILURE: LevelSetComplexShape is undefined!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - tagBodyPart(); - } - //=================================================================================================// - LevelSetComplexShape *NearShapeSurface::getLevelSetComplexShape() - { - return level_set_complex_shape_; - } - //=================================================================================================// - bool NearShapeSurface::checkIncluded(Vecd cell_position, Real threshold) - { - return body_part_shape_->checkNearSurface(cell_position, threshold); - } - //=================================================================================================// - TerminateBranches::TerminateBranches(SPHBody *body) - : BodyPartByParticle(body, "Leaves"), - tree_(dynamic_cast(body->generative_structure_)) - { - tagBodyPart(); - } - //=================================================================================================// - void TerminateBranches::tagBodyPart() - { - for (size_t branch_idx = 0; branch_idx != tree_->branches_.size(); ++branch_idx) - { - if (tree_->branches_[branch_idx]->is_terminated_) - { - size_t particle_id = tree_->branches_[branch_idx]->inner_particles_.back(); - tagAParticle(particle_id); - } - } - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/bodies/base_body.h b/SPHINXsys/src/shared/bodies/base_body.h deleted file mode 100644 index c722a59097..0000000000 --- a/SPHINXsys/src/shared/bodies/base_body.h +++ /dev/null @@ -1,314 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file base_body.h - * @brief This is the base classes of SPH bodies. The real body is for - * that with cell linked list and the fictitious one does not. - * Before the definition of the SPH bodies, the shapes with complex - * geometries, i.e. those are produced by advanced binary operation, - * such as intersection, should be produced first. - * Then, all shapes used in body definition should be either contain - * or not contain each other. - * Partial overlap between them are not premitted. - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#ifndef BASE_BODY_H -#define BASE_BODY_H - -#include "base_data_package.h" -#include "sph_data_conainers.h" -#include "particle_adaptation.h" -#include "all_particle_generators.h" -#include "particle_sorting.h" -#include "all_geometries.h" -#include "generative_structures.h" - -#include - -namespace SPH -{ - class SPHSystem; - class BaseParticles; - class BaseMeshCellLinkedList; - class SPHBodyRelation; - - /** - * @class SPHBody - * @brief SPHBody is a base body with basic data and functions. - * Its derived class can be a real fluid body, a real deformable solid body, - * a static or moving solid body or a fictitious body. - * Note that only real bodies have cell linked list. - */ - class SPHBody - { - protected: - SPHSystem &sph_system_; - std::string body_name_; - bool newly_updated_; /**< whether this body is in a newly updated state */ - /**< Computational domain bounds for boundary conditions. - * Note that domain bounds may be different from those of the initial body geometry. */ - BoundingBox body_domain_bounds_; - bool is_domain_bounds_determined_; - - public: - ParticleAdaptation *particle_adaptation_; /**< Particle adapation policy. */ - ParticleGenerator *particle_generator_; /**< Particle generator manner */ - BaseParticles *base_particles_; /**< Base particles of this body. */ - PositionsAndVolumes body_input_points_volumes_; /**< For direct generate particles. Note this should be moved to direct generator. */ - ComplexShape *body_shape_; /**< describe the geometry of the body*/ - GenerativeStructure *generative_structure_; /**< structure which can be used to generate particles or/and configurations directly*/ - /** - * @brief particle by cells lists is for parallel splitting algorithm. - * All particles in each cell are collected together. - * If two partiles each belongs two different cell entries, - * they have no interaction because they are too far. - */ - SplitCellLists split_cell_lists_; - - StdVec body_relations_; /**< all contact relations centered from this body **/ - - explicit SPHBody(SPHSystem &sph_system, std::string body_name, - ParticleAdaptation *particle_adaptation = new ParticleAdaptation(), - ParticleGenerator *particle_generator = new ParticleGeneratorLattice()); - virtual ~SPHBody(){}; - - std::string getBodyName(); - SPHSystem &getSPHSystem(); - Real getSPHBodyResolutionRef() { return particle_adaptation_->ReferenceSpacing(); }; - void setNewlyUpdated() { newly_updated_ = true; }; - void setNotNewlyUpdated() { newly_updated_ = false; }; - bool checkNewlyUpdated() { return newly_updated_; }; - void useParticleGeneratorReload(); - - void setBodyDomainBounds(BoundingBox body_domain_bounds); - BoundingBox getBodyDomainBounds(); - BoundingBox getSPHSystemBounds(); - - /** This will be called in BaseParticle constructor - * and is important because particles are not defined in SPHBody constructor. */ - virtual void assignBaseParticles(BaseParticles *base_particles); - void allocateConfigurationMemoriesForBufferParticles(); - - virtual void writeParticlesToVtuFile(std::ofstream &output_file); - virtual void writeParticlesToPltFile(std::ofstream &output_file); - virtual void writeParticlesToXmlForRestart(std::string &filefullpath); - virtual void readParticlesFromXmlForRestart(std::string &filefullpath); - virtual void writeToXmlForReloadParticle(std::string &filefullpath); - virtual void readFromXmlForReloadParticle(std::string &filefullpath); - virtual SPHBody *ThisObjectPtr() { return this; }; - }; - - /** - * @class RealBody - * @brief Derived class from SPHBody. - * With inner particle configuration or inner interactions. - */ - class RealBody : public SPHBody - { - public: - ParticleSorting particle_sorting_; - BaseMeshCellLinkedList *mesh_cell_linked_list_; /**< Cell linked mesh of this body. */ - - RealBody(SPHSystem &sph_system, std::string body_name, ParticleAdaptation *particle_adaptation, - ParticleGenerator *particle_generator = new ParticleGeneratorLattice()); - RealBody(SPHSystem &sph_system, std::string body_name, Real sph_body_resolution_ref, - ParticleAdaptation *particle_adaptation, - ParticleGenerator *particle_generator = new ParticleGeneratorLattice()); - virtual ~RealBody(){}; - - /** This will be called in BaseParticle constructor - * and is important because particles are not defined in FluidBody constructor. */ - virtual void assignBaseParticles(BaseParticles *base_particles) override; - virtual void sortParticleWithMeshCellLinkedList(); - virtual void updateCellLinkedList(); - }; - - /** - * @class FictitiousBody - * @brief Derived class from SPHBody. - * Without inner configuration or inner interaction. - */ - class FictitiousBody : public SPHBody - { - public: - FictitiousBody(SPHSystem &system, std::string body_name, - ParticleAdaptation *particle_adaptation = new ParticleAdaptation(), - ParticleGenerator *particle_generator = new ParticleGeneratorDirect()); - virtual ~FictitiousBody(){}; - }; - - /** - * @class BodyPart - * @brief An abstract auxillary class for SPHBody to indicate a part of the body. - */ - class BodyPart - { - public: - BodyPart(SPHBody *body, std::string body_part_name) - : body_(body), body_part_name_(body_part_name){}; - virtual ~BodyPart(){}; - - SPHBody *getBody() { return body_; }; - std::string BodyPartName() { return body_part_name_; }; - - protected: - SPHBody *body_; - std::string body_part_name_; - - virtual void tagBodyPart() = 0; - }; - - /** - * @class BodyPartByShape - * @brief An auxillary class for SPHBody to indicate - * a part of the body defined by a presribed complex shape. - */ - class BodyPartByShape : public BodyPart - { - public: - BodyPartByShape(SPHBody *body, std::string body_part_name); - virtual ~BodyPartByShape(){}; - - ComplexShape *getBodyPartShape() { return body_part_shape_; }; - BoundingBox BodyPartBounds(); - - protected: - ComplexShape *body_part_shape_; - }; - /** - * @class BodyPartByParticle - * @brief An auxillary class for SPHBody to - * indicate a part of the body moving together with particles. - */ - class BodyPartByParticle : public BodyPartByShape - { - public: - IndexVector body_part_particles_; /**< Collection particle in this body part. */ - - BodyPartByParticle(SPHBody *body, std::string body_part_name) - : BodyPartByShape(body, body_part_name){}; - - virtual ~BodyPartByParticle(){}; - - protected: - void tagAParticle(size_t particle_index); - virtual void tagBodyPart() override; - }; - - /** - * @class ShapeSurface - * @brief A auxillary class for Body to - * indicate the surface of a shape - */ - class ShapeSurface : public BodyPartByParticle - { - public: - ShapeSurface(SPHBody *body); - virtual ~ShapeSurface(){}; - - protected: - Real particle_spacing_min_; - virtual void tagBodyPart() override; - }; - - /** - * @class ShapeSurfaceLayer - * @brief A auxillary class for Body to - * indicate the particles within the inner layers of a shape - */ - class ShapeSurfaceLayer : public BodyPartByParticle - { - public: - ShapeSurfaceLayer(SPHBody *body, Real layer_thickness = 3.0); - virtual ~ShapeSurfaceLayer(){}; - - protected: - Real thickness_threshold_; - - virtual void tagBodyPart() override; - }; - - /** - * @class BodyPartByCell - * @brief An auxillary class for SPHBody to - * indicate a part of the body fixed in space defined by mesh cells. - */ - using namespace std::placeholders; - class BodyPartByCell : public BodyPartByShape - { - protected: - RealBody *real_body_; - typedef std::function CheckIncludedFunctor; - CheckIncludedFunctor checkIncluded_; - - /** all cells near or contained by the body part shape are included */ - virtual bool checkIncluded(Vecd cell_position, Real threshold); - virtual void tagBodyPart() override; - - public: - CellLists body_part_cells_; /**< Collection of cells to indicate the body part. */ - - BodyPartByCell(RealBody *real_body, std::string body_part_name); - virtual ~BodyPartByCell(){}; - }; - - /** - * @class NearShapeSurface - * @brief An auxillary class for SPHBody to - * indicate the region close to the surface of shape. - */ - class NearShapeSurface : public BodyPartByCell - { - public: - /** for the case that the body part shape is not that of the body */ - NearShapeSurface(RealBody *real_body, ComplexShape *complex_shape, std::string body_part_name); - /** for the case that the body part is the surface of the body shape */ - NearShapeSurface(RealBody *real_body); - virtual ~NearShapeSurface(){}; - - LevelSetComplexShape *getLevelSetComplexShape(); - - protected: - LevelSetComplexShape *level_set_complex_shape_; - /** only cells near the surface of the body part shape are included */ - virtual bool checkIncluded(Vecd cell_position, Real threshold) override; - }; - - /** - * @class TerminateBranches - * @brief A auxillary class for a Tree-like Body to - * indicate the particles from the terminates of the tree. - */ - class TerminateBranches : public BodyPartByParticle - { - public: - TerminateBranches(SPHBody *body); - virtual ~TerminateBranches(){}; - - protected: - GenerativeTree *tree_; - virtual void tagBodyPart() override; - }; -} -#endif //BASE_BODY_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/bodies/body_relation.cpp b/SPHINXsys/src/shared/bodies/body_relation.cpp deleted file mode 100644 index a3cd339fea..0000000000 --- a/SPHINXsys/src/shared/bodies/body_relation.cpp +++ /dev/null @@ -1,268 +0,0 @@ -/** - * @file body_relation.cpp - * @brief Here, Functions belong to BaseBody, RealBody and FictitiousBody are given. - * @author Chi ZHang and Xiangyu Hu - */ - -#include "body_relation.h" - -#include "base_kernel.h" -#include "base_particles.h" -#include "mesh_cell_linked_list.hpp" - -namespace SPH -{ - //=================================================================================================// - SPHBodyRelation::SPHBodyRelation(SPHBody* sph_body) - : sph_body_(sph_body), base_particles_(sph_body->base_particles_) {} - //=================================================================================================// - BaseBodyRelationInner::BaseBodyRelationInner(RealBody* real_body) - : SPHBodyRelation(real_body), real_body_(real_body) - { - subscribeToBody(); - updateConfigurationMemories(); - } - //=================================================================================================// - void BaseBodyRelationInner::updateConfigurationMemories() - { - size_t updated_size = sph_body_->base_particles_->real_particles_bound_; - inner_configuration_.resize(updated_size, Neighborhood()); - } - //=================================================================================================// - void BaseBodyRelationInner::resetNeighborhoodCurrentSize() - { - parallel_for(blocked_range(0, base_particles_->total_real_particles_), - [&](const blocked_range& r) { - for (size_t num = r.begin(); num != r.end(); ++num) { - inner_configuration_[num].current_size_ = 0; - } - }, ap); - } - //=================================================================================================// - BodyRelationInner::BodyRelationInner(RealBody* real_body) - : BaseBodyRelationInner(real_body), get_inner_neighbor_(real_body), - mesh_cell_linked_list_(dynamic_cast(real_body->mesh_cell_linked_list_)) {} - //=================================================================================================// - void BodyRelationInner::updateConfiguration() - { - resetNeighborhoodCurrentSize(); - mesh_cell_linked_list_->searchNeighborsByParticles(base_particles_->total_real_particles_, *base_particles_, - inner_configuration_, get_particle_index_, get_single_search_range_, get_inner_neighbor_); - } - //=================================================================================================// - BodyRelationInnerVariableSmoothingLength:: - BodyRelationInnerVariableSmoothingLength(RealBody* real_body) - : BaseBodyRelationInner(real_body), total_levels_(0), - get_inner_neighbor_variable_smoothing_length_(real_body) - { - MultilevelMeshCellLinkedList* multi_level_mesh_cell_linked_list = - dynamic_cast(real_body->mesh_cell_linked_list_); - mesh_cell_linked_list_levels_ = multi_level_mesh_cell_linked_list->getMeshLevels(); - total_levels_ = mesh_cell_linked_list_levels_.size(); - for (size_t l = 0; l != total_levels_; ++l) { - get_multi_level_search_range_.push_back( - new SearchRangeVariableSmoothingLength(real_body, mesh_cell_linked_list_levels_[l])); - } - } - //=================================================================================================// - void BodyRelationInnerVariableSmoothingLength::updateConfiguration() - { - resetNeighborhoodCurrentSize(); - for (size_t l = 0; l != total_levels_; ++l) { - mesh_cell_linked_list_levels_[l]->searchNeighborsByParticles(base_particles_->total_real_particles_, - *base_particles_, inner_configuration_, get_particle_index_, - *get_multi_level_search_range_[l], get_inner_neighbor_variable_smoothing_length_); - } - } - //=================================================================================================// - BaseBodyRelationContact::BaseBodyRelationContact(SPHBody* sph_body, RealBodyVector contact_sph_bodies) - : SPHBodyRelation(sph_body), contact_bodies_(contact_sph_bodies) - { - subscribeToBody(); - updateConfigurationMemories(); - } - //=================================================================================================// - BaseBodyRelationContact::BaseBodyRelationContact(SPHBody* sph_body, BodyPartVector contact_body_parts) - : SPHBodyRelation(sph_body) - { - for(size_t k = 0; k != contact_body_parts.size(); ++k) - { - contact_bodies_.push_back(dynamic_cast(contact_body_parts[k]->getBody())); - } - subscribeToBody(); - updateConfigurationMemories(); - } - //=================================================================================================// - void BaseBodyRelationContact::updateConfigurationMemories() - { - size_t updated_size = sph_body_->base_particles_->real_particles_bound_; - contact_configuration_.resize(contact_bodies_.size()); - for (size_t k = 0; k != contact_bodies_.size(); ++k) { - contact_configuration_[k].resize(updated_size, Neighborhood()); - } - } - //=================================================================================================// - void BaseBodyRelationContact::resetNeighborhoodCurrentSize() - { - for (size_t k = 0; k != contact_bodies_.size(); ++k) { - parallel_for(blocked_range(0, base_particles_->total_real_particles_), - [&](const blocked_range& r) { - for (size_t num = r.begin(); num != r.end(); ++num) { - contact_configuration_[k][num].current_size_ = 0; - } - }, ap); - } - } - //=================================================================================================// - BodyRelationContact::BodyRelationContact(SPHBody* sph_body, RealBodyVector contact_sph_bodies) - : BaseBodyRelationContact(sph_body, contact_sph_bodies) - { - initialization(); - } - //=================================================================================================// - BodyRelationContact::BodyRelationContact(SPHBody* sph_body, BodyPartVector contact_body_parts) - : BaseBodyRelationContact(sph_body, contact_body_parts) - { - initialization(); - } - //=================================================================================================// - void BodyRelationContact::initialization() - { - for (size_t k = 0; k != contact_bodies_.size(); ++k) - { - target_mesh_cell_linked_lists_.push_back(dynamic_cast(contact_bodies_[k]->mesh_cell_linked_list_)); - get_search_ranges_.push_back(new SearchRangeMultiResolution(sph_body_, contact_bodies_[k])); - get_contact_neighbors_.push_back(new NeighborRelationContact(sph_body_, contact_bodies_[k])); - } - } - //=================================================================================================// - void BodyRelationContact::updateConfiguration() - { - resetNeighborhoodCurrentSize(); - size_t total_real_particles = base_particles_->total_real_particles_; - for (size_t k = 0; k != contact_bodies_.size(); ++k) { - target_mesh_cell_linked_lists_[k]->searchNeighborsByParticles(total_real_particles, - *base_particles_, contact_configuration_[k], - get_particle_index_, *get_search_ranges_[k], *get_contact_neighbors_[k]); - } - } - //=================================================================================================// - SolidBodyRelationContact::SolidBodyRelationContact(SPHBody* sph_body, RealBodyVector contact_sph_bodies) - : BaseBodyRelationContact(sph_body, contact_sph_bodies), - body_part_particles_(body_surface_layer_.body_part_particles_), - get_body_part_particle_index_(body_part_particles_), - body_surface_layer_(ShapeSurfaceLayer(sph_body)) - { - initialization(); - } - //=================================================================================================// - void SolidBodyRelationContact::resetNeighborhoodCurrentSize() - { - for (size_t k = 0; k != contact_bodies_.size(); ++k) { - parallel_for(blocked_range(0, body_part_particles_.size()), - [&](const blocked_range& r) { - for (size_t num = r.begin(); num != r.end(); ++num) { - size_t index_i = get_body_part_particle_index_(num); - contact_configuration_[k][index_i].current_size_ = 0; - } - }, ap); - } - } - //=================================================================================================// - void SolidBodyRelationContact::initialization() - { - for (size_t k = 0; k != contact_bodies_.size(); ++k) - { - target_mesh_cell_linked_lists_.push_back(dynamic_cast(contact_bodies_[k]->mesh_cell_linked_list_)); - get_search_ranges_.push_back(new SearchRangeMultiResolution(sph_body_, contact_bodies_[k])); - get_contact_neighbors_.push_back(new NeighborRelationSolidContact(sph_body_, contact_bodies_[k])); - } - } - //=================================================================================================// - void SolidBodyRelationContact::updateConfiguration() - { - resetNeighborhoodCurrentSize(); - size_t total_real_particles = body_part_particles_.size(); - for (size_t k = 0; k != contact_bodies_.size(); ++k) { - target_mesh_cell_linked_lists_[k]->searchNeighborsByParticles(total_real_particles, - *base_particles_, contact_configuration_[k], - get_body_part_particle_index_,*get_search_ranges_[k], *get_contact_neighbors_[k]); - } - } - //=================================================================================================// - void GenerativeBodyRelationInner::updateConfiguration() - { - generative_structure_->buildParticleConfiguration(*base_particles_, inner_configuration_); - } - //=================================================================================================// - BodyPartRelationContact::BodyPartRelationContact(BodyPart* body_part, RealBodyVector contact_bodies) - : BodyRelationContact(body_part->getBody(), contact_bodies), body_part_(body_part), - body_part_particles_(dynamic_cast(body_part)->body_part_particles_), - get_body_part_particle_index_(dynamic_cast(body_part)->body_part_particles_) - { - } - //=================================================================================================// - void BodyPartRelationContact::updateConfiguration() - { - size_t number_of_particles = body_part_particles_.size(); - for (size_t k = 0; k != contact_bodies_.size(); ++k) - { - target_mesh_cell_linked_lists_[k]->searchNeighborsByParticles(number_of_particles, - *base_particles_, contact_configuration_[k], - get_body_part_particle_index_, *get_search_ranges_[k], *get_contact_neighbors_[k]); - } - } - //=================================================================================================// - BodyRelationContactToBodyPart::BodyRelationContactToBodyPart(RealBody* real_body, BodyPartVector contact_body_parts) - : BodyRelationContact(real_body, contact_body_parts), contact_body_parts_(contact_body_parts) - { - for (size_t k = 0; k != contact_bodies_.size(); ++k) - { - get_part_contact_neighbors_.push_back(new NeighborRelationContactBodyPart(sph_body_, contact_body_parts[k])); - } - } - //=================================================================================================// - void BodyRelationContactToBodyPart::updateConfiguration() - { - size_t number_of_particles = base_particles_->total_real_particles_; - for (size_t k = 0; k != contact_body_parts_.size(); ++k) - { - target_mesh_cell_linked_lists_[k]->searchNeighborsByParticles(number_of_particles, - *base_particles_, contact_configuration_[k], - get_particle_index_, *get_search_ranges_[k], *get_part_contact_neighbors_[k]); - } - } - //=================================================================================================// - ComplexBodyRelation::ComplexBodyRelation(BaseBodyRelationInner* inner_relation, BaseBodyRelationContact* contact_relation) : - SPHBodyRelation(inner_relation->sph_body_), - inner_relation_(inner_relation), contact_relation_(contact_relation), - contact_bodies_(contact_relation->contact_bodies_), - inner_configuration_(inner_relation->inner_configuration_), - contact_configuration_(contact_relation->contact_configuration_) - { - updateConfigurationMemories(); - } - //=================================================================================================// - ComplexBodyRelation::ComplexBodyRelation(RealBody* real_body, RealBodyVector contact_bodies) : - ComplexBodyRelation(new BodyRelationInner(real_body), new BodyRelationContact(real_body, contact_bodies)) {} - //=================================================================================================// - ComplexBodyRelation:: - ComplexBodyRelation(BaseBodyRelationInner* inner_relation, RealBodyVector contact_bodies) : - ComplexBodyRelation(inner_relation, new BodyRelationContact(inner_relation->sph_body_, contact_bodies)) {} - //=================================================================================================// - ComplexBodyRelation::ComplexBodyRelation(RealBody* real_body, BodyPartVector contact_body_parts) - :ComplexBodyRelation(new BodyRelationInner(real_body), new BodyRelationContactToBodyPart(real_body, contact_body_parts)) {} - //=================================================================================================// - void ComplexBodyRelation::updateConfigurationMemories() - { - inner_relation_->updateConfigurationMemories(); - contact_relation_->updateConfigurationMemories(); - } - //=================================================================================================// - void ComplexBodyRelation::updateConfiguration() - { - inner_relation_->updateConfiguration(); - contact_relation_->updateConfiguration(); - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/bodies/body_relation.h b/SPHINXsys/src/shared/bodies/body_relation.h deleted file mode 100644 index 398bd40c0a..0000000000 --- a/SPHINXsys/src/shared/bodies/body_relation.h +++ /dev/null @@ -1,308 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file body_relation.h - * @brief The topological relations between bodies are described here. - * @author Xiangyu Hu - * @version 0.3.0 - * -- Add reduced body relation for network. Chi ZHANG - */ - - -#ifndef BODY_RELATION_H -#define BODY_RELATION_H - -#include "base_body.h" -#include "base_particles.h" -#include "mesh_cell_linked_list.h" -#include "neighbor_relation.h" -#include "base_geometry.h" - -namespace SPH -{ - /** a small functor for obtaining particle index for container index */ - struct SPHBodyParticlesIndex - { - size_t operator () (size_t particle_index) const { return particle_index; }; - }; - - /** a small functor for obtaining particle index for body part container index */ - struct BodyPartParticlesIndex - { - IndexVector& body_part_particles_; - BodyPartParticlesIndex(IndexVector& body_part_particles) : body_part_particles_(body_part_particles) {}; - size_t operator () (size_t particle_entry) const {return body_part_particles_[particle_entry]; }; - }; - - /** a small functor for obtaining search range for the simplest case */ - struct SearchRangeSingleResolution - { - int operator () (size_t particle_index) const { return 1; }; - }; - - /** a small functor for obtaining search range across resolution */ - struct SearchRangeMultiResolution - { - int search_range_; - SearchRangeMultiResolution(SPHBody* body, SPHBody* contact_body) : search_range_(0) - { - int body_refinement_level = body->particle_adaptation_->GlobalRefinementLevel(); - int contact_body_refinement_level = contact_body->particle_adaptation_->GlobalRefinementLevel(); - search_range_ = body_refinement_level >= contact_body_refinement_level ? - 1 : powerN(2, contact_body_refinement_level - body_refinement_level); - }; - int operator () (size_t particle_index) const { return search_range_; }; - }; - - /** a small functor for obtaining search for variable smoothing length */ - struct SearchRangeVariableSmoothingLength - { - Real inv_grid_spacing_; - Kernel* kernel_; - StdLargeVec& h_ratio_; - SearchRangeVariableSmoothingLength(SPHBody* body, MeshCellLinkedList* target_mesh_cell_linked_list) : - inv_grid_spacing_(1.0 / target_mesh_cell_linked_list->GridSpacing()), - kernel_(body->particle_adaptation_->getKernel()), - h_ratio_(*body->base_particles_->getVariableByName("SmoothingLengthRatio")) {}; - int operator () (size_t particle_index) const - { - return 1 + (int)floor(kernel_->CutOffRadius(h_ratio_[particle_index]) * inv_grid_spacing_); - }; - }; - - /** - * @class SPHBodyRelation - * @brief The abstract class for all relations within a SPH body or with its contact SPH bodies - */ - class SPHBodyRelation - { - public: - SPHBody* sph_body_; - BaseParticles* base_particles_; - - SPHBodyRelation(SPHBody* sph_body); - virtual ~SPHBodyRelation() {}; - - void subscribeToBody() { sph_body_->body_relations_.push_back(this); }; - virtual void updateConfigurationMemories() = 0; - virtual void updateConfiguration() = 0; - }; - - /** - * @class BaseBodyRelationInner - * @brief The abstract relation within a SPH body - */ - class BaseBodyRelationInner : public SPHBodyRelation - { - protected: - virtual void resetNeighborhoodCurrentSize(); - public: - RealBody* real_body_; - ParticleConfiguration inner_configuration_; /**< inner configuration for the neighbor relations. */ - - BaseBodyRelationInner(RealBody* real_body); - virtual ~BaseBodyRelationInner() {}; - - virtual void updateConfigurationMemories() override; - }; - - /** - * @class BodyRelationInner - * @brief The first concrete relation within a SPH body - */ - class BodyRelationInner : public BaseBodyRelationInner - { - protected: - SPHBodyParticlesIndex get_particle_index_; - SearchRangeSingleResolution get_single_search_range_; - NeighborRelationInner get_inner_neighbor_; - MeshCellLinkedList* mesh_cell_linked_list_; - - public: - BodyRelationInner(RealBody* real_body); - virtual ~BodyRelationInner() {}; - - virtual void updateConfiguration() override; - }; - - /** - * @class BodyRelationInnerVariableSmoothingLength - * @brief The relation within a SPH body with smoothing length adaptation - */ - class BodyRelationInnerVariableSmoothingLength : public BaseBodyRelationInner - { - protected: - size_t total_levels_; - SPHBodyParticlesIndex get_particle_index_; - StdVec get_multi_level_search_range_; - NeighborRelationInnerVariableSmoothingLength get_inner_neighbor_variable_smoothing_length_; - StdVec mesh_cell_linked_list_levels_; - public: - BodyRelationInnerVariableSmoothingLength(RealBody* real_body); - virtual ~BodyRelationInnerVariableSmoothingLength() {}; - - virtual void updateConfiguration() override; - }; - - /** - * @class BaseBodyRelationContact - * @brief The base relation between a SPH body and its contact SPH bodies - */ - class BaseBodyRelationContact : public SPHBodyRelation - { - protected: - StdVec target_mesh_cell_linked_lists_; - StdVec get_search_ranges_; - StdVec get_contact_neighbors_; - - virtual void resetNeighborhoodCurrentSize(); - public: - RealBodyVector contact_bodies_; - ContatcParticleConfiguration contact_configuration_; /**< Configurations for particle interaction between bodies. */ - - BaseBodyRelationContact(SPHBody* body, RealBodyVector contact_bodies); - BaseBodyRelationContact(SPHBody* body, BodyPartVector contact_body_parts); - virtual ~BaseBodyRelationContact() {}; - - virtual void updateConfigurationMemories() override; - }; - - /** - * @class BodyRelationContact - * @brief The relation between a SPH body and its contact SPH bodies - */ - class BodyRelationContact : public BaseBodyRelationContact - { - protected: - SPHBodyParticlesIndex get_particle_index_; - - void initialization(); - public: - BodyRelationContact(SPHBody* body, RealBodyVector contact_bodies); - BodyRelationContact(SPHBody* body, BodyPartVector contact_body_parts); - virtual ~BodyRelationContact() {}; - virtual void updateConfiguration() override; - }; - - /** - * @class SolidBodyRelationContact - * @brief The relation between a solid body and its contact solid bodies - */ - class SolidBodyRelationContact : public BaseBodyRelationContact - { - protected: - IndexVector& body_part_particles_; - BodyPartParticlesIndex get_body_part_particle_index_; - - void initialization(); - virtual void resetNeighborhoodCurrentSize() override; - public: - ShapeSurfaceLayer body_surface_layer_; - - SolidBodyRelationContact(SPHBody* body, RealBodyVector contact_bodies); - virtual ~SolidBodyRelationContact() {}; - - virtual void updateConfiguration() override; - }; - - /** - * @class GenerativeBodyRelationInner - * @brief The relation within a reduced SPH body, viz. network - */ - class GenerativeBodyRelationInner : public BodyRelationInner - { - protected: - GenerativeStructure* generative_structure_; - public: - GenerativeBodyRelationInner(RealBody* real_body) - : BodyRelationInner(real_body), - generative_structure_(real_body->generative_structure_){}; - virtual ~GenerativeBodyRelationInner() {}; - - virtual void updateConfiguration() override; - }; - - /** - * @class BodyPartRelationContact - * @brief The relation between a Body part with a SPH body. - */ - class BodyPartRelationContact : public BodyRelationContact - { - - public: - BodyPart* body_part_; - IndexVector& body_part_particles_; - BodyPartParticlesIndex get_body_part_particle_index_; - - BodyPartRelationContact(BodyPart* body_part, RealBodyVector contact_bodies); - virtual ~BodyPartRelationContact() {}; - - virtual void updateConfiguration() override; - }; - - /** - * @class BodyRelationContactToBodyPart - * @brief The relation between a SPH body and a vector of body parts. - */ - class BodyRelationContactToBodyPart : public BodyRelationContact - { - - public: - BodyPartVector contact_body_parts_; - StdVec get_part_contact_neighbors_; - - BodyRelationContactToBodyPart(RealBody* real_body, BodyPartVector contact_body_parts); - virtual ~BodyRelationContactToBodyPart() {}; - - virtual void updateConfiguration() override; - }; - - /** - * @class ComplexBodyRelation - * @brief The relation combined an inner and a contactbody relation. - * The interaction is in a inner-boundary-condition fashion. Here inner interaction is - * different from contact interaction. - */ - class ComplexBodyRelation : public SPHBodyRelation - { - public: - BaseBodyRelationInner* inner_relation_; - BaseBodyRelationContact* contact_relation_; - RealBodyVector contact_bodies_; - ParticleConfiguration& inner_configuration_; - ContatcParticleConfiguration& contact_configuration_; - - ComplexBodyRelation(BaseBodyRelationInner* inner_relation, BaseBodyRelationContact* contact_relation); - ComplexBodyRelation(RealBody* real_body, RealBodyVector contact_bodies); - ComplexBodyRelation(BaseBodyRelationInner* inner_relation, RealBodyVector contact_bodies); - ComplexBodyRelation(RealBody* real_body, BodyPartVector contact_body_parts); - virtual ~ComplexBodyRelation() { - delete inner_relation_; - delete contact_relation_; - }; - - virtual void updateConfigurationMemories() override; - virtual void updateConfiguration() override; - }; -} -#endif //BODY_RELATION_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/bodies/fluid_body.cpp b/SPHINXsys/src/shared/bodies/fluid_body.cpp deleted file mode 100644 index 586eb4685e..0000000000 --- a/SPHINXsys/src/shared/bodies/fluid_body.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @file fluid_body.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#include "fluid_body.h" -#include "mesh_cell_linked_list.h" - -namespace SPH { - //=================================================================================================// - FluidBody::FluidBody(SPHSystem &system, std::string body_name, - ParticleAdaptation* particle_adaptation, ParticleGenerator* particle_generator) - : RealBody(system, body_name, particle_adaptation, particle_generator), - iteration_count_(0) {} - //=================================================================================================// - void FluidBody::updateCellLinkedList() - { - //sorting is carried out once for 100 iterations - if (iteration_count_ % 100 == 0) sortParticleWithMeshCellLinkedList(); - iteration_count_++; - mesh_cell_linked_list_->UpdateCellLists(); - } - //=================================================================================================// - EulerianFluidBody::EulerianFluidBody(SPHSystem &system, std::string body_name, - ParticleAdaptation* particle_adaptation, ParticleGenerator* particle_generator) - : RealBody(system, body_name, particle_adaptation, particle_generator) {} - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/bodies/fluid_body.h b/SPHINXsys/src/shared/bodies/fluid_body.h deleted file mode 100644 index bb4a59aa57..0000000000 --- a/SPHINXsys/src/shared/bodies/fluid_body.h +++ /dev/null @@ -1,73 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file fluid_body.h - * @brief This is the class for bodies used for fluid. - * @author Chi ZHang and Xiangyu Hu - */ - -#ifndef FLUID_BODY_H -#define FLUID_BODY_H - - - -#include "base_body.h" - -namespace SPH { - class SPHSystem; - /** - * @class FluidBody - * @brief Fluid body uses smoothing length to particle spacing 1.3 - * and carry out particle sorting every 100 iterations. - */ - class FluidBody : public RealBody - { - public: - explicit FluidBody(SPHSystem &system, std::string body_name, - ParticleAdaptation* particle_adaptation = new ParticleAdaptation(), - ParticleGenerator* particle_generator = new ParticleGeneratorLattice()); - virtual ~FluidBody() {}; - - /** Update cell linked list with particle sorting. */ - virtual void updateCellLinkedList() override; - virtual FluidBody* ThisObjectPtr() override {return this;}; - protected: - size_t iteration_count_; - }; - - /** - * @class EulerianFluidBody - * @brief Eulerian Fluid body uses smoothing length to particle spacing 1.3 - */ - class EulerianFluidBody : public RealBody - { - public: - explicit EulerianFluidBody(SPHSystem &system, std::string body_name, - ParticleAdaptation* particle_adaptation = new ParticleAdaptation(), - ParticleGenerator* particle_generator = new ParticleGeneratorLattice()); - virtual ~EulerianFluidBody() {}; - - virtual EulerianFluidBody* ThisObjectPtr() override { return this; }; - }; -} -#endif //FLUID_BODY_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/bodies/solid_body.cpp b/SPHINXsys/src/shared/bodies/solid_body.cpp deleted file mode 100644 index 3fd0a4818a..0000000000 --- a/SPHINXsys/src/shared/bodies/solid_body.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @file solid_body.cpp - * @brief This is the class for bodies used for solid BCs or Elastic structure. - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#include "solid_body.h" - -#include "base_kernel.h" -#include "sph_system.h" -#include "base_material.h" -#include "solid_particles.h" - -namespace SPH { - //=================================================================================================// - SolidBody::SolidBody(SPHSystem &system, std::string body_name, - ParticleAdaptation* particle_adaptation, ParticleGenerator* particle_generator) - : RealBody(system, body_name, particle_adaptation, particle_generator) - { - sph_system_.addASolidBody(this); - } - //=================================================================================================// - ThinStructure::ThinStructure(SPHSystem& system, std::string body_name, - ParticleAdaptation* particle_adaptation, ParticleGenerator* particle_generator) - : SolidBody(system, body_name, particle_adaptation, particle_generator) - { - particle_adaptation->getKernel()->reduceOnce(); - } - //=================================================================================================// - SolidBodyPartForSimbody - ::SolidBodyPartForSimbody(SPHBody* solid_body, std::string solid_body_part_name) - : BodyPartByParticle(solid_body, solid_body_part_name) - { - solid_particles_ = dynamic_cast(body_->base_particles_); - Solid* solid = dynamic_cast(body_->base_particles_->base_material_); - solid_body_density_ = solid->ReferenceDensity(); - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/bodies/solid_body.h b/SPHINXsys/src/shared/bodies/solid_body.h deleted file mode 100644 index d6e1241d47..0000000000 --- a/SPHINXsys/src/shared/bodies/solid_body.h +++ /dev/null @@ -1,91 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file solid_body.h - * @brief This is the class for bodies used for solid BCs or Elastic structure. - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - - -#ifndef SOLID_BODY_H -#define SOLID_BODY_H - - -#include "base_body.h" - -namespace SPH { - /** - * @brief Preclaimed class. - */ - class SPHSystem; - class SolidParticles; - /** - * @class SolidBody - * @brief Declaration of solidbody which is used for Solid BCs and derived from RealBody. - */ - class SolidBody : public RealBody - { - public: - SolidBody(SPHSystem &system, std::string body_name, - ParticleAdaptation* particle_adaptation = new ParticleAdaptation(1.15), - ParticleGenerator* particle_generator = new ParticleGeneratorLattice()); - virtual ~SolidBody() {}; - virtual SolidBody* ThisObjectPtr() override {return this;}; - }; - - /** - * @class ThinStructure - * @brief Declaration of thin structure solidbody. - */ - class ThinStructure : public SolidBody - { - public: - ThinStructure(SPHSystem& system, std::string body_name, - ParticleAdaptation* particle_adaptation = new ParticleAdaptation(1.15), - ParticleGenerator* particle_generator = new ParticleGeneratorLattice()); - virtual ~ThinStructure() {}; - virtual ThinStructure* ThisObjectPtr() override {return this;}; - }; - - /** - * @class SolidBodyPartForSimbody - * @brief A SolidBodyPart for coupling with Simbody. - * The mass, origin, and unit inertial matrix are computed. - * Note: In Simbody, all spatial vectors are three dimensional. - */ - class SolidBodyPartForSimbody : public BodyPartByParticle - { - public: - Vec3d initial_mass_center_; - SimTK::MassProperties* body_part_mass_properties_; - - SolidBodyPartForSimbody(SPHBody* body, std::string solid_body_part_name); - virtual~SolidBodyPartForSimbody() {}; - protected: - Real solid_body_density_; - SolidParticles* solid_particles_; - - virtual void tagBodyPart() override; - }; -} -#endif //SOLID_BODY_H diff --git a/SPHINXsys/src/shared/common/CMakeLists.txt b/SPHINXsys/src/shared/common/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/common/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/common/array_allocation.h b/SPHINXsys/src/shared/common/array_allocation.h deleted file mode 100644 index 6ace3c622d..0000000000 --- a/SPHINXsys/src/shared/common/array_allocation.h +++ /dev/null @@ -1,91 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -#ifndef ARRAY_ALLOCATION_H -#define ARRAY_ALLOCATION_H - -#include "small_vectors.h" - -namespace SPH { - //------------------------------------------------------------------------------------------------- - //Allocate and deallocate 3d array - //------------------------------------------------------------------------------------------------- - template - void Allocate3dArray(T*** &matrix, Vec3u res) - { - matrix = new T**[res[0]]; - for (size_t i = 0; i < res[0]; i++) { - matrix[i] = new T*[res[1]]; - for (size_t j = 0; j < res[1]; j++) { - matrix[i][j] = new T[res[2]]; - } - } - } - - template - void Delete3dArray(T*** matrix, Vec3u res) - { - for (size_t i = 0; i < res[0]; i++) { - for (size_t j = 0; j < res[1]; j++) { - delete[] matrix[i][j]; - } - delete[] matrix[i]; - } - delete[] matrix; - } - //------------------------------------------------------------------------------------------------- - // Allocate 2d array - //------------------------------------------------------------------------------------------------- - template - void Allocate2dArray(T** &matrix, Vec2u res) - { - matrix = new T*[res[0]]; - for (size_t i = 0; i < res[0]; i++) { - matrix[i] = new T[res[1]]; - } - } - template - void Delete2dArray(T** matrix, Vec2u res) - { - for (size_t i = 0; i < res[0]; i++) { - delete[] matrix[i]; - } - delete[] matrix; - } - - //------------------------------------------------------------------------------------------------- - // Allocate 1d array - //------------------------------------------------------------------------------------------------- - template - void Allocate1dArray(T* &matrix, size_t res) - { - matrix = new T[res]; - } - template - void Delete1dArray(T* matrix, size_t res) - { - delete[] matrix; - } - -} - -#endif //ARRAY_ALLOCATION_H diff --git a/SPHINXsys/src/shared/common/base_data_package.h b/SPHINXsys/src/shared/common/base_data_package.h deleted file mode 100644 index a9b2c9dbdd..0000000000 --- a/SPHINXsys/src/shared/common/base_data_package.h +++ /dev/null @@ -1,34 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -#ifndef BASE_DATA_PACKAGE_H -#define BASE_DATA_PACKAGE_H - -#include "scalar_functions.h" -#include "data_type.h" -#include "small_vectors.h" -#include "array_allocation.h" -#include "large_data_containers.h" - -#define TBB_PARALLEL true - -#endif //BASE_DATA_PACKAGE_H diff --git a/SPHINXsys/src/shared/common/base_data_type.h b/SPHINXsys/src/shared/common/base_data_type.h deleted file mode 100644 index a9672b7f7c..0000000000 --- a/SPHINXsys/src/shared/common/base_data_type.h +++ /dev/null @@ -1,363 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -#ifndef BASE_DATA_TYPE_H -#define BASE_DATA_TYPE_H - -#include "Simbody.h" -#include "SimTKcommon.h" -#include "SimTKmath.h" -#include "scalar_functions.h" - -#include -#include -#include -#include -#include -#include -#include - -namespace SPH { - - template - class SVec - { - private: - - T v[N]; - - public: - - SVec() - { - for (int i = 0; i < N; ++i) - v[i] = (T)0; - } - - explicit SVec(T value_for_all) - { - for (int i = 0; i < N; ++i) - v[i] = value_for_all; - } - - template - explicit SVec(const S *source) - { - for (int i = 0; i < N; ++i) - v[i] = (T)source[i]; - } - - template - explicit SVec(const SVec& source) - { - for (int i = 0; i < N; ++i) - v[i] = (T)source[i]; - } - - template - explicit SVec(const SimTK::Vec& source) - { - for (int i = 0; i < N; ++i) - v[i] = (T)source[i]; - } - - SVec(T v0, T v1) - { - v[0] = v0; v[1] = v1; - } - - SVec(T v0, T v1, T v2) - { - v[0] = v0; v[1] = v1; v[2] = v2; - } - - T &operator[](int index) - { - assert(index >= 0 && index < N); - return v[index]; - } - - const T &operator[](int index) const - { - assert(index >= 0 && index < N); - return v[index]; - } - - bool nonzero(void) const - { - for (int i = 0; i < N; ++i) - if (v[i]) return true; - return false; - } - - bool operator==(const SVec& b) const - { - bool res = true; - for (int i = 0; i < N; ++i) - res = res && (v[i] == b[i]); - return res; - } - - bool operator!=(const SVec& b) const - { - bool res = false; - for (int i = 0; i < N; ++i) - res = res || (v[i] != b[i]); - return res; - } - - // Arithmetic operators - SVec operator=(const SVec& b) - { - for (int i = 0; i < N; ++i) - v[i] = b.v[i]; - return *this; - } - - SVec operator+(void) const - { - return SVec(*this); - } - - SVec operator+=(T a) - { - for (int i = 0; i < N; ++i) - v[i] += a; - return *this; - } - - SVec operator+(T a) const - { - SVec w(*this); - w += a; - return w; - } - - SVec operator+=(const SVec &w) - { - for (int i = 0; i < N; ++i) - v[i] += w[i]; - return *this; - } - - SVec operator+(const SVec &w) const - { - SVec sum(*this); - sum += w; - return sum; - } - - SVec operator-=(T a) - { - for (int i = 0; i < N; ++i) - v[i] -= a; - return *this; - } - - SVec operator-(T a) const - { - SVec w(*this); - w -= a; - return w; - } - - SVec operator-=(const SVec &w) - { - for (int i = 0; i < N; ++i) - v[i] -= w[i]; - return *this; - } - - // unary minus - SVec operator-(void) const - { - SVec negative; - for (int i = 0; i < N; ++i) - negative.v[i] = -v[i]; - return negative; - } - - // minus - SVec operator-(const SVec &w) const - { - SVec diff(*this); - diff -= w; - return diff; - } - - // scalar product - SVec operator*=(T a) - { - for (int i = 0; i < N; ++i) - v[i] *= a; - return *this; - } - - SVec operator*(T a) const - { - SVec w(*this); - w *= a; - return w; - } - - SVec operator*=(const SVec &w) - { - for (int i = 0; i < N; ++i) - v[i] *= w.v[i]; - return *this; - } - - SVec operator*(const SVec &w) const - { - SVec componentwise_product; - for (int i = 0; i < N; ++i) - componentwise_product[i] = v[i] * w.v[i]; - return componentwise_product; - } - - SVec operator/=(T a) - { - for (int i = 0; i < N; ++i) - v[i] /= a; - return *this; - } - - SVec operator/(T a) const - { - SVec w(*this); - w /= a; - return w; - } - - SVec operator/=(const SVec &w) - { - for (int i = 0; i < N; ++i) - v[i] /= w.v[i]; - return *this; - } - - SVec operator/(const SVec &w) const - { - SVec componentwise_divide; - for (int i = 0; i < N; ++i) - componentwise_divide[i] = v[i] / w.v[i]; - return componentwise_divide; - } - }; - - template - std::ostream &operator<<(std::ostream &out, const SVec &v) - { - out << '[' << v[0]; - for (int i = 1; i < N; ++i) - out << ' ' << v[i]; - out << ']'; - return out; - } - - template - std::istream &operator >> (std::istream &in, SVec &v) - { - in >> v[0]; - for (int i = 1; i < N; ++i) - in >> v[i]; - return in; - } - - //vector with integers - using Vec2i = SVec<2, int>; - using Vec3i = SVec<3, int>; - - //vector with unsigned int - using Vec2u = SVec<2, size_t>; - using Vec3u = SVec<3, size_t>; - - //float point number - using Real = SimTK::Real; - - //useful float point constants s - const Real Pi = Real(M_PI); - using SimTK::Infinity; - using SimTK::Eps; - using SimTK::TinyReal; - constexpr size_t MaxSize_t = std::numeric_limits::max(); - - //vector with float point number - using Vec2d = SimTK::Vec2; - using Vec3d = SimTK::Vec3; - - //small matrix with float point number - using Mat2d = SimTK::Mat22; - using Mat3d = SimTK::Mat33; - //small symmetric matrix with float point number - using SymMat2d = SimTK::SymMat22; - using SymMat3d = SimTK::SymMat33; - - //particle data type index - const int indexScalar = 0; - const int indexVector = 1; - const int indexMatrix = 2; - const int indexInteger = 3; - - //verbal boolean for positive and negative axis directions - const int xAxis = 0; - const int yAxis = 1; - const int zAxis = 2; - const bool positiveDirection = true; - const bool negativeDirection = false; - - - /** - * @class Transform2d - * @brief Coordinate transfrom in 2D - */ - class Transform2d - { - Real rotation_angle_; - Vec2d translation_; - public: - Transform2d(SimTK::Real rotation_angle) - : rotation_angle_(rotation_angle), translation_(0) {}; - Transform2d(SimTK::Real rotation_angle, Vec2d translation) - : rotation_angle_(rotation_angle), translation_(translation) {}; - /** Forward tranformation. */ - Vec2d imposeTransform(Vec2d& origin) { - Vec2d target(origin[0] * cos(rotation_angle_) - origin[1] * sin(rotation_angle_), - origin[1] * cos(rotation_angle_) + origin[0] * sin(rotation_angle_)); - return target + translation_; - }; - /** Inverse tranformation. */ - Vec2d imposeInverseTransform(Vec2d& target) { - Vec2d origin(target[0] * cos(-rotation_angle_) - target[1] * sin(-rotation_angle_), - target[1] * cos(-rotation_angle_) + target[0] * sin(-rotation_angle_)); - return origin - translation_; - }; - }; - - /** - * @class Transform3d - * @brief Coordinate transfrom in 3D from SimTK - */ - using Transform3d = SimTK::Transform; -} - -#endif //BASE_DATA_TYPE_H diff --git a/SPHINXsys/src/shared/common/large_data_containers.h b/SPHINXsys/src/shared/common/large_data_containers.h deleted file mode 100644 index 4a2a3fe9e2..0000000000 --- a/SPHINXsys/src/shared/common/large_data_containers.h +++ /dev/null @@ -1,58 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -#ifndef LARGE_DATA_CONTAINER_H -#define LARGE_DATA_CONTAINER_H - -#include "tbb/tbb.h" -#include "tbb/blocked_range.h" -#include "tbb/blocked_range2d.h" -#include "tbb/blocked_range3d.h" -#include "tbb/parallel_for.h" -#include "tbb/parallel_reduce.h" -#include "tbb/tick_count.h" -#include "tbb/scalable_allocator.h" -#include "tbb/concurrent_unordered_set.h" -#include "tbb/concurrent_vector.h" -#include "tbb/cache_aligned_allocator.h" - -#include - -using namespace tbb; -static tbb::affinity_partitioner ap; - -namespace SPH { - - template - using LargeVec = tbb::concurrent_vector; - - template - using StdLargeVec = std::vector>; - - template - using StdVec = std::vector; - - template - using DataVec = std::vector>; -} - -#endif //LARGE_DATA_CONTAINER_H diff --git a/SPHINXsys/src/shared/common/scalar_functions.cpp b/SPHINXsys/src/shared/common/scalar_functions.cpp deleted file mode 100644 index 768b3893f2..0000000000 --- a/SPHINXsys/src/shared/common/scalar_functions.cpp +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @file scalar_functions.cpp - * @author Xiangyu Hu - * @version 0.1 - */ - -#include "scalar_functions.h" -//=================================================================================================// -namespace SPH { - //=================================================================================================// - int ThirdAxis(int axis_direction) { - return SecondAxis(SecondAxis(axis_direction)); - } - -} diff --git a/SPHINXsys/src/shared/common/scalar_functions.h b/SPHINXsys/src/shared/common/scalar_functions.h deleted file mode 100644 index c23003504b..0000000000 --- a/SPHINXsys/src/shared/common/scalar_functions.h +++ /dev/null @@ -1,184 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -#ifndef SCALAR_FUNCTIONS_H -#define SCALAR_FUNCTIONS_H - -#include -#include -#include -#include - -namespace SPH { - - template - inline T sqr(const T& x) - { - return x * x; - } - - template - inline T cube(const T& x) - { - return x * x * x; - } - - //Get the nth power - template - T powerN(const T& a, int n) - { - T res = 1; - for (int i = 0; i < n; i++) - res *= a; - return res; - } - - //Get the minimum - template - inline T SMIN(T a1, T a2) - { - return (a1 <= a2 ? a1 : a2); - } - - template - inline T SMIN(T a1, T a2, T a3) - { - return SMIN(a1, SMIN(a2, a3)); - } - - template - inline T SMIN(T a1, T a2, T a3, T a4) - { - return SMIN(SMIN(a1, a2), SMIN(a3, a4)); - } - - template - inline T SMIN(T a1, T a2, T a3, T a4, T a5) - { - return SMIN(SMIN(a1, a2), SMIN(a3, a4), a5); - } - - template - inline T SMIN(T a1, T a2, T a3, T a4, T a5, T a6) - { - return SMIN(SMIN(a1, a2), SMIN(a3, a4), SMIN(a5, a6)); - } - - //Get the maximum - template - inline T SMAX(T a1, T a2) - { - return (a1 >= a2 ? a1 : a2); - } - - template - inline T SMAX(T a1, T a2, T a3) - { - return SMAX(a1, SMAX(a2, a3)); - } - - template - inline T SMAX(T a1, T a2, T a3, T a4) - { - return SMAX(SMAX(a1, a2), SMAX(a3, a4)); - } - - template - inline T SMAX(T a1, T a2, T a3, T a4, T a5) - { - return SMAX(SMAX(a1, a2), SMAX(a3, a4), a5); - } - - template - inline T SMAX(T a1, T a2, T a3, T a4, T a5, T a6) - { - return SMAX(SMAX(a1, a2), SMAX(a3, a4), SMAX(a5, a6)); - } - - template - inline void update_minmax(T a1, T& amin, T& amax) - { - amin = SMIN(a1, amin); - amax = SMAX(a1, amax); - } - - template - inline void update_minmax(T a1, T a2, T& amin, T& amax) - { - if (a1 > a2) { - amin = a2; amax = a1; - } - else { - amin = a1; amax = a2; - } - } - - //Get the absolute - template - inline T ABS(const T& x) - { - return SMAX(x, -x); - } - - template - inline T SGN(const T& x) - { - return (x < 0) ? -1 : ((x > 0) ? 1 : 0); - } - /** Heaviside step function */ - template - inline T HSF(const T& x) - { - return 0.5 * (1.0 + SGN(x)); - } - - template - inline T clamp(T a, T lower, T upper) - { - if (a < lower) return lower; - else if (a > upper) return upper; - else return a; - } - - template - inline bool Not_a_number(T a) - { - return (std::isnan(a) || !(std::isfinite(a))) ? true : false; - } - - inline double rand_norm(double u, double std) - { - unsigned seed = (unsigned)std::chrono::system_clock::now().time_since_epoch().count(); - std::default_random_engine generator (seed); - std::normal_distribution distribution(u, std); - return distribution(generator); - } - /** rotating axis once according to right hand rule. - * The axis_direction must be 0, 1 for 2d and 0, 1, 2 for 3d - */ - int SecondAxis(int axis_direction); - /** rotating axis twice according to right hand rule. - * The axis_direction must be 0, 1 for 2d and 0, 1, 2 for 3d - */ - int ThirdAxis(int axis_direction); -} -#endif //SCALAR_FUNCTIONS_H diff --git a/SPHINXsys/src/shared/common/small_vectors.cpp b/SPHINXsys/src/shared/common/small_vectors.cpp deleted file mode 100644 index cabcf1cf0d..0000000000 --- a/SPHINXsys/src/shared/common/small_vectors.cpp +++ /dev/null @@ -1,217 +0,0 @@ -/** - * @file small_vectors.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - * @version 0.1 - */ - -#include "small_vectors.h" -//=================================================================================================// -namespace SPH { - - //=================================================================================================// - Vec2d FirstAxisVector(const Vec2d& zero_vector) - { - return Vec2d(1.0, 0.0); - } - //=================================================================================================// - Vec3d FirstAxisVector(const Vec3d& zero_vector) - { - return Vec3d(1.0, 0.0, 0.0); - }; - //=================================================================================================// - Real getMaxAbsoluteElement(const Vec2d& input) - { - Real max = 0.0; - for (int n = 0; n != input.size(); n++) max = SMAX(fabs(input[n]), max); - return max; - } - //=================================================================================================// - Real getMaxAbsoluteElement(const Vec3d& input) - { - Real max = 0.0; - for (int n = 0; n != input.size(); n++) max = SMAX(fabs(input[n]), max); - return max; - } - //=================================================================================================// - Vec3d upgradeToVector3D(const Real& input) { - return Vec3d(input, 0.0, 0.0); - } - //=================================================================================================// - Vec3d upgradeToVector3D(const Vec2d& input) { - return Vec3d(input[0], input[1], 0.0); - } - //=================================================================================================// - Vec3d upgradeToVector3D(const Vec3d& input) { - return input; - } - //=================================================================================================// - Mat3d upgradeToMatrix3D(const Mat2d& input) { - Mat3d output(0); - output.col(0) = upgradeToVector3D(input.col(0)); - output.col(1) = upgradeToVector3D(input.col(1)); - return output; - } - //=================================================================================================// - Mat3d upgradeToMatrix3D(const Mat3d& input) { - return input; - } - //=================================================================================================// - Mat2d getInverse(const Mat2d& A) - { - Mat2d minv(0); - SimTK::Real det = A(0, 0) * A(1, 1) - A(0, 1) * A(1, 0); - SimTK::Real invdet = 1.0 / det; - minv(0, 0) = A(1, 1) * invdet; - minv(0, 1) = -A(0, 1) * invdet; - minv(1, 0) = -A(1, 0) * invdet; - minv(1, 1) = A(0, 0) * invdet; - return minv; - } - //=================================================================================================// - Mat3d getInverse(const Mat3d& A) - { - SimTK::Real det = A(0, 0) * (A(1, 1) * A(2, 2) - A(2, 1) * A(1, 2)) - - A(0, 1) * (A(1, 0) * A(2, 2) - A(1, 2) * A(2, 0)) + - A(0, 2) * (A(1, 0) * A(2, 1) - A(1, 1) * A(2, 0)); - - SimTK::Real invdet = 1 / det; - Mat3d minv(0); // inverse of matrix m - minv(0, 0) = (A(1, 1) * A(2, 2) - A(2, 1) * A(1, 2)) * invdet; - minv(0, 1) = (A(0, 2) * A(2, 1) - A(0, 1) * A(2, 2)) * invdet; - minv(0, 2) = (A(0, 1) * A(1, 2) - A(0, 2) * A(1, 1)) * invdet; - minv(1, 0) = (A(1, 2) * A(2, 0) - A(1, 0) * A(2, 2)) * invdet; - minv(1, 1) = (A(0, 0) * A(2, 2) - A(0, 2) * A(2, 0)) * invdet; - minv(1, 2) = (A(1, 0) * A(0, 2) - A(0, 0) * A(1, 2)) * invdet; - minv(2, 0) = (A(1, 0) * A(2, 1) - A(2, 0) * A(1, 1)) * invdet; - minv(2, 1) = (A(2, 0) * A(0, 1) - A(0, 0) * A(2, 1)) * invdet; - minv(2, 2) = (A(0, 0) * A(1, 1) - A(1, 0) * A(0, 1)) * invdet; - - return minv; - } - //=================================================================================================// - Mat2d getAverageValue(const Mat2d& A, const Mat2d& B) - { - Mat2d C(1.0); - for (int i = 0; i < 2; i++) - { - for (int j = 0; j < 2; j++) - { - C(i, j) = 2.0 * A(i, j) * B(i, j) / (A(i, j) + B(i, j) + TinyReal); - } - } - return C; - } - //=================================================================================================// - Mat3d getAverageValue(const Mat3d& A, const Mat3d& B) - { - Mat3d C(1.0); - for (int i = 0; i < 3; i++) - { - for (int j = 0; j < 3; j++) - { - C(i, j) = 2.0 * A(i, j) * B(i, j) / (A(i, j) + B(i, j) + TinyReal); - } - } - return C; - } - //=================================================================================================// - Mat2d inverseCholeskyDecomposition(const Mat2d& A) - { - Mat2d lower(0); - int n = 2; - /** Decomposing a matrix into Lower Triangular. */ - for (int i = 0; i < n; i++) - { - for (int j = 0; j < (i + 1); j++) - { - double sum = 0; - for (int k = 0; k < j; k++) - { - sum += lower(i, k) * lower(j, k); - } - if (i == j) - { - lower(i, j) = sqrt(A(i, i) - sum); - } - else - { - lower(i, j) = (1.0 / lower(j, j) * (A(i, j) - sum)); - } - } - } - Mat2d inverse_lower = getInverse(lower); - return inverse_lower; - } - //=================================================================================================// - Mat3d inverseCholeskyDecomposition(const Mat3d& A) - { - Mat3d lower(0); - int n = 3; - /** Decomposing a matrix into Lower Triangular. */ - for (int i = 0; i < n; i++) - { - for (int j = 0; j < (i + 1); j++) - { - double sum = 0; - for (int k = 0; k < j; k++) - { - sum += lower(i, k) * lower(j, k); - } - if (i == j) - { - lower(i, j) = sqrt(A(i, i) - sum); - } - else - { - lower(i, j) = (1.0 / lower(j, j) * (A(i, j) - sum)); - } - } - } - Mat3d inverse_lower = getInverse(lower); - return inverse_lower; - } - //=================================================================================================// - Mat2d getTransformationMatrix(const Vec2d& direction_of_y) - { - Mat2d transformation_matrix(0.0); - transformation_matrix[0][0] = direction_of_y[1]; - transformation_matrix[0][1] = -direction_of_y[0]; - transformation_matrix[1][0] = direction_of_y[0]; - transformation_matrix[1][1] = direction_of_y[1]; - return transformation_matrix; - } - //=================================================================================================// - Mat3d getTransformationMatrix(const Vec3d& direction_of_z) - { - Mat3d transformation_matrix(0.0); - transformation_matrix[0][0] = direction_of_z[2] + powerN(direction_of_z[1], 2) / (1 + direction_of_z[2] + Eps); - transformation_matrix[0][1] = -direction_of_z[0] * direction_of_z[1] / (1 + direction_of_z[2] + Eps); - transformation_matrix[0][2] = -direction_of_z[0]; - transformation_matrix[1][0] = transformation_matrix[0][1]; - transformation_matrix[1][1] = direction_of_z[2] + powerN(direction_of_z[0], 2) / (1 + direction_of_z[2] + Eps); - transformation_matrix[1][2] = -direction_of_z[1]; - transformation_matrix[2][0] = direction_of_z[0]; - transformation_matrix[2][1] = direction_of_z[1]; - transformation_matrix[2][2] = direction_of_z[2]; - return transformation_matrix; - } - //=================================================================================================// - Mat2d getDiagonal(const Mat2d& A) - { - Mat2d diag(1.0); - diag[0][0] = A[0][0]; - diag[1][1] = A[1][1]; - - return diag; - } - Mat3d getDiagonal(const Mat3d& A) - { - Mat3d diag(1.0); - diag[0][0] = A[0][0]; - diag[1][1] = A[1][1]; - diag[2][2] = A[2][2]; - - return diag; - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/common/small_vectors.h b/SPHINXsys/src/shared/common/small_vectors.h deleted file mode 100644 index 83593b4c20..0000000000 --- a/SPHINXsys/src/shared/common/small_vectors.h +++ /dev/null @@ -1,79 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -#ifndef SMALL_VECTORS_H -#define SMALL_VECTORS_H - -#include "base_data_type.h" - -namespace SPH { - - Vec2d FirstAxisVector(const Vec2d& zero_vector); - Vec3d FirstAxisVector(const Vec3d& zero_vector); - Real getMaxAbsoluteElement(const Vec2d& input); - Real getMaxAbsoluteElement(const Vec3d& input); - Vec3d upgradeToVector3D(const Real& input); - Vec3d upgradeToVector3D(const Vec2d& input); - Vec3d upgradeToVector3D(const Vec3d& input); - Mat3d upgradeToMatrix3D(const Mat2d& input); - Mat3d upgradeToMatrix3D(const Mat3d& input); - - template - OutVectorType upgradeVector(const Real& input) - { - OutVectorType out_vector(0); - out_vector[0] = input; - return out_vector; - }; - template - OutVectorType upgradeVector(const Vec2d& input) - { - OutVectorType out_vector(0); - out_vector[0] = input[0]; - out_vector[1] = input[1]; - return out_vector; - }; - template - OutVectorType upgradeVector(const Vec3d& input) - { - OutVectorType out_vector(0); - out_vector[0] = input[0]; - out_vector[1] = input[1]; - out_vector[2] = input[2]; - return out_vector; - }; - - Mat2d getInverse(const Mat2d& A); - Mat3d getInverse(const Mat3d& A); - Mat2d getAverageValue(const Mat2d &A, const Mat2d& B); - Mat3d getAverageValue(const Mat3d &A, const Mat3d& B); - Mat2d inverseCholeskyDecomposition(const Mat2d& A); - Mat3d inverseCholeskyDecomposition(const Mat3d& A); - Mat2d getDiagonal(const Mat2d& A); - Mat3d getDiagonal(const Mat3d& A); - - /** get transformation matrix. */ - Mat2d getTransformationMatrix(const Vec2d& direction_of_y); - Mat3d getTransformationMatrix(const Vec3d& direction_of_z); -} - -#endif //SMALL_VECTORS_H diff --git a/SPHINXsys/src/shared/common/sph_data_conainers.h b/SPHINXsys/src/shared/common/sph_data_conainers.h deleted file mode 100644 index f32a75835c..0000000000 --- a/SPHINXsys/src/shared/common/sph_data_conainers.h +++ /dev/null @@ -1,115 +0,0 @@ -/** - * @file sph_data_conainers.h - * @brief Set up of basic data structure. - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#ifndef SPH_DATA_CONTAINERS_H -#define SPH_DATA_CONTAINERS_H - -#include "base_data_package.h" - -namespace SPH { - /** - * @brief Preclaimed classes. - */ - class BaseMaterial; - class SPHBody; - class RealBody; - class SolidBody; - class BodyPart; - class FictitiousBody; - class CellList; - class BaseParticles; - - /** Bounding box for system, body, body part and shape, first: lower bound, second: upper bound. */ - typedef std::pair BoundingBox; - /** Generalized particle data type */ - typedef std::tuple*>, StdVec*>, StdVec*>, - StdVec*>> ParticleData; - /** Generalized particle variable to index map */ - typedef std::array, 4> ParticleDataMap; - /** Generalized particle variable list */ - typedef std::array>, 4> ParticleVariableList; - /** Vector of Material. Note that vector of references are not allowed in c++.*/ - using MaterialVector = StdVec; - /** Vector of bodies */ - using SPHBodyVector = StdVec; - using SolidBodyVector = StdVec; - using RealBodyVector = StdVec; - using BodyPartVector = StdVec; - using FictitiousBodyVector = StdVec; - - /** Index container with elements of size_t. */ - using IndexVector = StdVec; - /** Concurrent particle indexes .*/ - using ConcurrentIndexVector = LargeVec; - - /** List data pair */ - using ListData = std::pair; - /** Vector of list data pair */ - using ListDataVector = StdLargeVec; - /** Cell lists*/ - using CellLists = StdLargeVec; - - /** Concurrent vector .*/ - template - using ConcurrentVector = LargeVec; - /** concurrent cell lists*/ - using ConcurrentCellLists = LargeVec; - /** Split cell list for split algorithms. */ - using SplitCellLists = StdVec; - /** Pair of point and volume. */ - using PositionsAndVolumes = StdVec>; - - /** loop particle data with operations */ - template typename OperationType, - typename... ParticleArgs> - void loopParticleData(ParticleData& particle_data, ParticleArgs... particle_args) - { - OperationType scalar_operation; - OperationType vector_operation; - OperationType matrix_operation; - OperationType integer_operation; - - scalar_operation(particle_data, particle_args...); - vector_operation(particle_data, particle_args...); - matrix_operation(particle_data, particle_args...); - integer_operation(particle_data, particle_args...); - }; - - /** operation by looping or going through a particle data map */ - template - struct loopParticleDataMap - { - template - void operator () (ParticleData& particle_data, - ParticleDataMap& particle_data_map, VariableOperation& variable_operation) const - { - for (auto const& name_index : particle_data_map[DataTypeIndex]) - { - std::string variable_name = name_index.first; - StdLargeVec& variable = *(std::get(particle_data)[name_index.second]); - variable_operation(variable_name, variable); - } - }; - }; - - /** operation by looping or going through a variable name list */ - template - struct loopVariabaleNameList - { - template - void operator () (ParticleData& particle_data, - ParticleVariableList& variable_name_list, VariableOperation& variable_operation) const - { - for (std::pair& name_index : variable_name_list[DataTypeIndex]) - { - std::string variable_name = name_index.first; - StdLargeVec& variable = *(std::get(particle_data)[name_index.second]); - variable_operation(variable_name, variable); - } - }; - }; -} -#endif //SPH_DATA_CONTAINERS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/generative_structures/generative_structures.cpp b/SPHINXsys/src/shared/generative_structures/generative_structures.cpp deleted file mode 100644 index e7bd6c7f11..0000000000 --- a/SPHINXsys/src/shared/generative_structures/generative_structures.cpp +++ /dev/null @@ -1,270 +0,0 @@ -/** - * @file generative_structures.cpp - * @author Xiangyu Hu - */ - -#include "generative_structures.h" - -#include "base_body.h" -#include "base_particles.h" -#include "particle_adaptation.h" - -namespace SPH -{ - //=================================================================================================// - GenerativeStructure::GenerativeStructure(SPHBody *sph_body) - : sph_body_(sph_body), - spacing_ref_(sph_body_->particle_adaptation_->ReferenceSpacing()), - base_particles_(sph_body->base_particles_), - neighbor_relation_inner_(sph_body), - pos_n_(base_particles_->pos_n_), - Vol_(base_particles_->Vol_){}; - //=================================================================================================// - GenerativeTree::GenerativeTree(SPHBody *sph_body) - : GenerativeStructure(sph_body), last_branch_id_(0) - { - root_ = new Branch(this); - } - //=================================================================================================// - GenerativeTree::~GenerativeTree() - { - for (size_t i = 0; i != branches_.size(); i++) - delete branches_[i]; - } - //=================================================================================================// - void GenerativeTree::buildParticleConfiguration(BaseParticles &base_particles, - ParticleConfiguration &particle_configuration) - { - size_t particle_id; - size_t parent_branch_id; - size_t child_branch_id; - size_t num_ele; - std::vector neighboring_ids; - std::vector child_ids; - /** First branch - * Note that the first branch has only one particle. - * Find the neighbors in child branch, the first branch only have one child, id = 1. - */ - particle_id = branches_[0]->inner_particles_.front(); - neighboring_ids.clear(); - neighboring_ids.push_back(branches_[1]->inner_particles_[0]); - neighboring_ids.push_back(branches_[1]->inner_particles_[1]); - /** Build configuration. */ - Neighborhood &neighborhood = particle_configuration[particle_id]; - for (size_t n = 0; n != neighboring_ids.size(); ++n) - { - Vecd displacement = base_particles.pos_n_[particle_id] - base_particles.pos_n_[neighboring_ids[n]]; - neighbor_relation_inner_(neighborhood, displacement, particle_id, neighboring_ids[n]); - } - /** Second branch. - * The second branch has special parent branch, branch 0, consisting only one point. - * The child branch are two normal branch. - */ - num_ele = branches_[1]->inner_particles_.size(); - child_ids.clear(); - for (size_t k = 0; k < branches_[1]->out_edge_.size(); ++k) - { - child_ids.push_back(branches_[1]->out_edge_[k]); - } - - for (size_t i = 0; i != num_ele; i++) - { - neighboring_ids.clear(); - particle_id = branches_[1]->inner_particles_.front() + i; - if (i == 0) - { - neighboring_ids.push_back(branches_[0]->inner_particles_.front()); - neighboring_ids.push_back(particle_id + 1); - neighboring_ids.push_back(particle_id + 2); - } - else if (i == 1) - { - neighboring_ids.push_back(branches_[0]->inner_particles_.front()); - neighboring_ids.push_back(particle_id - 1); - neighboring_ids.push_back(particle_id + 1); - neighboring_ids.push_back(particle_id + 2); - } - else if (2 <= i && i <= (num_ele - 3)) - { - neighboring_ids.push_back(particle_id - 1); - neighboring_ids.push_back(particle_id - 2); - neighboring_ids.push_back(particle_id + 1); - neighboring_ids.push_back(particle_id + 2); - } - else if (i == (num_ele - 2)) - { - neighboring_ids.push_back(particle_id - 2); - neighboring_ids.push_back(particle_id - 1); - neighboring_ids.push_back(particle_id + 1); - - for (size_t k = 0; k < branches_[1]->out_edge_.size(); ++k) - { - child_branch_id = branches_[1]->out_edge_[k]; - neighboring_ids.push_back(branches_[child_branch_id]->inner_particles_.front()); - } - } - else if (i == (num_ele - 1)) - { - neighboring_ids.push_back(particle_id - 1); - neighboring_ids.push_back(particle_id - 2); - - for (size_t k = 0; k < branches_[1]->out_edge_.size(); ++k) - { - child_branch_id = branches_[1]->out_edge_[k]; - neighboring_ids.push_back(branches_[child_branch_id]->inner_particles_.front()); - neighboring_ids.push_back(branches_[child_branch_id]->inner_particles_.front() + 1); - } - } - - Neighborhood &neighborhood = particle_configuration[particle_id]; - for (size_t n = 0; n != neighboring_ids.size(); ++n) - { - Vecd displacement = base_particles.pos_n_[particle_id] - base_particles.pos_n_[neighboring_ids[n]]; - neighbor_relation_inner_(neighborhood, displacement, particle_id, neighboring_ids[n]); - } - } - /** Other branches. - * They are may normal branch (fully growed, has child and parent) or non-fully growed branch - */ - for (size_t branch_idx = 2; branch_idx != branches_.size(); ++branch_idx) - { - num_ele = branches_[branch_idx]->inner_particles_.size(); - parent_branch_id = branches_[branch_idx]->in_edge_; - if (!branches_[branch_idx]->is_terminated_) - { - /** This branch is fully growed. */ - for (size_t i = 0; i != num_ele; i++) - { - neighboring_ids.clear(); - particle_id = branches_[branch_idx]->inner_particles_.front() + i; - if (i == 0) - { - neighboring_ids.push_back(branches_[parent_branch_id]->inner_particles_.back()); - neighboring_ids.push_back(branches_[parent_branch_id]->inner_particles_.back() - 1); - - neighboring_ids.push_back(particle_id + 1); - neighboring_ids.push_back(particle_id + 2); - } - else if (i == 1) - { - neighboring_ids.push_back(branches_[parent_branch_id]->inner_particles_.back()); - neighboring_ids.push_back(particle_id - 1); - neighboring_ids.push_back(particle_id + 1); - neighboring_ids.push_back(particle_id + 2); - } - else if (2 <= i && i <= (num_ele - 3)) - { - neighboring_ids.push_back(particle_id - 1); - neighboring_ids.push_back(particle_id - 2); - neighboring_ids.push_back(particle_id + 1); - neighboring_ids.push_back(particle_id + 2); - } - else if (i == (num_ele - 2)) - { - neighboring_ids.push_back(particle_id - 2); - neighboring_ids.push_back(particle_id - 1); - neighboring_ids.push_back(particle_id + 1); - - for (size_t k = 0; k < branches_[branch_idx]->out_edge_.size(); ++k) - { - child_branch_id = branches_[branch_idx]->out_edge_[k]; - neighboring_ids.push_back(branches_[child_branch_id]->inner_particles_.front()); - } - } - else if (i == (num_ele - 1)) - { - neighboring_ids.push_back(particle_id - 1); - neighboring_ids.push_back(particle_id - 2); - - for (size_t k = 0; k < branches_[branch_idx]->out_edge_.size(); ++k) - { - child_branch_id = branches_[branch_idx]->out_edge_[k]; - neighboring_ids.push_back(branches_[child_branch_id]->inner_particles_.front()); - if (branches_[child_branch_id]->inner_particles_.size() >= 2) - { - neighboring_ids.push_back(branches_[child_branch_id]->inner_particles_.front() + 1); - } - } - } - - Neighborhood &neighborhood = particle_configuration[particle_id]; - for (size_t n = 0; n != neighboring_ids.size(); ++n) - { - Vecd displacement = base_particles.pos_n_[particle_id] - base_particles.pos_n_[neighboring_ids[n]]; - neighbor_relation_inner_(neighborhood, displacement, particle_id, neighboring_ids[n]); - } - } - } - else - { - /** This branch is not fully growed. */ - for (size_t i = 0; i != num_ele; i++) - { - neighboring_ids.clear(); - particle_id = branches_[branch_idx]->inner_particles_.front() + i; - if (i == 0) - { - neighboring_ids.push_back(branches_[parent_branch_id]->inner_particles_.back()); - if (branches_[parent_branch_id]->inner_particles_.size() >= 2) - neighboring_ids.push_back(branches_[parent_branch_id]->inner_particles_.back() - 1); - } - else if (i == 1) - { - neighboring_ids.push_back(branches_[parent_branch_id]->inner_particles_.back()); - neighboring_ids.push_back(particle_id - 1); - } - else - { - neighboring_ids.push_back(particle_id - 1); - neighboring_ids.push_back(particle_id - 2); - } - - if (i + 1 < num_ele) - neighboring_ids.push_back(particle_id + 1); - if (i + 2 < num_ele) - neighboring_ids.push_back(particle_id + 2); - - Neighborhood &neighborhood = particle_configuration[particle_id]; - for (size_t n = 0; n != neighboring_ids.size(); ++n) - { - Vecd displacement = base_particles.pos_n_[particle_id] - base_particles.pos_n_[neighboring_ids[n]]; - neighbor_relation_inner_(neighborhood, displacement, particle_id, neighboring_ids[n]); - } - } - } - } - } - //=================================================================================================// - void GenerativeTree:: - growAParticleOnBranch(Branch *branch, const Vecd &new_point, const Vecd &end_direction) - { - base_particles_->initializeABaseParticle(new_point, spacing_ref_); - branch_locations_.push_back(branch->id_); - branch->inner_particles_.push_back(pos_n_.size() - 1); - branch->end_direction_ = end_direction; - } - //=================================================================================================// - size_t GenerativeTree::BranchLocation(size_t particle_idx) - { - return particle_idx < pos_n_.size() ? branch_locations_[particle_idx] : MaxSize_t; - } - //=================================================================================================// - GenerativeTree::Branch::Branch(GenerativeTree *tree) - : Edge(), is_terminated_(false) - { - in_edge_ = 0; - id_ = 0; - tree->branches_.push_back(this); - tree->last_branch_id_ = id_; - } - //=================================================================================================// - GenerativeTree::Branch::Branch(size_t parent_id, GenerativeTree *tree) - : Edge(parent_id), is_terminated_(false) - { - id_ = tree->branches_.size(); - tree->branches_[parent_id]->out_edge_.push_back(id_); - tree->branches_.push_back(this); - tree->last_branch_id_ = id_; - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/generative_structures/generative_structures.h b/SPHINXsys/src/shared/generative_structures/generative_structures.h deleted file mode 100644 index c9bcbb4977..0000000000 --- a/SPHINXsys/src/shared/generative_structures/generative_structures.h +++ /dev/null @@ -1,118 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file generative_structures.h -* @brief In generative structures, the particles and, if necessary, -* and their neighnor relations or particle configuration -* can be generated directly form the information of the structure. -* @author Xiangyu Hu -*/ - -#ifndef GENERATIVE_STRUCTURES_H -#define GENERATIVE_STRUCTURES_H - -#include "base_geometry.h" -#include "neighbor_relation.h" - -namespace SPH -{ - - class SPHBody; - class BaseParticles; - - /** - * @class GenerativeStructure - * @brief Abstract class as interface for all generative structures. - * It is linked with particles by particle position and volume - */ - class GenerativeStructure - { - public: - explicit GenerativeStructure(SPHBody *sph_body); - virtual ~GenerativeStructure(){}; - - virtual void buildParticleConfiguration(BaseParticles &base_particles, - ParticleConfiguration &particle_configuration) = 0; - - protected: - SPHBody *sph_body_; - Real spacing_ref_; - BaseParticles *base_particles_; - NeighborRelationInner neighbor_relation_inner_; - - public: - StdLargeVec &pos_n_; /**< current position */ - StdLargeVec &Vol_; /**< particle volume */ - }; - - /** - * @class GenerativeTree - * @brief The tree is composed of a root (the first branch) - * and other branch generated sequentially. - */ - class GenerativeTree : public GenerativeStructure - { - public: - class Branch; - StdVec branches_; /**< list of all branches */ - IndexVector branch_locations_; /**< in which branch are the particles located */ - size_t last_branch_id_; - Branch *root_; - - explicit GenerativeTree(SPHBody *sph_body); - virtual ~GenerativeTree(); - - void growAParticleOnBranch(Branch *branch, const Vecd &new_point, const Vecd &end_direction); - size_t BranchLocation(size_t particle_idx); - Branch *LastBranch() { return branches_[last_branch_id_]; }; - - virtual void buildParticleConfiguration(BaseParticles &base_particles, - ParticleConfiguration &particle_configuration) override; - }; - - /** - * @class GenerativeTree::Branch - * @brief Each branch (excapt the root) has a parent and several children, and geometric information. - * It is a realized edge and has multi inner particles. - * The first is the last particle from the parent or root, - * and the last is the first particle of all its child branches. - * Many connected branches compose a tree. */ - class GenerativeTree::Branch : public Edge - { - public: - /** construct the root branch */ - Branch(GenerativeTree *tree); - /** construct an branch connecting with its parent */ - Branch(size_t parent_id, GenerativeTree *tree); - virtual ~Branch(){}; - - Vecd end_direction_; /**< the direction pointing to the last particle */ - /** The indexes of particle within this branch. - * The first is the last particle from the parent or root, - * and the last is the first of all its child branches. */ - IndexVector inner_particles_; - bool is_terminated_; /**< whether is an terminate branch or not */ - }; - -} -#endif //GENERATIVE_STRUCTURES_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/geometries/CMakeLists.txt b/SPHINXsys/src/shared/geometries/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/geometries/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/geometries/all_geometries.h b/SPHINXsys/src/shared/geometries/all_geometries.h deleted file mode 100644 index 8c2adbb87f..0000000000 --- a/SPHINXsys/src/shared/geometries/all_geometries.h +++ /dev/null @@ -1,12 +0,0 @@ - -#ifndef ALL_GEOMETRIES_H -#define ALL_GEOMETRIES_H - -/** @file -This is the header file that user code should include to pick up all -geometry classes used in SPHinXsys. **/ - -#include "geometry.h" -#include "geometry_level_set.h" - -#endif //ALL_GEOMETRIES_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/geometries/base_geometry.h b/SPHINXsys/src/shared/geometries/base_geometry.h deleted file mode 100644 index b43f7c58d1..0000000000 --- a/SPHINXsys/src/shared/geometries/base_geometry.h +++ /dev/null @@ -1,91 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file base_geometry.h -* @brief Shape is the base class for all geometries. -* @details Several pure virtual functions -* are defined here. (a) closet point on surface: to find the closet point on shape -* surface to a given point. (b) find the lower and upper bounds. -* @author Chi ZHang and Xiangyu Hu -*/ - - -#ifndef BASE_GEOMETRY_H -#define BASE_GEOMETRY_H - - -#include "base_particles.h" -#include "base_data_package.h" -#include "sph_data_conainers.h" - -#include - -namespace SPH -{ - class Tree; - class Neighborhood; - /** - * @class ShapeBooleanOps - * @brief Boolian operation for generate complex shapes - * @details Note that, for 3D applications, only add and sub boolean operation have been defined right now - */ - enum class ShapeBooleanOps { add, sub, sym_diff, intersect }; - - /** - * @class Shape - * @brief Base class for all geometries - */ - class Shape - { - public: - Shape(std::string shape_name) : name_(shape_name) {}; - virtual ~Shape() {}; - - std::string getName() { return name_; }; - virtual BoundingBox findBounds() = 0; - protected: - std::string name_; - }; - - /** - * @class Edge - * @brief template base class of linear structure - * only with topology information - */ - template - class Edge - { - public: - Edge() : id_(0) {}; /**< constructor without specifying a leading-in edge */ - Edge(InEdgeType in_edge) : Edge() /**< constructor with specifying a leading-in edge */ - { - in_edge_ = in_edge; - }; - virtual ~Edge() {}; - - size_t id_; /**< id of this edge */ - InEdgeType in_edge_; /**< id(s) of parent edge(s) */ - OutEdgeType out_edge_; /**< id(s) of child edge(s) */ - }; -} -#endif //BASE_GEOMETRY_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/geometries/geometry_level_set.cpp b/SPHINXsys/src/shared/geometries/geometry_level_set.cpp deleted file mode 100644 index 18baf6d912..0000000000 --- a/SPHINXsys/src/shared/geometries/geometry_level_set.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/** - * @file geometry_level_set.cpp - * @author Chi ZHang and Xiangyu Hu - */ - -#include "geometry_level_set.h" - -#include "level_set.h" -#include "base_body.h" -#include "in_output.h" -#include "sph_system.h" - - -namespace SPH { - //=================================================================================================// - LevelSetComplexShape:: - LevelSetComplexShape(SPHBody* sph_body, ComplexShape& complex_shape, bool isCleaned) - : ComplexShape(complex_shape), level_set_(nullptr) - { - name_ = sph_body->getBodyName(); - level_set_ = sph_body->particle_adaptation_->createLevelSet(complex_shape); - if (isCleaned) level_set_->cleanInterface(); - - In_Output* in_output = sph_body->getSPHSystem().in_output_; - MeshRecordingToPlt write_level_set(*in_output, sph_body, level_set_); - write_level_set.writeToFile(0); - } - //=================================================================================================// - bool LevelSetComplexShape::checkContain(Vecd& input_pnt, bool BOUNDARY_INCLUDED) - { - return level_set_->probeSignedDistance(input_pnt) < 0.0 ? true : false; - } - //=================================================================================================// - bool LevelSetComplexShape::checkNearSurface(Vecd& input_pnt, Real threshold) - { - if(!checkNotFar(input_pnt, threshold)) return false; - return getMaxAbsoluteElement(findSignedDistance(input_pnt) - * findNormalDirection(input_pnt)) < threshold ? true : false; - } - //=================================================================================================// - Real LevelSetComplexShape::findSignedDistance(Vecd& input_pnt) - { - return level_set_->probeSignedDistance(input_pnt); - } - //=================================================================================================// - Vecd LevelSetComplexShape::findNormalDirection(Vecd& input_pnt) - { - return level_set_->probeNormalDirection(input_pnt); - } - //=================================================================================================// - bool LevelSetComplexShape::checkNotFar(Vecd& input_pnt, Real threshold) - { - return level_set_->isWithinMeshBound(input_pnt); - } - //=================================================================================================// - Real LevelSetComplexShape::computeKernelIntegral(Vecd& input_pnt, Real h_ratio) - { - return level_set_->probeKernelIntegral(input_pnt, h_ratio); - } - //=================================================================================================// - Vecd LevelSetComplexShape::computeKernelGradientIntegral(Vecd& input_pnt, Real h_ratio) - { - return level_set_->probeKernelGradientIntegral(input_pnt,h_ratio); - } -} diff --git a/SPHINXsys/src/shared/geometries/geometry_level_set.h b/SPHINXsys/src/shared/geometries/geometry_level_set.h deleted file mode 100644 index 254295a7fc..0000000000 --- a/SPHINXsys/src/shared/geometries/geometry_level_set.h +++ /dev/null @@ -1,44 +0,0 @@ -/** -* @file geometry_level_set.h -* @brief Here, we define geometry based on level set technique. -* @author Luhui Han, Chi ZHang and Xiangyu Hu -*/ - - -#ifndef GEOMETRY_LEVEL_SET_H -#define GEOMETRY_LEVEL_SET_H - - - -#include "geometry.h" - -#include - -namespace SPH { - - class SPHBody; - class BaseLevelSet; - /** - * @class LevelSetComplexShape - * @brief the final geomtrical definition of the SPHBody based on a narrow band level set function - * generated from the original ComplexShape - */ - class LevelSetComplexShape : public ComplexShape - { - public: - LevelSetComplexShape(SPHBody* sph_body, ComplexShape &complex_shape, bool isCleaned = false); - virtual ~LevelSetComplexShape() {}; - - virtual bool checkContain(Vecd& input_pnt, bool BOUNDARY_INCLUDED = true) override; - virtual bool checkNotFar(Vecd& input_pnt, Real threshold) override; - virtual bool checkNearSurface(Vecd& input_pnt, Real threshold) override; - virtual Real findSignedDistance(Vecd& input_pnt) override; - virtual Vecd findNormalDirection(Vecd& input_pnt) override; - virtual Real computeKernelIntegral(Vecd& input_pnt, Real h_ratio = 1.0); - virtual Vecd computeKernelGradientIntegral(Vecd& input_pnt, Real h_ratio = 1.0); - protected: - BaseLevelSet* level_set_; /**< narrow bounded levelset mesh. */ - }; -} - -#endif //GEOMETRY_LEVEL_SET_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/geometries/level_set.cpp b/SPHINXsys/src/shared/geometries/level_set.cpp deleted file mode 100644 index 0bea71a4e5..0000000000 --- a/SPHINXsys/src/shared/geometries/level_set.cpp +++ /dev/null @@ -1,247 +0,0 @@ -/** - * @file level_set.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#include "level_set.h" -#include "base_body.h" -#include "particle_adaptation.h" - -namespace SPH { - //=================================================================================================// - LevelSetDataPackage:: - LevelSetDataPackage() : BaseDataPackage<4, 6>(), is_core_pkg_(false) - { - initializePackageDataAddress(phi_, phi_addrs_); - initializePackageDataAddress(n_, n_addrs_); - initializePackageDataAddress(kernel_weight_, kernel_weight_addrs_); - initializePackageDataAddress(kernel_gradient_, kernel_gradient_addrs_); - initializePackageDataAddress(near_interface_id_, near_interface_id_addrs_); - } - //=================================================================================================// - void LevelSetDataPackage:: - assignAllPackageDataAddress(Vecu addrs_index, LevelSetDataPackage* src_pkg, Vecu data_index) - { - assignPackageDataAddress(phi_addrs_, addrs_index, src_pkg->phi_, data_index); - assignPackageDataAddress(n_addrs_, addrs_index, src_pkg->n_, data_index); - assignPackageDataAddress(kernel_weight_addrs_, addrs_index, src_pkg->kernel_weight_, data_index); - assignPackageDataAddress(kernel_gradient_addrs_, addrs_index, src_pkg->kernel_gradient_, data_index); - assignPackageDataAddress(near_interface_id_addrs_, addrs_index, src_pkg->near_interface_id_, data_index); - } - //=================================================================================================// - void LevelSetDataPackage::computeNormalDirection() - { - computeNormalizedGradient(phi_addrs_, n_addrs_); - } - //=================================================================================================// - BaseLevelSet - ::BaseLevelSet(BoundingBox tentative_bounds, Real data_spacing, - ComplexShape& complex_shape, ParticleAdaptation& particle_adaptation) - : Mesh(tentative_bounds, LevelSetDataPackage().PackageSize() * data_spacing, 4), - complex_shape_(complex_shape), particle_adaptation_(particle_adaptation) - { - name_ = "BaseLevelSet"; - } - //=================================================================================================// - LevelSet::LevelSet(BoundingBox tentative_bounds, Real data_spacing, - ComplexShape& complex_shape, ParticleAdaptation& particle_adaptation) - : MeshWithDataPackages(tentative_bounds, data_spacing, - complex_shape, particle_adaptation), - global_h_ratio_(particle_adaptation.ReferenceSpacing() / data_spacing), - kernel_(*particle_adaptation.getKernel()) - { - name_ = "LevelSet"; - Real far_field_distance = grid_spacing_ * (Real)buffer_width_; - LevelSetDataPackage* negative_far_field = new LevelSetDataPackage(); - negative_far_field->initializeWithUniformData(-far_field_distance); - singular_data_pkgs_addrs.push_back(negative_far_field); - LevelSetDataPackage* positive_far_field = new LevelSetDataPackage(); - positive_far_field->initializeWithUniformData(far_field_distance); - singular_data_pkgs_addrs.push_back(positive_far_field); - initializeDataPackages(); - } - //=================================================================================================// - void LevelSet::initializeDataPackages() - { - MeshFunctor initialize_data_in_a_cell = std::bind(&LevelSet::initializeDataInACell, this, _1, _2); - MeshIterator_parallel(Vecu(0), number_of_cells_, initialize_data_in_a_cell); - MeshFunctor tag_a_cell_inner_pkg = std::bind(&LevelSet::tagACellIsInnerPackage, this, _1, _2); - MeshIterator_parallel(Vecu(0), number_of_cells_, tag_a_cell_inner_pkg); - MeshFunctor initial_address_in_a_cell = std::bind(&LevelSet::initializeAddressesInACell, this, _1, _2); - MeshIterator_parallel(Vecu(0), number_of_cells_, initial_address_in_a_cell); - updateNormalDirection(); - updateKernelIntegrals(); - } - //=================================================================================================// - void LevelSet::initializeAddressesInACell(Vecu cell_index, Real dt) - { - initializePackageAddressesInACell(cell_index); - } - //=================================================================================================// - void LevelSet::updateNormalDirection() - { - PackageFunctor update_normal_diraction - = std::bind(&LevelSet::updateNormalDirectionForAPackage, this, _1, _2); - PackageIterator_parallel(inner_data_pkgs_, update_normal_diraction); - } - //=================================================================================================// - void LevelSet::updateKernelIntegrals() - { - PackageFunctor update_kernel_value - = std::bind(&LevelSet::updateKernelIntegralsForAPackage, this, _1, _2); - PackageIterator_parallel(inner_data_pkgs_, update_kernel_value); - } - //=================================================================================================// - Vecd LevelSet::probeNormalDirection(const Vecd& position) - { - return probeMesh, &LevelSetDataPackage::n_addrs_>(position); - } - //=================================================================================================// - Real LevelSet::probeSignedDistance(const Vecd& position) - { - return probeMesh, &LevelSetDataPackage::phi_addrs_>(position); - - } - //=================================================================================================// - Real LevelSet::probeKernelIntegral(const Vecd& position, Real h_ratio) - { - return probeMesh, &LevelSetDataPackage::kernel_weight_addrs_>(position); - - } - //=================================================================================================// - Vecd LevelSet::probeKernelGradientIntegral(const Vecd& position, Real h_ratio) - { - return probeMesh, &LevelSetDataPackage::kernel_gradient_addrs_>(position); - } - //=================================================================================================// - void LevelSet:: - updateNormalDirectionForAPackage(LevelSetDataPackage* inner_data_pkg, Real dt) - { - inner_data_pkg->computeNormalDirection(); - } - //=================================================================================================// - void LevelSet:: - updateKernelIntegralsForAPackage(LevelSetDataPackage* inner_data_pkg, Real dt) - { - inner_data_pkg->computeKernelIntegrals(*this); - } - //=================================================================================================// - void LevelSet:: - stepReinitializationForAPackage(LevelSetDataPackage* inner_data_pkg, Real dt) - { - inner_data_pkg->stepReinitialization(); - } - //=============================================================================================// - void LevelSet::reinitializeLevelSet() - { - PackageFunctor reinitialize_levelset - = std::bind(&LevelSet::stepReinitializationForAPackage, this, _1, _2); - for (size_t i = 0; i < 50; ++i) - PackageIterator_parallel(inner_data_pkgs_, reinitialize_levelset); - } - //=================================================================================================// - void LevelSet::markNearInterface() - { - PackageFunctor mark_cutcell_by_levelset - = std::bind(&LevelSet::markNearInterfaceForAPackage, this, _1, _2); - PackageIterator_parallel(core_data_pkgs_, mark_cutcell_by_levelset); - } - //=================================================================================================// - void LevelSet::markNearInterfaceForAPackage(LevelSetDataPackage* core_data_pkg, Real dt) - { - core_data_pkg->markNearInterface(); - } - //=================================================================================================// - void LevelSet::redistanceInterface() - { - PackageFunctor clean_levelset - = std::bind(&LevelSet::redistanceInterfaceForAPackage, this, _1, _2); - PackageIterator_parallel(core_data_pkgs_, clean_levelset); - } - //=================================================================================================// - Real LevelSet::computeHeaviside(Real phi, Real half_width) - { - Real heaviside = 0.0; - Real normalized_phi = phi / half_width; - if (phi < half_width && phi > -half_width) - heaviside = (0.5 + 0.5 * normalized_phi) + 0.5 * sin(Pi * normalized_phi) / Pi; - if (normalized_phi > 1.0) heaviside = 1.0; - return heaviside; - } - //=================================================================================================// - void LevelSet::cleanInterface(bool isSmoothed) - { - markNearInterface(); - redistanceInterface(); - reinitializeLevelSet(); - updateNormalDirection(); - updateKernelIntegrals(); - } - //=============================================================================================// - MultilevelLevelSet:: - MultilevelLevelSet(BoundingBox tentative_bounds, Real reference_data_spacing, - size_t total_levels, Real maximum_spacing_ratio, - ComplexShape& complex_shape, ParticleAdaptation& particle_adaptation) - : MultilevelMesh(tentative_bounds, reference_data_spacing, - total_levels, maximum_spacing_ratio, complex_shape, particle_adaptation) - { - name_ = "MultilevelLevelSet"; - } - //=================================================================================================// - size_t MultilevelLevelSet::getMeshLevel(Real h_ratio) - { - for (size_t level = total_levels_; level != 0; --level) - if (h_ratio - mesh_levels_[level - 1]->global_h_ratio_ > - Eps) return level - 1; //jump out the loop! - - std::cout << "\n Error: LevelSet level searching out of bound!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - return 999; //means an error in level searching - }; - //=============================================================================================// - Real MultilevelLevelSet::probeSignedDistance(const Vecd& position) - { - return mesh_levels_[getProbeLevel(position)]->probeSignedDistance(position); - } - //=============================================================================================// - Vecd MultilevelLevelSet::probeNormalDirection(const Vecd& position) - { - return mesh_levels_[getProbeLevel(position)]->probeNormalDirection(position); - } - //=============================================================================================// - size_t MultilevelLevelSet::getProbeLevel(const Vecd& position) - { - for (size_t level = total_levels_; level != 0; --level) - if (mesh_levels_[level - 1]->isWithinCorePackage(position)) return level - 1; //jump out of the loop! - return 0; - } - //=================================================================================================// - void MultilevelLevelSet::cleanInterface(bool isSmoothed) - { - //current only implement this, to be update later together with particle adaptation - return mesh_levels_[total_levels_ - 1]->cleanInterface(isSmoothed); - } - //=================================================================================================// - Real MultilevelLevelSet::probeKernelIntegral(const Vecd& position, Real h_ratio) - { - size_t coarse_level = getMeshLevel(h_ratio); - Real alpha = (mesh_levels_[coarse_level + 1]->global_h_ratio_ - h_ratio) - / (mesh_levels_[coarse_level + 1]->global_h_ratio_ - mesh_levels_[coarse_level]->global_h_ratio_); - Real coarse_level_value = mesh_levels_[coarse_level]->probeKernelIntegral(position); - Real fine_level_value = mesh_levels_[coarse_level + 1]->probeKernelIntegral(position); - - return alpha * coarse_level_value + (1.0 - alpha) * fine_level_value; - } - //=================================================================================================// - Vecd MultilevelLevelSet::probeKernelGradientIntegral(const Vecd& position, Real h_ratio) - { - size_t coarse_level = getMeshLevel(h_ratio); - Real alpha = (mesh_levels_[coarse_level + 1]->global_h_ratio_ - h_ratio) - / (mesh_levels_[coarse_level + 1]->global_h_ratio_ - mesh_levels_[coarse_level]->global_h_ratio_); - Vecd coarse_level_value = mesh_levels_[coarse_level]->probeKernelGradientIntegral(position); - Vecd fine_level_value = mesh_levels_[coarse_level + 1]->probeKernelGradientIntegral(position); - - return alpha * coarse_level_value + (1.0 - alpha) * fine_level_value; - } - //=============================================================================================// -} diff --git a/SPHINXsys/src/shared/geometries/level_set.h b/SPHINXsys/src/shared/geometries/level_set.h deleted file mode 100644 index 83d5d07309..0000000000 --- a/SPHINXsys/src/shared/geometries/level_set.h +++ /dev/null @@ -1,175 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file level_set.h -* @brief This is the base classes of mesh, which describe ordered and indexed -* data sets. Depending on application, there are different data -* saved on the mesh. The intersection points of mesh lines are called -* grid points, the element enclosed by mesh lines (2D) or faces (3D) called -* cells. The mesh line or face are also called cell faces. Grid points are -* also called cell corners. -* @author Chi ZHang and Xiangyu Hu -*/ - - -#ifndef LEVEL_SET_H -#define LEVEL_SET_H - - - -#include "mesh_with_data_packages.h" -#include "mesh_with_data_packages.hpp" -#include "geometry.h" - -namespace SPH -{ - class LevelSet; - class Kernel; - /** - * @class LevelSetDataPackage - * @brief Fixed memory level set data packed in a package. - * Level set is the signed distance to an interface, - * here, the surface of a body. - */ - class LevelSetDataPackage : public BaseDataPackage<4, 6> - { - public: - bool is_core_pkg_; /**< If true, the package is near to zero level set. */ - PackageData phi_; /**< the level set or signed distance. */ - PackageDataAddress phi_addrs_; /**< address for the level set. */ - PackageData n_; /**< level set normalized gradient, to approximate interface normal direction */ - PackageDataAddress n_addrs_; - PackageData kernel_weight_; - PackageDataAddress kernel_weight_addrs_; - PackageData kernel_gradient_; - PackageDataAddress kernel_gradient_addrs_; - /** mark the near interface cells. 0 for zero level set cut cells, - * -1 and 1 for negative and positive cut cells, - * 0 can also be for other cells in the region closed - * by negative and positive cut cells - */ - PackageData near_interface_id_; - PackageDataAddress near_interface_id_addrs_; - - LevelSetDataPackage(); - virtual ~LevelSetDataPackage() {}; - - void assignAllPackageDataAddress(Vecu data_index, LevelSetDataPackage* src_pkg, Vecu addrs_index); - void initializeBasicData(ComplexShape& complex_shape); - void initializeWithUniformData(Real level_set); - void computeKernelIntegrals(LevelSet& level_set); - void computeNormalDirection(); - void stepReinitialization(); - void markNearInterface(); - }; - - /** - * @class BaseLevelSet - * @brief A abstract describes a mesh with level set data packages. - */ - class BaseLevelSet : public Mesh - { - public: - BaseLevelSet(BoundingBox tentative_bounds, Real data_spacing, - ComplexShape& complex_shape, ParticleAdaptation& particle_adaptation); - virtual ~BaseLevelSet() {}; - - virtual Real probeSignedDistance(const Vecd& position) = 0; - virtual Vecd probeNormalDirection(const Vecd& position) = 0; - virtual Real probeKernelIntegral(const Vecd& position, Real h_ratio = 1.0) = 0; - virtual Vecd probeKernelGradientIntegral(const Vecd& position, Real h_ratio = 1.0) = 0; - virtual void cleanInterface(bool isSmoothed = false) = 0; - protected: - ComplexShape& complex_shape_; /**< the geometry is described by the level set. */ - ParticleAdaptation& particle_adaptation_; - }; - - /** - * @class LevelSet - * @brief Mesh with level set data as packages. - */ - class LevelSet - : public MeshWithDataPackages - { - public: - ConcurrentVector core_data_pkgs_; /**< packages near to zero level set. */ - Real global_h_ratio_; - - LevelSet(BoundingBox tentative_bounds, Real data_spacing, - ComplexShape& complex_shape, ParticleAdaptation& particle_adaptation); - virtual ~LevelSet() {}; - - virtual Real probeSignedDistance(const Vecd& position) override; - virtual Vecd probeNormalDirection(const Vecd& position) override; - virtual Real probeKernelIntegral(const Vecd& position, Real h_ratio = 1.0) override; - virtual Vecd probeKernelGradientIntegral(const Vecd& position, Real h_ratio = 1.0) override; - virtual void cleanInterface(bool isSmoothed = false) override; - virtual void writeMeshToPltFile(std::ofstream& output_file) override; - bool isWithinCorePackage(Vecd position); - Real computeKernelIntegral(const Vecd& position); - Vecd computeKernelGradientIntegral(const Vecd& position); - protected: - Kernel& kernel_; - - Real computeHeaviside(Real phi, Real half_width); - void reinitializeLevelSet(); - void markNearInterface(); - void redistanceInterface(); - void updateNormalDirection(); - void updateNormalDirectionForAPackage(LevelSetDataPackage* inner_data_pkg, Real dt = 0.0); - void updateKernelIntegrals(); - void updateKernelIntegralsForAPackage(LevelSetDataPackage* inner_data_pkg, Real dt = 0.0); - void stepReinitializationForAPackage(LevelSetDataPackage* inner_data_pkg, Real dt = 0.0); - void markNearInterfaceForAPackage(LevelSetDataPackage* core_data_pkg, Real dt = 0.0); - void redistanceInterfaceForAPackage(LevelSetDataPackage* core_data_pkg, Real dt = 0.0); - virtual void initializeDataInACell(Vecu cell_index, Real dt) override; - virtual void initializeAddressesInACell(Vecu cell_index, Real dt) override; - virtual void tagACellIsInnerPackage(Vecu cell_index, Real dt) override; - virtual void initializeDataPackages() override; - }; - - /** - * @class MultilevelMeshCellLinkedList - * @brief Defining a multilevel level set for a complex region. - */ - class MultilevelLevelSet : - public MultilevelMesh - { - public: - MultilevelLevelSet(BoundingBox tentative_bounds, Real reference_data_spacing, - size_t total_levels, Real maximum_spacing_ratio, - ComplexShape& complex_shape, ParticleAdaptation& particle_adaptation); - virtual ~MultilevelLevelSet() {}; - - virtual Real probeSignedDistance(const Vecd& position) override; - virtual Vecd probeNormalDirection(const Vecd& position) override; - virtual Real probeKernelIntegral(const Vecd& position, Real h_ratio = 1.0) override; - virtual Vecd probeKernelGradientIntegral(const Vecd& position, Real h_ratio = 1.0) override; - virtual void cleanInterface(bool isSmoothed = false) override; - protected: - inline size_t getProbeLevel(const Vecd& position); - inline size_t getMeshLevel(Real h_ratio); - - }; -} -#endif //LEVEL_SET_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/include/CMakeLists.txt b/SPHINXsys/src/shared/include/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/include/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/include/sphinxsys.h b/SPHINXsys/src/shared/include/sphinxsys.h deleted file mode 100644 index 4f16c8d35e..0000000000 --- a/SPHINXsys/src/shared/include/sphinxsys.h +++ /dev/null @@ -1,44 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ - -#ifndef SPHINXSYS_H -#define SPHINXSYS_H - -/** @file -This is the header file that user code should include to pick up all sphinxsys -capabilities. **/ - -#include "all_kernels.h" -#include "all_particles.h" -#include "all_geometries.h" -#include "all_bodies.h" -#include "generative_structures.h" -#include "sph_system.h" -#include "all_materials.h" -#include "all_physical_dynamics.h" -#include "all_simbody.h" -#include "in_output.h" -#include "parameterization.h" -#include "regression_testing.h" - -#endif //SPHINXSYS_H diff --git a/SPHINXsys/src/shared/io_system/CMakeLists.txt b/SPHINXsys/src/shared/io_system/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/io_system/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/io_system/in_output.cpp b/SPHINXsys/src/shared/io_system/in_output.cpp deleted file mode 100644 index a3b69b26a4..0000000000 --- a/SPHINXsys/src/shared/io_system/in_output.cpp +++ /dev/null @@ -1,377 +0,0 @@ -/** - * @file in_output.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#include "in_output.h" -#include "all_bodies.h" -#include "level_set.h" -#include "sph_system.h" - -namespace SPH -{ - //=============================================================================================// - In_Output::In_Output(SPHSystem& sph_system) - : sph_system_(sph_system), - input_folder_("./input"), output_folder_("./output"), - restart_folder_("./restart"), reload_folder_("./reload") - { - if (!fs::exists(input_folder_)) - { - fs::create_directory(input_folder_); - } - - if (!fs::exists(output_folder_)) - { - fs::create_directory(output_folder_); - } - - if (!fs::exists(restart_folder_)) - { - fs::create_directory(restart_folder_); - } - - if (sph_system.restart_step_ == 0) - { - fs::remove_all(restart_folder_); - fs::create_directory(restart_folder_); - - fs::remove_all(output_folder_); - fs::create_directory(output_folder_); - } - - restart_step_ = std::to_string(sph_system.restart_step_); - - sph_system.in_output_ = this; - } - //=============================================================================================// - void PltEngine:: - writeAQuantityHeader(std::ofstream& out_file, const Real& quantity, std::string quantity_name) - { - out_file << "\"" << quantity_name<< "\"" << " "; - } - //=============================================================================================// - void PltEngine:: - writeAQuantityHeader(std::ofstream& out_file, const Vecd& quantity, std::string quantity_name) - { - for (int i = 0; i != Dimensions; ++i) - out_file << "\"" << quantity_name << "[" << i << "]\"" << " "; - } - //=============================================================================================// - void PltEngine::writeAQuantity(std::ofstream& out_file, const Real& quantity) - { - out_file << std::fixed << std::setprecision(9) << quantity << " "; - } - //=============================================================================================// - void PltEngine::writeAQuantity(std::ofstream& out_file, const Vecd& quantity) - { - for (int i = 0; i < Dimensions; ++i) - out_file << std::fixed << std::setprecision(9) << quantity[i] << " "; - } - //=============================================================================================// - std::string BodyStatesIO::convertPhysicalTimeToString(Real convertPhysicalTimeToStream) - { - int i_time = int(GlobalStaticVariables::physical_time_ * 1.0e6); - std::stringstream s_time; - s_time << std::setw(10) << std::setfill('0') << i_time; - return s_time.str(); - } - //=============================================================================================// - void BodyStatesRecordingToVtu::writeWithFileName(const std::string& sequence) - { - for (SPHBody* body : bodies_) - { - if (body->checkNewlyUpdated()) - { - std::string filefullpath = in_output_.output_folder_ + "/SPHBody_" + body->getBodyName() + "_" + sequence + ".vtu"; - if (fs::exists(filefullpath)) - { - fs::remove(filefullpath); - } - std::ofstream out_file(filefullpath.c_str(), std::ios::trunc); - //begin of the XML file - out_file << "\n"; - out_file << "\n"; - out_file << " \n"; - - BaseParticles* base_particles = body->base_particles_; - size_t total_real_particles = base_particles->total_real_particles_; - out_file << " getBodyName() << "\" NumberOfPoints=\"" << total_real_particles << "\" NumberOfCells=\"0\">\n"; - - body->writeParticlesToVtuFile(out_file); - - out_file << " \n"; - - //write empty cells - out_file << " \n"; - out_file << " \n"; - out_file << " \n"; - out_file << " \n"; - out_file << " \n"; - out_file << " \n"; - out_file << " \n"; - out_file << " \n"; - - out_file << " \n"; - - out_file << " \n"; - out_file << "\n"; - - out_file.close(); - } - body->setNotNewlyUpdated(); - } - } - //=============================================================================================// - void BodyStatesRecordingToPlt::writeWithFileName(const std::string& sequence) - { - for (SPHBody* body : bodies_) - { - if (body->checkNewlyUpdated()) - { - std::string filefullpath = in_output_.output_folder_ + "/SPHBody_" + body->getBodyName()+ "_" + sequence +".plt"; - if (fs::exists(filefullpath)) - { - fs::remove(filefullpath); - } - std::ofstream out_file(filefullpath.c_str(), std::ios::trunc); - - //begin of the plt file writing - - body->writeParticlesToPltFile(out_file); - - out_file.close(); - } - body->setNotNewlyUpdated(); - } - } - //=============================================================================================// - WriteToVtuIfVelocityOutOfBound - ::WriteToVtuIfVelocityOutOfBound(In_Output& in_output, - SPHBodyVector bodies, Real velocity_bound) - : BodyStatesRecordingToVtu(in_output, bodies), out_of_bound_(false) - { - for (SPHBody* body : bodies_) - { - check_bodies_.push_back(new VelocityBoundCheck(body, velocity_bound)); - } - } - //=============================================================================================// - void WriteToVtuIfVelocityOutOfBound::writeWithFileName(const std::string& sequence) - { - for (auto check_body : check_bodies_) - { - out_of_bound_ = out_of_bound_ || check_body->parallel_exec(); - } - - if (out_of_bound_) { - BodyStatesRecordingToVtu::writeWithFileName(sequence); - std::cout << "\n Velocity is out of bound at iteration step " << sequence - << "\n The body states have been outputted and the simulation terminates here. \n"; - } - } - //=============================================================================================// - MeshRecordingToPlt - ::MeshRecordingToPlt(In_Output& in_output, SPHBody* body, Mesh* mesh) - : BodyStatesRecording(in_output, body), mesh_(mesh) - { - filefullpath_ = in_output_.output_folder_ + "/" + body->getBodyName() + "_" + mesh_->Name() + ".dat"; - } - //=============================================================================================// - void MeshRecordingToPlt::writeWithFileName(const std::string& sequence) - { - std::ofstream out_file(filefullpath_.c_str(), std::ios::app); - mesh_->writeMeshToPltFile(out_file); - out_file.close(); - } - //=============================================================================================// - ReloadParticleIO::ReloadParticleIO(In_Output& in_output, SPHBodyVector bodies) : - BodyStatesIO(in_output, bodies) - { - if (!fs::exists(in_output.reload_folder_)) - { - fs::create_directory(in_output.reload_folder_); - } - - for (SPHBody* body : bodies) - { - file_paths_.push_back(in_output.reload_folder_ + "/SPHBody_" + body->getBodyName() + "_rld.xml"); - } - }; - //=============================================================================================// - ReloadParticleIO::ReloadParticleIO(In_Output& in_output, SPHBodyVector bodies, - StdVec given_body_names) : ReloadParticleIO(in_output, bodies) - { - for (size_t i = 0; i != bodies.size(); ++i) - { - file_paths_[i] = in_output.reload_folder_ + "/SPHBody_" + given_body_names[i] + "_rld.xml"; - } - } - //=============================================================================================// - void ReloadParticleIO::writeToFile(size_t iteration_step) - { - for (size_t i = 0; i < bodies_.size(); ++i) - { - std::string filefullpath = file_paths_[i]; - - if (fs::exists(filefullpath)) - { - fs::remove(filefullpath); - } - bodies_[i]->writeToXmlForReloadParticle(filefullpath); - } - } - //=============================================================================================// - void ReloadParticleIO::readFromFile(size_t restart_step) - { - std::cout << "\n Reloading particles from files." << std::endl; - for (size_t i = 0; i < bodies_.size(); ++i) - { - std::string filefullpath = file_paths_[i]; - - if (!fs::exists(filefullpath)) - { - std::cout << "\n Error: the input file:" << filefullpath << " is not exists" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - - bodies_[i]->readFromXmlForReloadParticle(filefullpath); - } - } - //=============================================================================================// - RestartIO::RestartIO(In_Output& in_output, SPHBodyVector bodies) : - BodyStatesIO(in_output, bodies) - { - overall_file_path_ = in_output.restart_folder_ + "/Restart_time_"; - for (SPHBody* body : bodies) - { - file_paths_.push_back(in_output.restart_folder_ + "/SPHBody_" + body->getBodyName() + "_rst_"); - } - } - //=============================================================================================// - void RestartIO::writeToFile(size_t iteration_step) - { - std::string overall_filefullpath = overall_file_path_ + std::to_string(iteration_step) + ".dat"; - if (fs::exists(overall_filefullpath)) - { - fs::remove(overall_filefullpath); - } - std::ofstream out_file(overall_filefullpath.c_str(), std::ios::app); - out_file << std::fixed << std::setprecision(9) << GlobalStaticVariables::physical_time_ << " \n"; - out_file.close(); - - for (size_t i = 0; i < bodies_.size(); ++i) - { - std::string filefullpath = file_paths_[i] + std::to_string(iteration_step) + ".xml"; - - if (fs::exists(filefullpath)) - { - fs::remove(filefullpath); - } - bodies_[i]->writeParticlesToXmlForRestart(filefullpath); - } - } - //=============================================================================================// - Real RestartIO::readRestartTime(size_t restart_step) - { - std::cout << "\n Reading restart files from the restart step = " << restart_step << std::endl; - std::string overall_filefullpath = overall_file_path_ + std::to_string(restart_step) + ".dat"; - if (!fs::exists(overall_filefullpath)) - { - std::cout << "\n Error: the input file:" << overall_filefullpath << " is not exists" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - Real restart_time; - std::ifstream in_file(overall_filefullpath.c_str()); - in_file >> restart_time; - in_file.close(); - - return restart_time; - } - //=============================================================================================// - void RestartIO::readFromFile(size_t restart_step) - { - for (size_t i = 0; i < bodies_.size(); ++i) - { - std::string filefullpath = file_paths_[i] + std::to_string(restart_step) + ".xml"; - - if (!fs::exists(filefullpath)) - { - std::cout << "\n Error: the input file:" << filefullpath << " is not exists" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - - bodies_[i]->readParticlesFromXmlForRestart(filefullpath); - } - } - //=============================================================================================// - WriteSimBodyPinData:: - WriteSimBodyPinData(In_Output& in_output, SimTK::RungeKuttaMersonIntegrator& integ, SimTK::MobilizedBody::Pin& pinbody) : - WriteSimBodyStates(in_output, integ, pinbody) - { - filefullpath_ = in_output_.output_folder_ + "/mb_pinbody_data.dat"; - std::ofstream out_file(filefullpath_.c_str(), std::ios::app); - - out_file << "\"time\"" << " "; - out_file << " " << "angles" << " "; - out_file << " " << "angle_rates" << " "; - out_file << "\n"; - - out_file.close(); - }; - //=============================================================================================// - void WriteSimBodyPinData::writeToFile(size_t iteration_step) - { - std::ofstream out_file(filefullpath_.c_str(), std::ios::app); - out_file << GlobalStaticVariables::physical_time_ << " "; - const SimTK::State& state = integ_.getState(); - - out_file << " " << mobody_.getAngle(state) << " " << mobody_.getRate(state) << " "; - - out_file << "\n"; - out_file.close(); - }; - //=================================================================================================// - ReloadMaterialParameterIO::ReloadMaterialParameterIO(In_Output& in_output, BaseMaterial* material) : - in_output_(in_output), material_(material) - { - file_path_ = in_output.reload_folder_ + "/Material_" + material->LocalParametersName() + "_rld.xml"; - } - //=================================================================================================// - ReloadMaterialParameterIO:: - ReloadMaterialParameterIO(In_Output& in_output, BaseMaterial* material, std::string given_parameters_name) : - in_output_(in_output), material_(material) - { - file_path_ = in_output.reload_folder_ + "/Material_" + given_parameters_name + "_rld.xml"; - } - //=================================================================================================// - void ReloadMaterialParameterIO::writeToFile(size_t iteration_step) - { - std::string reload_material_folder = in_output_.reload_folder_; - if (!fs::exists(reload_material_folder)) - { - fs::create_directory(reload_material_folder); - } - - if (fs::exists(file_path_)) - { - fs::remove(file_path_); - } - material_->writeToXmlForReloadLocalParameters(file_path_); - } - //=================================================================================================// - void ReloadMaterialParameterIO::readFromFile(size_t restart_step) - { - if (!fs::exists(file_path_)) - { - std::cout << "\n Error: the reloading material property file:" << file_path_ << " is not exists" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - material_->readFromXmlForLocalParameters(file_path_); - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/io_system/in_output.h b/SPHINXsys/src/shared/io_system/in_output.h deleted file mode 100644 index 58be9a013e..0000000000 --- a/SPHINXsys/src/shared/io_system/in_output.h +++ /dev/null @@ -1,654 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file in_output.h - * @brief Classes for input and output functions. - * @author Chi Zhang and Xiangyu Hu - */ - -#pragma once -#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING - -#include "base_data_package.h" -#include "sph_data_conainers.h" -#include "all_physical_dynamics.h" -#include "xml_engine.h" - -#include "SimTKcommon.h" -#include "SimTKmath.h" -#include "Simbody.h" - -#include -#include -#include -/** Macro for APPLE compilers*/ -#ifdef __APPLE__ -#include -namespace fs = boost::filesystem; -#else -#include -namespace fs = std::experimental::filesystem; -#endif - -namespace SPH { - - /** - * @brief preclaimed classes. - */ - class BaseLevelSet; - - /** - * @class In_Output - * @brief The base class which defines folders for output, - * restart and particle reload folders. - */ - class In_Output - { - public: - In_Output(SPHSystem &sph_system); - virtual ~In_Output() {}; - - SPHSystem &sph_system_; - std::string input_folder_; - std::string output_folder_; - std::string restart_folder_; - std::string reload_folder_; - - std::string restart_step_; - }; - - /** - * @class PltEngine - * @brief The base class which defines Tecplot file related operation. - */ - class PltEngine - { - public: - PltEngine() {}; - virtual ~PltEngine() {}; - - void writeAQuantityHeader(std::ofstream& out_file, const Real& quantity, std::string quantity_name); - void writeAQuantityHeader(std::ofstream& out_file, const Vecd& quantity, std::string quantity_name); - void writeAQuantity(std::ofstream& out_file, const Real& quantity); - void writeAQuantity(std::ofstream& out_file, const Vecd& quantity); - }; - - /** - * @class BodyStatesIO - * @brief base class for write and read body states. - */ - class BodyStatesIO - { - protected: - In_Output &in_output_; - SPHBody *body_; - SPHBodyVector bodies_; - - std::string convertPhysicalTimeToString(Real physical_time); - public: - BodyStatesIO(In_Output &in_output, SPHBody *body) - : in_output_(in_output), body_(body), bodies_({body}) {}; - BodyStatesIO(In_Output& in_output, SPHBodyVector bodies) - : in_output_(in_output), body_(bodies[0]), bodies_(bodies) {}; - virtual ~BodyStatesIO() {}; - }; - - /** - * @class BodyStatesRecording - * @brief base class for write body states. - */ - class BodyStatesRecording : public BodyStatesIO - { - public: - BodyStatesRecording(In_Output &in_output, SPHBody *body) - : BodyStatesIO(in_output, body) {}; - BodyStatesRecording(In_Output &in_output, SPHBodyVector bodies) - : BodyStatesIO(in_output, bodies) {}; - virtual ~BodyStatesRecording() {}; - - /** write with filename indicated by physical time */ - void writeToFile() - { - writeWithFileName(convertPhysicalTimeToString(GlobalStaticVariables::physical_time_)); - }; - - /** write with filename indicated by iteration step */ - virtual void writeToFile(size_t iteration_step) - { - writeWithFileName(std::to_string(iteration_step)); - }; - - protected: - virtual void writeWithFileName(const std::string& sequence) = 0; - }; - - /** - * @class SimBodyStatesIO - * @brief base class for write and read SimBody states. - */ - template - class SimBodyStatesIO - { - protected: - In_Output &in_output_; - SimTK::RungeKuttaMersonIntegrator& integ_; - MobilizedBodyType& mobody_; - public: - SimBodyStatesIO(In_Output& in_output, SimTK::RungeKuttaMersonIntegrator& integ, MobilizedBodyType& mobody) - : in_output_(in_output),integ_(integ), mobody_(mobody) {}; - virtual ~SimBodyStatesIO() {}; - }; - - /** - * @class WriteSimBodyStates - * @brief base class for write SimBody states. - */ - template - class WriteSimBodyStates : public SimBodyStatesIO - { - public: - WriteSimBodyStates(In_Output& in_output, SimTK::RungeKuttaMersonIntegrator& integ, MobilizedBodyType& mobody) - : SimBodyStatesIO(in_output,integ, mobody) {}; - virtual ~WriteSimBodyStates() {}; - - virtual void writeToFile(size_t iteration_step) = 0; - }; - - /** - * @class ReadSimBodyStates - * @brief base class for read SimBody states. - */ - template - class ReadSimBodyStates : public SimBodyStatesIO - { - public: - ReadSimBodyStates(In_Output& in_output, MobilizedBodyType* mobody) - : SimBodyStatesIO(in_output, mobody) {}; - ReadSimBodyStates(In_Output& in_output, StdVec mobodies) - : SimBodyStatesIO(in_output, mobodies) {}; - virtual ~ReadSimBodyStates() {}; - - virtual void readFromFile(size_t iteration_step) = 0; - }; - - /** - * @class BodyStatesRecordingToVtu - * @brief Write files for bodies - * the output file is VTK XML format can visualized by ParaView - * the data type vtkUnstructedGrid - */ - class BodyStatesRecordingToVtu : public BodyStatesRecording - { - public: - BodyStatesRecordingToVtu(In_Output& in_output, SPHBodyVector bodies) - : BodyStatesRecording(in_output, bodies) {}; - virtual ~BodyStatesRecordingToVtu() {}; - - protected: - virtual void writeWithFileName(const std::string& sequence) override; - }; - - /** - * @class BodyStatesRecordingToPlt - * @brief Write files for bodies - * the output file is dat format can visualized by TecPlot - */ - class BodyStatesRecordingToPlt : public BodyStatesRecording - { - public: - BodyStatesRecordingToPlt(In_Output& in_output, SPHBodyVector bodies) - : BodyStatesRecording(in_output, bodies) {}; - virtual ~BodyStatesRecordingToPlt() {}; - - protected: - virtual void writeWithFileName(const std::string& sequence) override; - }; - - /** - * @class WriteToVtuIfVelocityOutOfBound - * @brief output body sates if particle velocity is - * out of a bound - */ - class WriteToVtuIfVelocityOutOfBound - : public BodyStatesRecordingToVtu - { - protected: - bool out_of_bound_; - StdVec check_bodies_; - virtual void writeWithFileName(const std::string& sequence) override; - public: - WriteToVtuIfVelocityOutOfBound(In_Output& in_output, - SPHBodyVector bodies, Real velocity_bound); - virtual ~WriteToVtuIfVelocityOutOfBound() {}; - }; - - /** - * @class MeshRecordingToPlt - * @brief write the background mesh data for relax body - */ - class MeshRecordingToPlt : public BodyStatesRecording - { - protected: - std::string filefullpath_; - Mesh* mesh_; - virtual void writeWithFileName(const std::string& sequence) override; - public: - MeshRecordingToPlt(In_Output& in_output, SPHBody* body, Mesh* mesh); - virtual ~MeshRecordingToPlt() {}; - }; - - /** - * @class ObservedQuantityRecording - * @brief write files for observed quantity - */ - template - class ObservedQuantityRecording : public BodyStatesRecording, - public observer_dynamics::InterpolatingAQuantity - { - protected: - SPHBody* observer_; - PltEngine plt_engine_; - BaseParticles* base_particles_; - std::string body_name_; - std::string quantity_name_; - XmlEngine observe_xml_engine_; - std::string filefullpath_input_; - std::string filefullpath_output_; - - DataVec current_result_; /* the container of the current result. */ - StdVec element_tag_; /* the container of the current tag. */ - - void WriteDataToXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, - string element_name, size_t particle_n, const Real& quantity) - { - SimTK::Xml::element_iterator ele_ite = element.element_begin(element_name); - std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); - xmlengine.setAttributeToElement(ele_ite, attribute_name_, quantity); - }; - void WriteDataToXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, - string element_name, size_t particle_n, const Vecd& quantity) - { - SimTK::Xml::element_iterator ele_ite = element.element_begin(element_name); - std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); - xmlengine.setAttributeToElement(ele_ite, attribute_name_, quantity); - }; - void WriteDataToXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, - string element_name, size_t particle_n, const Matd& quantity) - { - SimTK::Xml::element_iterator ele_ite = element.element_begin(element_name); - std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); - xmlengine.setAttributeToElement(ele_ite, attribute_name_, quantity); - }; - - void ReadDataFromXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, - size_t particle_n, DataVec& container) - { - size_t index_i_ = 0; - SimTK::Xml::element_iterator ele_ite = element.element_begin(); - for (; ele_ite != element.element_end(); ++ele_ite) - { - std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); - xmlengine.getRequiredAttributeValue(ele_ite, attribute_name_, container[index_i_][particle_n]); - index_i_++; - } - }; - void ReadDataFromXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, - size_t particle_n, DataVec& container) - { - size_t index_i_ = 0; - SimTK::Xml::element_iterator ele_ite = element.element_begin(); - for (; ele_ite != element.element_end(); ++ele_ite) - { - std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); - xmlengine.getRequiredAttributeValue(ele_ite, attribute_name_, container[index_i_][particle_n]); - index_i_++; - } - }; - void ReadDataFromXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, - size_t particle_n, DataVec& container) - { - size_t index_i_ = 0; - SimTK::Xml::element_iterator ele_ite = element.element_begin(); - for (; ele_ite != element.element_end(); ++ele_ite) - { - std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); - xmlengine.getRequiredAttributeMatrixValue(ele_ite, attribute_name_, container[index_i_][particle_n]); - index_i_++; - } - }; - - void ReadTagFromXmlMemory(SimTK::Xml::Element element, StdVec& element_tag) - { - size_t index_i_ = 0; - SimTK::Xml::element_iterator ele_ite = element.element_begin(); - for (; ele_ite != element.element_end(); ++ele_ite) - { - element_tag[index_i_] = ele_ite->getElementTag(); - index_i_++; - } - }; - - public: - ObservedQuantityRecording(std::string quantity_name, In_Output& in_output, - BaseBodyRelationContact* body_contact_relation) : - BodyStatesRecording(in_output, body_contact_relation->sph_body_), - observer_dynamics::InterpolatingAQuantity(body_contact_relation, quantity_name), - observer_(body_contact_relation->sph_body_), plt_engine_(), base_particles_(observer_->base_particles_), - body_name_(body_contact_relation->sph_body_->getBodyName()), quantity_name_(quantity_name), - observe_xml_engine_("xml_observe", quantity_name_) - { - /** Output for .dat file. */ - filefullpath_output_ = in_output_.output_folder_ + "/" + body_name_ - + "_" + quantity_name + "_" + in_output_.restart_step_ + ".dat"; - std::ofstream out_file(filefullpath_output_.c_str(), std::ios::app); - out_file << "run_time" << " "; - for (size_t i = 0; i != base_particles_->total_real_particles_; ++i) - { - std::string quantity_name_i = quantity_name + "[" + std::to_string(i) + "]"; - plt_engine_.writeAQuantityHeader(out_file, this->interpolated_quantities_[i], quantity_name_i); - } - out_file << "\n"; - out_file.close(); - - /** Output for .xml file. */ - filefullpath_input_ = in_output_.input_folder_ + "/" + body_name_ - + "_" + quantity_name + "_" + in_output_.restart_step_ + ".xml"; - }; - virtual ~ObservedQuantityRecording() {}; - - VariableType type_indicator_; /*< this is an indicator to identify the variable type. */ - - void WriteXmlToXmlFile() { observe_xml_engine_.writeToXmlFile(filefullpath_input_); } - - virtual void writeWithFileName(const std::string& sequence) override - { - this->parallel_exec(); - std::ofstream out_file(filefullpath_output_.c_str(), std::ios::app); - out_file << GlobalStaticVariables::physical_time_ << " "; - for (size_t i = 0; i != base_particles_->total_real_particles_; ++i) - { - plt_engine_.writeAQuantity(out_file, this->interpolated_quantities_[i]); - } - out_file << "\n"; - out_file.close(); - }; - - void WriteToXml(size_t iteration = 0) - { - this->parallel_exec(); - std::string element_name_ = "Snapshot_" + std::to_string(iteration); - SimTK::Xml::Element element_ = observe_xml_engine_.root_element_; - observe_xml_engine_.addElementToXmlDoc(element_name_); - for (size_t i = 0; i != base_particles_->total_real_particles_; ++i) - { - WriteDataToXmlMemory(observe_xml_engine_, element_, element_name_, i, this->interpolated_quantities_[i]); - }; - }; - - void ReadFromXml() - { - observe_xml_engine_.loadXmlFile(filefullpath_input_); - size_t number_of_particle_ = base_particles_->total_real_particles_; - size_t number_of_snapshot_ = std::distance(observe_xml_engine_.root_element_.element_begin(), - observe_xml_engine_.root_element_.element_end()); - DataVec current_result_temp_(number_of_snapshot_, StdVec(number_of_particle_)); - StdVec element_tag_temp_(number_of_snapshot_); - current_result_ = current_result_temp_; - element_tag_ = element_tag_temp_; - SimTK::Xml::Element element_ = observe_xml_engine_.root_element_; - for (size_t j = 0; j != number_of_particle_; ++j) - { - ReadDataFromXmlMemory(observe_xml_engine_, element_, j, current_result_); - ReadTagFromXmlMemory(element_, element_tag_); - } - }; - }; - - /** - * @class BodyReducedQuantityRecording - * @brief write reduced quantity of a body - */ - template - class BodyReducedQuantityRecording - { - protected: - In_Output& in_output_; - PltEngine plt_engine_; - ReduceMethodType reduce_method_; - std::string body_name_; - std::string quantity_name_; - XmlEngine observe_xml_engine_; - std::string filefullpath_input_; - std::string filefullpath_output_; - - /*< deduce variable type from reduce method. */ - using VariableType = decltype(reduce_method_.InitialReference()); - DataVec current_result_; /* the container of the current result. */ - StdVec element_tag_; /* the container of the current tag. */ - - void WriteDataToXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, - string element_name, size_t particle_n, const Real& quantity) - { - SimTK::Xml::element_iterator ele_ite = element.element_begin(element_name); - std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); - xmlengine.setAttributeToElement(ele_ite, attribute_name_, quantity); - }; - void WriteDataToXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, - string element_name, size_t particle_n, const Vecd& quantity) - { - SimTK::Xml::element_iterator ele_ite = element.element_begin(element_name); - std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); - xmlengine.setAttributeToElement(ele_ite, attribute_name_, quantity); - }; - void WriteDataToXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, - string element_name, size_t particle_n, const Matd& quantity) - { - SimTK::Xml::element_iterator ele_ite = element.element_begin(element_name); - std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); - xmlengine.setAttributeToElement(ele_ite, attribute_name_, quantity); - }; - - void ReadDataFromXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element_name, - size_t particle_n, DataVec& container) - { - size_t index_i_ = 0; - SimTK::Xml::element_iterator ele_ite = element_name.element_begin(); - for (; ele_ite != element_name.element_end(); ++ele_ite) - { - std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); - xmlengine.getRequiredAttributeValue(ele_ite, attribute_name_, container[index_i_][particle_n]); - index_i_++; - } - }; - void ReadDataFromXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element_name, - size_t particle_n, DataVec& container) - { - size_t index_i_ = 0; - SimTK::Xml::element_iterator ele_ite = element_name.element_begin(); - for (; ele_ite != element_name.element_end(); ++ele_ite) - { - std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); - xmlengine.getRequiredAttributeValue(ele_ite, attribute_name_, container[index_i_][particle_n]); - index_i_++; - } - }; - void ReadDataFromXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element_name, - size_t particle_n, DataVec& container) - { - size_t index_i_ = 0; - SimTK::Xml::element_iterator ele_ite = element_name.element_begin(); - for (; ele_ite != element_name.element_end(); ++ele_ite) - { - std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); - xmlengine.getRequiredAttributeMatrixValue(ele_ite, attribute_name_, container[index_i_][particle_n]); - index_i_++; - } - }; - - void ReadTagFromXmlMemory(SimTK::Xml::Element element, StdVec& element_tag) - { - size_t index_i_ = 0; - SimTK::Xml::element_iterator ele_ite = element.element_begin(); - for (; ele_ite != element.element_end(); ++ele_ite) - { - element_tag[index_i_] = ele_ite->getElementTag(); - index_i_++; - } - }; - - public: - template - BodyReducedQuantityRecording(In_Output& in_output, ConstructorArgs... constructor_args) : - in_output_(in_output), plt_engine_(), reduce_method_(constructor_args...), - body_name_(reduce_method_.getSPHBody()->getBodyName()), - quantity_name_(reduce_method_.QuantityName()), - observe_xml_engine_("xml_reduce", quantity_name_) - { - /** output for .dat file. */ - filefullpath_output_ = in_output_.output_folder_ + "/" + body_name_ - + "_" + quantity_name_ + "_" + in_output_.restart_step_ + ".dat"; - std::ofstream out_file(filefullpath_output_.c_str(), std::ios::app); - out_file << "\"run_time\"" << " "; - plt_engine_.writeAQuantityHeader(out_file, reduce_method_.InitialReference(), quantity_name_); - out_file << "\n"; - out_file.close(); - - /** output for .xml file. */ - filefullpath_input_ = in_output_.input_folder_ + "/" + body_name_ - + "_" + quantity_name_ + "_" + in_output_.restart_step_ + ".xml"; - }; - virtual ~BodyReducedQuantityRecording() {}; - - VariableType type_indicator_; /*< this is an indicator to identify the variable type. */ - - void writeXmlToXmlFile() { observe_xml_engine_.writeToXmlFile(filefullpath_input_); } - - virtual void writeToFile(size_t iteration_step = 0) - { - std::ofstream out_file(filefullpath_output_.c_str(), std::ios::app); - out_file << GlobalStaticVariables::physical_time_ << " "; - plt_engine_.writeAQuantity(out_file, reduce_method_.parallel_exec()); - out_file << "\n"; - out_file.close(); - }; - - void WriteToXml(size_t iteration = 0) - { - std::string element_name_ = "Snapshot_" + std::to_string(iteration); - SimTK::Xml::Element element_ = observe_xml_engine_.root_element_; - observe_xml_engine_.addElementToXmlDoc(element_name_); - WriteDataToXmlMemory(observe_xml_engine_, element_, element_name_, 0, reduce_method_.parallel_exec()); - }; - - void ReadFromXml() - { - observe_xml_engine_.loadXmlFile(filefullpath_input_); - size_t number_of_particle_ = 1; - size_t number_of_snapshot_ = std::distance(observe_xml_engine_.root_element_.element_begin(), - observe_xml_engine_.root_element_.element_end()); - DataVec current_result_temp_(number_of_snapshot_, StdVec(number_of_particle_)); - StdVec element_tag_temp_(number_of_snapshot_); - current_result_ = current_result_temp_; - element_tag_ = element_tag_temp_; - SimTK::Xml::Element element_ = observe_xml_engine_.root_element_; - for (size_t j = 0; j != number_of_particle_; ++j) - { - ReadDataFromXmlMemory(observe_xml_engine_, element_, j, current_result_); - ReadTagFromXmlMemory(element_, element_tag_); - } - }; - }; - - /** - * @class ReloadParticleIO - * @brief Write the reload particles file in XML format. - */ - class ReloadParticleIO : public BodyStatesIO - { - protected: - StdVec file_paths_; - public: - ReloadParticleIO(In_Output& in_output, SPHBodyVector bodies); - ReloadParticleIO(In_Output& in_output, SPHBodyVector bodies, StdVec given_body_names); - virtual ~ReloadParticleIO() {}; - - virtual void writeToFile(size_t iteration_step = 0); - virtual void readFromFile(size_t iteration_step = 0); - }; - - /** - * @class RestartIO - * @brief Write the restart file in XML format. - */ - class RestartIO : public BodyStatesIO - { - protected: - std::string overall_file_path_; - StdVec file_paths_; - - Real readRestartTime(size_t restart_step); - public: - RestartIO(In_Output& in_output, SPHBodyVector bodies); - virtual ~RestartIO() {}; - - virtual void writeToFile(size_t iteration_step = 0); - virtual void readFromFile(size_t iteration_step = 0); - virtual Real readRestartFiles(size_t restart_step) { - readFromFile(restart_step); - return readRestartTime(restart_step); - }; - }; - - /** - * @class WriteSimBodyPinData - * @brief Write total force acting a solid body. - */ - class WriteSimBodyPinData : public WriteSimBodyStates - { - protected: - std::string filefullpath_; - public: - WriteSimBodyPinData(In_Output& in_output, SimTK::RungeKuttaMersonIntegrator& integ, SimTK::MobilizedBody::Pin& pinbody); - virtual ~WriteSimBodyPinData() {}; - virtual void writeToFile(size_t iteration_step = 0) override; - }; - - /** - * @class ReloadMaterialParameterIO - * @brief For write and read material property. - */ - class ReloadMaterialParameterIO - { - protected: - In_Output& in_output_; - BaseMaterial *material_; - std::string file_path_; - public: - ReloadMaterialParameterIO(In_Output& in_output, BaseMaterial* material); - ReloadMaterialParameterIO(In_Output& in_output, BaseMaterial *material, std::string given_parameters_name); - virtual ~ReloadMaterialParameterIO() {}; - - virtual void writeToFile(size_t iteration_step = 0); - virtual void readFromFile(size_t iteration_step = 0); - }; -} \ No newline at end of file diff --git a/SPHINXsys/src/shared/io_system/parameterization.cpp b/SPHINXsys/src/shared/io_system/parameterization.cpp deleted file mode 100644 index d64318bb90..0000000000 --- a/SPHINXsys/src/shared/io_system/parameterization.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @file parameterization.cpp - * @author Xiangyu Hu - */ - -#include "parameterization.h" - -namespace SPH -{ - //=============================================================================================// - ParameterizationIO::ParameterizationIO(In_Output& in_output) : - xml_paremeters_("xml_parameters", "parameters") - { - filefullpath_ = in_output.input_folder_ + "/" + "project_parameters.dat"; - xml_paremeters_.loadXmlFile(filefullpath_); - } - //=============================================================================================// - void ParameterizationIO::writeProjectParameters() - { - xml_paremeters_.writeToXmlFile(filefullpath_); - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/io_system/parameterization.h b/SPHINXsys/src/shared/io_system/parameterization.h deleted file mode 100644 index 457421a66d..0000000000 --- a/SPHINXsys/src/shared/io_system/parameterization.h +++ /dev/null @@ -1,96 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file parameterization.h - * @brief This is the base classes for introducing the parameterization - * of a class or method. - * @author Xiangyu Hu - */ - -#ifndef SPHINXSYS_PARAMETERIZATION_H -#define SPHINXSYS_PARAMETERIZATION_H - -#include "base_data_package.h" -#include "in_output.h" -#include "xml_engine.h" - -#include - -namespace SPH -{ - class ParameterizationIO - { - public: - XmlEngine xml_paremeters_; - std::string filefullpath_; - - ParameterizationIO(In_Output& in_output); - ~ParameterizationIO() {}; - - void writeProjectParameters(); - }; - - template - class BaseParameterization : public BaseClassType - { - public: - template - explicit BaseParameterization(ParameterizationIO& parameterization_io, ConstructorArgs... constructor_args) : - BaseClassType(constructor_args...), xml_paremeters_(parameterization_io.xml_paremeters_), - filefullpath_(parameterization_io.filefullpath_) {}; - ~BaseParameterization() {}; - protected: - XmlEngine& xml_paremeters_; - std::string filefullpath_; - - template - void getAParameter(const std::string& element_name, const std::string& variable_name, VariableType& variable_addrs) - { - SimTK::Xml::element_iterator ele_ite = - xml_paremeters_.root_element_.element_begin(element_name); - if(ele_ite != xml_paremeters_.root_element_.element_end()) - { - xml_paremeters_.getRequiredAttributeValue(ele_ite, variable_name, variable_addrs); - } - else { - std::cout << "\n Error: the variable '" << variable_name << "' is given not in project_parameters.dat !" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - }; - - template - void setAParameter(const std::string& element_name, const std::string& variable_name, VariableType& variable_addrs) - { - SimTK::Xml::element_iterator ele_ite = - xml_paremeters_.root_element_.element_begin(element_name); - if (ele_ite == xml_paremeters_.root_element_.element_end()) - { - xml_paremeters_.addElementToXmlDoc(element_name); - ele_ite = xml_paremeters_.root_element_.element_begin(element_name); - } - xml_paremeters_.setAttributeToElement(ele_ite, variable_name, variable_addrs); - }; - }; -} -#endif //SPHINXSYS_PARAMETERIZATION_H diff --git a/SPHINXsys/src/shared/kernels/CMakeLists.txt b/SPHINXsys/src/shared/kernels/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/kernels/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/kernels/all_kernels.h b/SPHINXsys/src/shared/kernels/all_kernels.h deleted file mode 100644 index 4a6e66db0f..0000000000 --- a/SPHINXsys/src/shared/kernels/all_kernels.h +++ /dev/null @@ -1,10 +0,0 @@ - -#ifndef ALL_KERNELS_H -#define ALL_KERNELS_H - - - -#include "kernel_wenland_c2.h" -#include "kernel_hyperbolic.h" -#include "kernel_tabulated.hpp" -#endif //ALL_KERNELS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/kernels/base_kernel.cpp b/SPHINXsys/src/shared/kernels/base_kernel.cpp deleted file mode 100644 index ceeee838b8..0000000000 --- a/SPHINXsys/src/shared/kernels/base_kernel.cpp +++ /dev/null @@ -1,180 +0,0 @@ -/** - * @file base_kernel.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#include "base_kernel.h" - -namespace SPH -{ - //=================================================================================================// - Kernel::Kernel(std::string kernel_name) - : kernel_name_(kernel_name), h_(1.0), inv_h_(1.0) {}; - //=================================================================================================// - void Kernel::initialize(Real h) - { - h_ = h; - inv_h_ = 1.0 / h; - if (h <= 0.0) - { - std::cout << "\n FAILURE: The Kernel gets a non-positive smoothing length \"" - << h << "\"!\n"; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - - setBasicParameters(); - setDerivativeParameters(); - } - //=================================================================================================// - void Kernel::setDerivativeParameters() - { - cutoff_radius_ref_ = KernelSize()*h_; - factor_dW_1D_ = inv_h_ * factor_W_1D_; - factor_dW_2D_ = inv_h_ * factor_W_2D_; - factor_dW_3D_ = inv_h_ * factor_W_3D_; - factor_d2W_1D_ = inv_h_ * factor_dW_1D_; - factor_d2W_2D_ = inv_h_ * factor_dW_2D_; - factor_d2W_3D_ = inv_h_ * factor_dW_3D_; - } - //=================================================================================================// - Real Kernel::W(const Real& r_ij, const Real& displacement) const - { - Real q = r_ij * inv_h_; - return factor_W_1D_ * W_1D(q); - } - //=================================================================================================// - Real Kernel::W(const Real& r_ij, const Vec2d& displacement) const - { - Real q = r_ij * inv_h_; - return factor_W_2D_ * W_2D(q); - } - //=================================================================================================// - Real Kernel::W(const Real& r_ij, const Vec3d& displacement) const - { - Real q = r_ij * inv_h_; - return factor_W_3D_ * W_3D(q); - } - //=================================================================================================// - Real Kernel::dW(const Real& r_ij, const Real& displacement) const - { - Real q = r_ij * inv_h_; - return factor_dW_1D_ * dW_1D(q); - } - //=================================================================================================// - Real Kernel::dW(const Real& r_ij, const Vec2d& displacement) const - { - Real q = r_ij * inv_h_; - return factor_dW_2D_ * dW_2D(q); - } - //=================================================================================================// - Real Kernel::dW(const Real& r_ij, const Vec3d& displacement) const - { - Real q = r_ij * inv_h_; - return factor_dW_3D_ * dW_3D(q); - } - //=================================================================================================// - Real Kernel::d2W(const Real& r_ij, const Real& displacement) const - { - Real q = r_ij * inv_h_; - return factor_d2W_1D_ * d2W_1D(q); - } - //=================================================================================================// - Real Kernel::d2W(const Real& r_ij, const Vec2d& displacement) const - { - Real q = r_ij * inv_h_; - return factor_d2W_2D_ * d2W_2D(q); - } - //=================================================================================================// - Real Kernel::d2W(const Real& r_ij, const Vec3d& displacement) const - { - Real q = r_ij * inv_h_; - return factor_d2W_3D_ * d2W_3D(q); - } - //=================================================================================================// - Real Kernel::W(const Real& h_ratio, const Real& r_ij, const Real& displacement) const - { - Real q = r_ij * inv_h_ * h_ratio; - return factor_W_1D_ * W_1D(q) * SmoothingLengthFactor1D(h_ratio); - } - //=================================================================================================// - Real Kernel::W(const Real& h_ratio, const Real& r_ij, const Vec2d& displacement) const - { - Real q = r_ij * inv_h_ * h_ratio; - return factor_W_2D_ * W_2D(q) * SmoothingLengthFactor2D(h_ratio); - } - //=================================================================================================// - Real Kernel::W(const Real& h_ratio, const Real& r_ij, const Vec3d& displacement) const - { - Real q = r_ij * inv_h_ * h_ratio; - return factor_W_3D_ * W_3D(q) * SmoothingLengthFactor3D(h_ratio); - } - //=================================================================================================// - Real Kernel::W0(const Real& h_ratio, const Real& point_i) const - { - return factor_W_1D_ * SmoothingLengthFactor1D(h_ratio); - }; - //=================================================================================================// - Real Kernel::W0(const Real& h_ratio, const Vec2d& point_i) const - { - return factor_W_2D_ * SmoothingLengthFactor2D(h_ratio); - }; - //=================================================================================================// - Real Kernel::W0(const Real& h_ratio, const Vec3d& point_i) const - { - return factor_W_3D_ * SmoothingLengthFactor3D(h_ratio); - }; - //=================================================================================================// - Real Kernel::dW(const Real& h_ratio, const Real& r_ij, const Real& displacement) const - { - Real q = r_ij * inv_h_ * h_ratio; - return factor_dW_1D_ * dW_1D(q) * SmoothingLengthFactor1D(h_ratio); - } - //=================================================================================================// - Real Kernel::dW(const Real& h_ratio, const Real& r_ij, const Vec2d& displacement) const - { - Real q = r_ij * inv_h_ * h_ratio; - return factor_dW_2D_ * dW_2D(q) * SmoothingLengthFactor2D(h_ratio); - } - //=================================================================================================// - Real Kernel::dW(const Real& h_ratio, const Real& r_ij, const Vec3d& displacement) const - { - Real q = r_ij * inv_h_ * h_ratio; - return factor_dW_3D_ * dW_3D(q) * SmoothingLengthFactor3D(h_ratio); - } - //=================================================================================================// - Real Kernel::d2W(const Real& h_ratio, const Real& r_ij, const Real& displacement) const - { - Real q = r_ij * inv_h_ * h_ratio; - return factor_d2W_1D_ * d2W_1D(q) * SmoothingLengthFactor1D(h_ratio); - } - //=================================================================================================// - Real Kernel::d2W(const Real& h_ratio, const Real& r_ij, const Vec2d& displacement) const - { - Real q = r_ij * inv_h_ * h_ratio; - return factor_d2W_2D_ * d2W_2D(q) * SmoothingLengthFactor2D(h_ratio); - } - //=================================================================================================// - Real Kernel::d2W(const Real& h_ratio, const Real& r_ij, const Vec3d& displacement) const - { - Real q = r_ij * inv_h_ * h_ratio; - return factor_d2W_3D_ * d2W_3D(q) * SmoothingLengthFactor3D(h_ratio); - } - //=================================================================================================// - void Kernel::reduceOnce() - { - factor_W_3D_ = factor_W_2D_; - factor_W_2D_ = factor_W_1D_; - factor_W_1D_ = 0.0; - setDerivativeParameters(); - } - //=================================================================================================// - void Kernel::reduceTwice() - { - factor_W_3D_ = factor_W_1D_; - factor_W_2D_ = 0.0; - factor_W_1D_ = 0.0; - setDerivativeParameters(); - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/kernels/base_kernel.h b/SPHINXsys/src/shared/kernels/base_kernel.h deleted file mode 100644 index b3e672ae57..0000000000 --- a/SPHINXsys/src/shared/kernels/base_kernel.h +++ /dev/null @@ -1,176 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file base_kernel.h -* @brief This is the base classes of kernel functions. Implementation will be -* implemented in derived classes. The kernal function define the relevance -* between two neighboring particles. Basically, the further the two -* particles, the less relevance they have. -* @author Luhui Han, Chi ZHang and Xiangyu Hu -* @version 0.1 -* @version 0.3.0 -* Add the reduced kernel for reduced dynamics of linear structure. -* -- Chi ZHANG -*/ - - -#ifndef BASE_KERNELS_H -#define BASE_KERNELS_H - - - -#include "base_data_package.h" - -#include - -namespace SPH -{ - /** - * @class Kernel - * @brief Abstract base class of a general SPH kernel function which - * is a smoothed Dirac delta function, - * a kernel function is radial symmetric, and has a scaling factor. - * Based on difference data type in 2d or 3d buildings, - * the kernel is defined for 2 and 3 dimensions. - * The kernel gives value one at the origin. - * The naming of kernel function follows the stand SPH literature. - * Currently, only constant smoothing length is applied. - * Basically, one can assign different kernel for different particle interactions. - */ - class Kernel - { - protected: - const std::string kernel_name_; - Real h_, inv_h_; /**< reference smoothing length and its inverse **/ - Real cutoff_radius_ref_; /** reference cut off radius **/ - /** Normalization factors for the kernel function **/ - Real factor_W_1D_, factor_W_2D_, factor_W_3D_; - /** Auxiliary factors for the derivative of kernel function **/ - Real factor_dW_1D_, factor_dW_2D_, factor_dW_3D_; - /** Auxiliary factors for the second order derivative of kernel function **/ - Real factor_d2W_1D_, factor_d2W_2D_, factor_d2W_3D_; - - virtual void setBasicParameters() = 0; - void setDerivativeParameters(); - public: - /** empty initialization in constructor, initialization will be carried out later. */ - Kernel(std::string kernel_name = "Kernel"); - virtual ~Kernel() {}; - - void initialize(Real h); - std::string Name() const { return kernel_name_; }; - Real SmoothingLength() const { return h_; }; - /**< non-dimensional size of the kernel, generally 2.0 **/ - virtual Real KernelSize() const { return 2.0; }; - Real CutOffRadius() const { return cutoff_radius_ref_; }; - Real FactorW1D() const { return factor_W_1D_; }; - Real FactorW2D() const { return factor_W_2D_; }; - Real FactorW3D() const { return factor_W_3D_; }; - - /** Calculates the kernel value for the given displacement of two particles - * r_ij pointing from particle j to particle i - */ - virtual Real W(const Real& r_ij, const Real& displacement) const; - virtual Real W(const Real& r_ij, const Vec2d& displacement) const; - virtual Real W(const Real& r_ij, const Vec3d& displacement) const; - - /** this value could be use to calculate the value of W - * they are realized in specific kernel implementations - */ - virtual Real W_1D(const Real q) const = 0; - virtual Real W_2D(const Real q) const = 0; - virtual Real W_3D(const Real q) const = 0; - - /** Calculates the kernel value at the origin **/ - virtual Real W0(const Real& point_i) const { return factor_W_1D_; }; - virtual Real W0(const Vec2d& point_i) const { return factor_W_2D_; }; - virtual Real W0(const Vec3d& point_i) const { return factor_W_3D_; }; - - /** Calculates the kernel derivation for - * the given distance of two particles - */ - virtual Real dW(const Real& r_ij, const Real& displacement) const; - virtual Real dW(const Real& r_ij, const Vec2d& displacement) const; - virtual Real dW(const Real& r_ij, const Vec3d& displacement) const; - - /** this value could be use to calculate the value of dW - * they are realized in specific kernel implementations - */ - virtual Real dW_1D(const Real q) const = 0; - virtual Real dW_2D(const Real q) const = 0; - virtual Real dW_3D(const Real q) const = 0; - - /** Calculates the kernel second order derivation for - * the given distance of two particles - */ - virtual Real d2W(const Real& r_ij, const Real& displacement) const; - virtual Real d2W(const Real& r_ij, const Vec2d& displacement) const; - virtual Real d2W(const Real& r_ij, const Vec3d& displacement) const; - - /** this value could be use to calculate the value of d2W - * they are realized in specific kernel implementations - */ - virtual Real d2W_1D(const Real q) const = 0; - virtual Real d2W_2D(const Real q) const = 0; - virtual Real d2W_3D(const Real q) const = 0; - - //---------------------------------------------------------------------- - // Below are for variable smoothing length. - // Note that we input the ratio between the reference smoothing length - // to the variable smoothing length. - //---------------------------------------------------------------------- - protected: - Real SmoothingLengthFactor1D(const Real& h_ratio) const { return h_ratio; }; - Real SmoothingLengthFactor2D(const Real& h_ratio) const { return h_ratio * h_ratio; }; - Real SmoothingLengthFactor3D(const Real& h_ratio) const { return h_ratio * h_ratio * h_ratio; }; - - public: - Real CutOffRadius(Real h_ratio) const { return cutoff_radius_ref_ / h_ratio; }; - - Real W(const Real& h_ratio, const Real& r_ij, const Real& displacement) const; - Real W(const Real& h_ratio, const Real& r_ij, const Vec2d& displacement) const; - Real W(const Real& h_ratio, const Real& r_ij, const Vec3d& displacement) const; - - /** Calculates the kernel value at the origin **/ - Real W0(const Real& h_ratio, const Real& point_i) const; - Real W0(const Real& h_ratio, const Vec2d& point_i) const; - Real W0(const Real& h_ratio, const Vec3d& point_i) const; - - /** Calculates the kernel derivation for the given distance of two particles **/ - Real dW(const Real& h_ratio, const Real& r_ij, const Real& displacement) const; - Real dW(const Real& h_ratio, const Real& r_ij, const Vec2d& displacement) const; - Real dW(const Real& h_ratio, const Real& r_ij, const Vec3d& displacement) const; - - /** Calculates the kernel second order derivation for the given distance of two particles **/ - Real d2W(const Real& h_ratio, const Real& r_ij, const Real& displacement) const; - Real d2W(const Real& h_ratio, const Real& r_ij, const Vec2d& displacement) const; - Real d2W(const Real& h_ratio, const Real& r_ij, const Vec3d& displacement) const; - //---------------------------------------------------------------------- - // Below are for reduced kernels. - //---------------------------------------------------------------------- - public: - void reduceOnce(); /** reduce for thin structures or films */ - void reduceTwice(); /** reduce for linear structures or filaments */ - }; -} -#endif //BASE_KERNELS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/kernels/kernel_hyperbolic.cpp b/SPHINXsys/src/shared/kernels/kernel_hyperbolic.cpp deleted file mode 100644 index 9232deaac6..0000000000 --- a/SPHINXsys/src/shared/kernels/kernel_hyperbolic.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/** - * @file kernel_hyperbolic.cpp - * @author Luhui Han, Chi ZHang, Yongchuan Yu and Xiangyu Hu - */ - -#include "kernel_hyperbolic.h" - -#include - -namespace SPH -{ - //=================================================================================================// - KernelHyperbolic::KernelHyperbolic() - : Kernel("HyperbolicKernel") {} - //=================================================================================================// - void KernelHyperbolic::setBasicParameters() - { - factor_W_1D_ = inv_h_ / 7.0; - factor_W_2D_ = inv_h_ * inv_h_ / (3.0 * Pi); - factor_W_3D_ = inv_h_ * inv_h_ * inv_h_ * 15.0 / (62.0 *Pi); - } - //=================================================================================================// - Real KernelHyperbolic::W_1D(const Real q) const - { - if (q < 1.0) { - return (6.0 - 6.0 * q + powerN(q, 3)); - } - else { - return powerN(2.0 - q, 3); - } - } - //=================================================================================================// - Real KernelHyperbolic::W_2D(const Real q) const - { - return W_1D(q); - } - //=================================================================================================// - Real KernelHyperbolic::W_3D(const Real q) const - { - return W_1D(q); - } - //=================================================================================================// - Real KernelHyperbolic::dW_1D(const Real q) const - { - if (q < 1.0) { - return (-6.0 + 3.0 * powerN(q, 2)); - } - else { - return powerN(2.0 - q, 2) * (-1.0); - } - } - //=================================================================================================// - Real KernelHyperbolic::dW_2D(const Real q) const - { - return dW_1D(q); - } - //=================================================================================================// - Real KernelHyperbolic::dW_3D(const Real q) const - { - return dW_1D(q); - } - //=================================================================================================// - Real KernelHyperbolic::d2W_1D(const Real q) const - { - if (q < 1.0) { - return 6.0 * q; - } - else { - return 2.0 * (2.0 - q); - } - } - //=================================================================================================// - Real KernelHyperbolic::d2W_2D(const Real q) const - { - return d2W_1D(q); - } - //=================================================================================================// - Real KernelHyperbolic::d2W_3D(const Real q) const - { - return d2W_1D(q); - } - //=================================================================================================// -} - \ No newline at end of file diff --git a/SPHINXsys/src/shared/kernels/kernel_hyperbolic.h b/SPHINXsys/src/shared/kernels/kernel_hyperbolic.h deleted file mode 100644 index aa7384f5e2..0000000000 --- a/SPHINXsys/src/shared/kernels/kernel_hyperbolic.h +++ /dev/null @@ -1,67 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file kernel_hyperbolic.h -* @brief Here, we define hyperbolic kernel functions. -* @details Numerical experiments suggests -* the this kernel is more stable than gaussian like kernel due to its spike -* at the origin. However, it is also found that such kernels give bad density -* predictions. Therefore, the application of this kernel should be clarified. -* @author Chi ZHang and Xiangyu Hu -*/ - - -#ifndef KERNEL_HYPERBOLIC_H -#define KERNEL_HYPERBOLIC_H - - - -#include "base_kernel.h" - -namespace SPH -{ - /** - * @class KernelHyperbolic - * @brief Kernel from Yang el al. - */ - class KernelHyperbolic : public Kernel - { - protected: - virtual void setBasicParameters() override; - public: - KernelHyperbolic(); - - virtual Real W_1D(const Real q) const override; - virtual Real W_2D(const Real q) const override; - virtual Real W_3D(const Real q) const override; - - virtual Real dW_1D(const Real q) const override; - virtual Real dW_2D(const Real q) const override; - virtual Real dW_3D(const Real q) const override; - - virtual Real d2W_1D(const Real q) const override; - virtual Real d2W_2D(const Real q) const override; - virtual Real d2W_3D(const Real q) const override; - }; -} -#endif //KERNEL_HYPERBOLIC_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/kernels/kernel_quadratic.cpp b/SPHINXsys/src/shared/kernels/kernel_quadratic.cpp deleted file mode 100644 index be98c30d62..0000000000 --- a/SPHINXsys/src/shared/kernels/kernel_quadratic.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/** - * @file kernel_hyperbolic.cpp - * @author Luhui Han, Chi ZHang, Yongchuan Yu and Xiangyu Hu - */ - -#include "kernel_quadratic.h" - -#include - -namespace SPH -{ - //=================================================================================================// - KernelQuadratic::KernelQuadratic() - : Kernel("QuadraticKernel") - { - std::cout << "\n Error: The KernelQuadratic is not implemented yet! \n"; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - //=================================================================================================// - void KernelQuadratic::setBasicParameters() - { - factor_W_1D_ = inv_h_ / 7.0; - factor_W_2D_ = inv_h_ * inv_h_ / (3.0 * Pi); - factor_W_3D_ = inv_h_ * inv_h_ * inv_h_ / Pi; - } - //=================================================================================================// - Real KernelQuadratic::W_1D(const Real q) const - { - return 5.0 * (3.0 * q * q - 12.0 * q + 12.0) / 64.0; - } - //=================================================================================================// - Real KernelQuadratic::W_2D(const Real q) const - { - return W_1D(q); - } - //=================================================================================================// - Real KernelQuadratic::W_3D(const Real q) const - { - return W_1D(q); - } - //=================================================================================================// - Real KernelQuadratic::dW_1D(const Real q) const - { - if (q < 1.0) { - return (-6.0 + 3.0 * powerN(q, 2)); - } - else { - return powerN(2.0 - q, 2) * (-1.0); - } - } - //=================================================================================================// - Real KernelQuadratic::dW_2D(const Real q) const - { - return dW_1D(q); - } - //=================================================================================================// - Real KernelQuadratic::dW_3D(const Real q) const - { - return 15.0 * (q - 2.0) / 32.0; - } - //=================================================================================================// - Real KernelQuadratic::d2W_1D(const Real q) const - { - if (q < 1.0) { - return 6.0 * q; - } - else { - return 2.0 * (2.0 - q); - } - } - //=================================================================================================// - Real KernelQuadratic::d2W_2D(const Real q) const - { - return d2W_1D(q); - } - //=================================================================================================// - Real KernelQuadratic::d2W_3D(const Real q) const - { - return 15.0 / 32.0; - } - //=================================================================================================// -} - \ No newline at end of file diff --git a/SPHINXsys/src/shared/kernels/kernel_quadratic.h b/SPHINXsys/src/shared/kernels/kernel_quadratic.h deleted file mode 100644 index 991eec2dee..0000000000 --- a/SPHINXsys/src/shared/kernels/kernel_quadratic.h +++ /dev/null @@ -1,67 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file kernel_quadratic.h -* @brief Here, we define quadratic kernel functions but not fully implemented it yet. -* @details This kernel function is very simple. -* I put here because some references use it for solid dynamics. -* However, I am quite skeptical on it application in fluid dynamics, -* in which the first order consistency correction is not applied. -* @author Xiangyu Hu -*/ - - -#ifndef KERNEL_QUADRATIC_H -#define KERNEL_QUADRATIC_H - - - -#include "base_kernel.h" - -namespace SPH -{ - /** - * @class KernelQuadratic - * @brief Kernel from Yang el al. - */ - class KernelQuadratic : public Kernel - { - protected: - virtual void setBasicParameters() override; - public: - KernelQuadratic(); - - virtual Real W_1D(const Real q) const override; - virtual Real W_2D(const Real q) const override; - virtual Real W_3D(const Real q) const override; - - virtual Real dW_1D(const Real q) const override; - virtual Real dW_2D(const Real q) const override; - virtual Real dW_3D(const Real q) const override; - - virtual Real d2W_1D(const Real q) const override; - virtual Real d2W_2D(const Real q) const override; - virtual Real d2W_3D(const Real q) const override; - }; -} -#endif //KERNEL_QUADRATIC_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/kernels/kernel_tabulated.hpp b/SPHINXsys/src/shared/kernels/kernel_tabulated.hpp deleted file mode 100644 index 6332b4f071..0000000000 --- a/SPHINXsys/src/shared/kernels/kernel_tabulated.hpp +++ /dev/null @@ -1,152 +0,0 @@ -/** -* @file kerneltabulated.hpp -* @brief This is the class for tabulated kernels using template. -* @details This kernel tabulate a kernel function -* so that computing any kernel will have cost the same amount of time. -* @author Yongchuan Yu, Massoud Rezevand, Chi ZHang and Xiangyu Hu -*/ - - -#ifndef KERNEL_TABULATED_HPP -#define KERNEL_TABULATED_HPP - - - -#include "base_kernel.h" -#include - -namespace SPH -{ - template - class KernelTabulated : public Kernel - { - protected: - KernelType *original_kernel_; - int kernel_resolution_; - Real dq_ , delta_q_0_, delta_q_1_, delta_q_2_, delta_q_3_; - StdVec w_1d, w_2d, w_3d; - StdVec dw_1d, dw_2d, dw_3d; - StdVec d2w_1d, d2w_2d, d2w_3d; - - virtual void setBasicParameters() override; - /** interpolation function, Four-point Lagrangian interpolation. */ - Real InterpolationCubic(const StdVec &data, Real q) const { - int location = (int)floor(q / dq_); - int i = location + 1; - Real fraction_1 = q - Real(location)*dq_; //fraction_1 correspond to i - Real fraction_0 = fraction_1 + dq_; //fraction_0 correspond to i-1 - Real fraction_2 = fraction_1 - dq_; //fraction_2 correspond to i+1 - Real fraction_3 = fraction_1 - 2 * dq_; ////fraction_3 correspond to i+2 - - return ((fraction_1 * fraction_2 * fraction_3) / delta_q_0_ * data[i - 1] - + (fraction_0 * fraction_2 * fraction_3) / delta_q_1_ * data[i] - + (fraction_0 * fraction_1 * fraction_3) / delta_q_2_ * data[i + 1] - + (fraction_0 * fraction_1 * fraction_2) / delta_q_3_ * data[i + 2]); - }; - public: - KernelTabulated(int kernel_resolution); - - virtual Real KernelSize() const { return original_kernel_->KernelSize(); }; - - virtual Real W_1D(const Real q) const override; - virtual Real W_2D(const Real q) const override; - virtual Real W_3D(const Real q) const override; - - virtual Real dW_1D(const Real q) const override; - virtual Real dW_2D(const Real q) const override; - virtual Real dW_3D(const Real q) const override; - - virtual Real d2W_1D(const Real q) const override; - virtual Real d2W_2D(const Real q) const override; - virtual Real d2W_3D(const Real q) const override; - }; - //=================================================================================================// - template - KernelTabulated::KernelTabulated(int kernel_resolution) - : Kernel("KernelTabulated"), kernel_resolution_(kernel_resolution) {} - //=================================================================================================// - template - void KernelTabulated::setBasicParameters() - { - original_kernel_ = new KernelType(); - original_kernel_->initialize(h_); - factor_W_1D_ = original_kernel_->FactorW1D(); - factor_W_2D_ = original_kernel_->FactorW2D(); - factor_W_3D_ = original_kernel_->FactorW3D(); - - dq_ = KernelSize() / Real(kernel_resolution_); - for (int i = 0; i < kernel_resolution_ + 4; i++) - { - w_1d.push_back(original_kernel_->W_1D(Real(i - 1)*dq_)); - w_2d.push_back(original_kernel_->W_2D(Real(i - 1)*dq_)); - w_3d.push_back(original_kernel_->W_3D(Real(i - 1)*dq_)); - dw_1d.push_back(original_kernel_->dW_1D(Real(i - 1)*dq_)); - dw_2d.push_back(original_kernel_->dW_2D(Real(i - 1)*dq_)); - dw_3d.push_back(original_kernel_->dW_3D(Real(i - 1)*dq_)); - d2w_1d.push_back(original_kernel_->d2W_1D(Real(i - 1) * dq_)); - d2w_2d.push_back(original_kernel_->d2W_2D(Real(i - 1) * dq_)); - d2w_3d.push_back(original_kernel_->d2W_3D(Real(i - 1) * dq_)); - } - - delta_q_0_ = (-1.0 * dq_) * (-2.0 * dq_) * (-3.0 * dq_); - delta_q_1_ = dq_ * (-1.0 * dq_) * (-2.0 * dq_); - delta_q_2_ = (2.0 * dq_) * dq_ * (-1.0 * dq_); - delta_q_3_ = (3.0 * dq_) * (2.0 * dq_) * dq_; - } - //=================================================================================================// - template - Real KernelTabulated ::W_1D(Real q) const - { - return InterpolationCubic(w_1d, q); - } - //=================================================================================================// - template - Real KernelTabulated::W_2D(Real q) const - { - return InterpolationCubic(w_2d, q); - } - //=================================================================================================// - template - Real KernelTabulated::W_3D(Real q) const - { - return InterpolationCubic(w_3d, q); - } - //=================================================================================================// - template - Real KernelTabulated::dW_1D(Real q) const - { - return InterpolationCubic(dw_1d, q); - } - //=================================================================================================// - template - Real KernelTabulated::dW_2D(Real q) const - { - return InterpolationCubic(dw_2d, q); - } - //=================================================================================================// - template - Real KernelTabulated::dW_3D(Real q) const - { - return InterpolationCubic(dw_3d, q); - } - //=================================================================================================// - template - Real KernelTabulated::d2W_1D(Real q) const - { - return InterpolationCubic(d2w_1d, q); - } - //=================================================================================================// - template - Real KernelTabulated::d2W_2D(Real q) const - { - return InterpolationCubic(d2w_2d, q); - } - //=================================================================================================// - template - Real KernelTabulated::d2W_3D(Real q) const - { - return InterpolationCubic(d2w_3d, q); - } - //=================================================================================================// -} -#endif //KERNEL_TABULATED_HPP \ No newline at end of file diff --git a/SPHINXsys/src/shared/kernels/kernel_wenland_c2.cpp b/SPHINXsys/src/shared/kernels/kernel_wenland_c2.cpp deleted file mode 100644 index 4904a92fa3..0000000000 --- a/SPHINXsys/src/shared/kernels/kernel_wenland_c2.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @file kernel_wenland.cpp - * @author Luhui Han, Chi ZHang, Yongchuan Yu and Xiangyu Hu - */ -#include "kernel_wenland_c2.h" - -#include - -namespace SPH -{ - //=================================================================================================// - KernelWendlandC2::KernelWendlandC2() - : Kernel("Wendland2CKernel") {} - //=================================================================================================// - void KernelWendlandC2::setBasicParameters() - { - factor_W_1D_ = inv_h_ * 3.0 / 4.0; - factor_W_2D_ = inv_h_ * inv_h_ * 7.0 / (4.0 * Pi); - factor_W_3D_ = inv_h_ * inv_h_ * inv_h_ * 21.0 / (16.0 * Pi); - } - //=================================================================================================// - Real KernelWendlandC2::W_1D(const Real q) const - { - return powerN(1.0 - 0.5*q, 4) * (1.0 + 2.0 * q); - } - //=================================================================================================// - Real KernelWendlandC2::W_2D(const Real q) const - { - return W_1D(q); - } - //=================================================================================================// - Real KernelWendlandC2::W_3D(const Real q) const - { - return W_2D(q); - } - //=================================================================================================// - Real KernelWendlandC2::dW_1D(const Real q) const - { - return 0.625 * powerN(q - 2.0, 3) * q; - } - //=================================================================================================// - Real KernelWendlandC2::dW_2D(const Real q) const - { - return dW_1D(q); - } - //=================================================================================================// - Real KernelWendlandC2::dW_3D(const Real q) const - { - return dW_2D(q); - } - //=================================================================================================// - Real KernelWendlandC2::d2W_1D(const Real q) const - { - return 1.25 * powerN(q - 2.0, 2) * (2.0 * q - 1.0); - } - //=================================================================================================// - Real KernelWendlandC2::d2W_2D(const Real q) const - { - return d2W_1D(q); - } - //=================================================================================================// - Real KernelWendlandC2::d2W_3D(const Real q) const - { - return d2W_2D(q); - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/kernels/kernel_wenland_c2.h b/SPHINXsys/src/shared/kernels/kernel_wenland_c2.h deleted file mode 100644 index a4dd23f857..0000000000 --- a/SPHINXsys/src/shared/kernels/kernel_wenland_c2.h +++ /dev/null @@ -1,67 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file kernel_wenland_c2.h -* @brief This is the class for Wenland kernel. -* @details NThis kernel has compact support of 2h. -* The smoothing length h can be variable when variable h functions are applied. -* @author Luhui Han, Chi ZHang and Xiangyu Hu -*/ - - -#ifndef KERNEL_WENLAND_C2_H -#define KERNEL_WENLAND_C2_H - - - -#include "base_kernel.h" - -namespace SPH -{ - /** - * @class KernelWendlandC2 - * @brief Kernel WendlandC2 - */ - class KernelWendlandC2 : public Kernel - { - protected: - virtual void setBasicParameters() override; - public: - KernelWendlandC2(); - - /** Calculates the kernel value for - the given distance of two particles */ - virtual Real W_1D(const Real q) const override; - virtual Real W_2D(const Real q) const override; - virtual Real W_3D(const Real q) const override; - - virtual Real dW_1D(const Real q) const override; - virtual Real dW_2D(const Real q) const override; - virtual Real dW_3D(const Real q) const override; - - virtual Real d2W_1D(const Real q) const override; - virtual Real d2W_2D(const Real q) const override; - virtual Real d2W_3D(const Real q) const override; - }; -} -#endif //KERNEL_WENLAND_C2_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/materials/CMakeLists.txt b/SPHINXsys/src/shared/materials/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/materials/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/materials/all_materials.h b/SPHINXsys/src/shared/materials/all_materials.h deleted file mode 100644 index fa1d3e70cd..0000000000 --- a/SPHINXsys/src/shared/materials/all_materials.h +++ /dev/null @@ -1,16 +0,0 @@ -/** -* @file all_materials.h -* @brief This is the header file that user code should include to pick up all -* material -* @author Xiangyu Hu and Chi Zhang -*/ - -#pragma once - -#include "weakly_compressible_fluid.h" -#include "elastic_solid.h" -#include "inelastic_solid.h" -#include "complex_solid.h" -#include "complex_solid.hpp" -#include "diffusion_reaction.h" -#include "compressible_fluid.h" \ No newline at end of file diff --git a/SPHINXsys/src/shared/materials/base_material.h b/SPHINXsys/src/shared/materials/base_material.h deleted file mode 100644 index ff86d4abb1..0000000000 --- a/SPHINXsys/src/shared/materials/base_material.h +++ /dev/null @@ -1,188 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file base_material.h - * @brief This is the base classes of all materials. - * A function in a derived material class returns a value with the inputs - * from the particle data. - * Basically, it is a interface from which - * one can access derived material by dynamic cast. - * Note that the derived material may have position dependent or - * local properties. - * @author Chi Zhang and Xiangyu Hu - */ - - -#ifndef BASE_MATERIAL_H -#define BASE_MATERIAL_H - - - -#include "base_data_package.h" -#include "base_particles.h" - -#include - -namespace SPH { - - class FluidParticles; - class SolidParticles; - - /** @class BaseMaterial - * @brief Base of all materials - * @details Note that the case dependent parameters of the material properties - * will be defined in applications. - */ - class BaseMaterial - { - protected: - std::string material_name_; - std::string parameters_name_; - Real rho0_; /**< reference density. */ - BaseParticles* base_particles_; - XmlEngine reload_material_xml_engine_; - ParticleVariableList reload_local_parameters_; - - virtual void assignDerivedMaterialParameters() {}; - public: - BaseMaterial() : material_name_("BaseMaterial"), parameters_name_("LocalParameters"), - rho0_(1.0), base_particles_(nullptr), - reload_material_xml_engine_("xml_material", "material_paramaters") {}; - virtual ~BaseMaterial() {}; - - /** This will be called in BaseParticle constructor - * and is important because particles are not defined in SPHBody constructor. - * For a composite material, i.e. there is a material pointer with another material, - * one need assign the base particle to that material too. */ - virtual void assignBaseParticles(BaseParticles* base_particles) - { - base_particles_ = base_particles; - }; - std::string MaterialName() { return material_name_; } - std::string LocalParametersName() { return parameters_name_; } - Real ReferenceDensity() { return rho0_; }; - - virtual void writeToXmlForReloadLocalParameters(std::string& filefullpath) - { - std::cout << "\n Material properties writing. " << std::endl; - size_t total_real_particles = base_particles_->total_real_particles_; - ParticleData& all_particle_data = base_particles_->all_particle_data_; - base_particles_->resizeXmlDocForParticles(reload_material_xml_engine_); - WriteAParticleVariableToXml - write_variable_to_xml(reload_material_xml_engine_, total_real_particles); - loopParticleData(all_particle_data, reload_local_parameters_, write_variable_to_xml); - reload_material_xml_engine_.writeToXmlFile(filefullpath); - std::cout << "\n Material properties writing finished. " << std::endl; - }; - - virtual void readFromXmlForLocalParameters(std::string& filefullpath) - { - reload_material_xml_engine_.loadXmlFile(filefullpath); - size_t total_real_particles = base_particles_->total_real_particles_; - ParticleData& all_particle_data = base_particles_->all_particle_data_; - ReadAParticleVariableFromXml - read_variable_from_xml(reload_material_xml_engine_, total_real_particles); - loopParticleData(all_particle_data, reload_local_parameters_, read_variable_from_xml); - - if (total_real_particles != reload_material_xml_engine_.SizeOfXmlDoc()) - { - std::cout << "\n Error: reload material properties does not match!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - else - { - std::cout << "\n Material properties reading finished." << std::endl; - } - }; - - virtual BaseMaterial* ThisObjectPtr() { return this; }; - }; - - /** @class Fluid - * @brief Base class of all fluids - */ - class Fluid : public BaseMaterial - { - protected: - Real c0_, mu_; /**< reference sound speed, viscosity. */ - FluidParticles* fluid_particles_; - - virtual void assignDerivedMaterialParameters() override - { - BaseMaterial::assignDerivedMaterialParameters(); - }; - public: - Fluid() : BaseMaterial(), c0_(1.0), mu_(0.0), - fluid_particles_(nullptr) { - material_name_ = "Fluid"; - }; - virtual ~Fluid() {}; - - void assignFluidParticles(FluidParticles* fluid_particles) - { - fluid_particles_ = fluid_particles; - }; - - Real ReferenceSoundSpeed() { return c0_; }; - Real ReferenceViscosity() { return mu_; }; - virtual Real getPressure(Real rho) = 0; - virtual Real getPressure(Real rho, Real rho_e) { return getPressure(rho); }; - virtual Real DensityFromPressure(Real p) = 0; - virtual Real getSoundSpeed(Real p = 0.0, Real rho = 1.0) = 0; - virtual Fluid* ThisObjectPtr() override { return this; }; - }; - - /** @class Solid - * @brief Base class of all solid materials - */ - class Solid : public BaseMaterial - { - public: - Solid() : BaseMaterial(), contact_stiffness_(1.0), - contact_friction_(0.0), solid_particles_(nullptr) - { - material_name_ = "Solid"; - }; - virtual ~Solid() {}; - - void assignSolidParticles(SolidParticles* solid_particles) - { - solid_particles_ = solid_particles; - }; - - Real ContactFriction() { return contact_friction_; }; - Real ContactStiffness() { return contact_stiffness_; }; - virtual Solid* ThisObjectPtr() override { return this; }; - protected: - Real contact_stiffness_; /**< contact-force stiffness related to bulk modulus*/ - Real contact_friction_; /**< friction property mimic fluid viscosity*/ - SolidParticles* solid_particles_; - - virtual void assignDerivedMaterialParameters() override - { - BaseMaterial::assignDerivedMaterialParameters(); - }; - }; -} -#endif //BASE_MATERIAL_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/materials/complex_solid.h b/SPHINXsys/src/shared/materials/complex_solid.h deleted file mode 100644 index 610f384320..0000000000 --- a/SPHINXsys/src/shared/materials/complex_solid.h +++ /dev/null @@ -1,56 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file complex_solid.h -* @brief These are classes for define complex solid materials. -* @author Xiangyu Hu and Chi Zhang -*/ -#pragma once - -#include "elastic_solid.h" - -namespace SPH { - - class ActiveMuscleParticles; - - /** - * @class ActiveMuscle - * @brief Here, the active reponse is considered. - */ - template - class ActiveMuscle : public MuscleType - { - protected: - ActiveMuscleParticles* active_muscle_particles_; - - virtual void assignDerivedMaterialParameters() override; - public: - ActiveMuscle(); - virtual ~ActiveMuscle() {}; - - void assignActiveMuscleParticles(ActiveMuscleParticles* active_muscle_particles); - /** compute the stress through Constitutive relation. */ - virtual Matd ConstitutiveRelation(Matd& deformation, size_t index_i) override; - virtual ActiveMuscle* ThisObjectPtr() override { return this; }; - }; -} diff --git a/SPHINXsys/src/shared/materials/complex_solid.hpp b/SPHINXsys/src/shared/materials/complex_solid.hpp deleted file mode 100644 index a7ca769550..0000000000 --- a/SPHINXsys/src/shared/materials/complex_solid.hpp +++ /dev/null @@ -1,41 +0,0 @@ -/** -* @file complex_solid.hpp -* @brief These are implementation of the classes for complex solids. -* @author Xiangyu Hu and Chi Zhang -*/ -#pragma once - -#include "complex_solid.h" -#include "solid_particles.h" - -using namespace std; - -namespace SPH { - //=============================================================================================// - template - ActiveMuscle::ActiveMuscle() : MuscleType(), active_muscle_particles_(nullptr) - { - MuscleType::material_name_ = "ActiveMuscle"; - } - //=============================================================================================// - template - void ActiveMuscle::assignDerivedMaterialParameters() - { - MuscleType::assignDerivedMaterialParameters(); - } - //=============================================================================================// - template - void ActiveMuscle:: - assignActiveMuscleParticles(ActiveMuscleParticles* active_muscle_particles) - { - active_muscle_particles_ = active_muscle_particles; - } - //=============================================================================================// - template - Matd ActiveMuscle::ConstitutiveRelation(Matd& deformation, size_t index_i) - { - return MuscleType::ConstitutiveRelation(deformation, index_i) + - active_muscle_particles_->active_contraction_stress_[index_i] * MuscleType::MuscleFiberDirection(index_i); - } - //=============================================================================================// - } diff --git a/SPHINXsys/src/shared/materials/compressible_fluid.cpp b/SPHINXsys/src/shared/materials/compressible_fluid.cpp deleted file mode 100644 index de0b4d3be7..0000000000 --- a/SPHINXsys/src/shared/materials/compressible_fluid.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @file compressible_fluid.cpp - * @author Luhui Han, Chi ZHang ,Xiangyu Hu and Zhentong Wang - */ - -#include "compressible_fluid.h" - -using namespace std; - -namespace SPH { - //===============================================================// - Real CompressibleFluid::getPressure(Real rho, Real rho_e) - { - return rho_e * (gamma_ - 1.0); - } - //===============================================================// - Real CompressibleFluid::getSoundSpeed(Real p, Real rho) - { - return sqrt(gamma_ * p / rho); - } - //===============================================================// -} diff --git a/SPHINXsys/src/shared/materials/compressible_fluid.h b/SPHINXsys/src/shared/materials/compressible_fluid.h deleted file mode 100644 index cbdbbe9bc0..0000000000 --- a/SPHINXsys/src/shared/materials/compressible_fluid.h +++ /dev/null @@ -1,70 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file compressible_fluid.h - * @brief Describe the compressible fluid which is used - * model compressible fluids. Here, we have ideal gas equation of states. - * @author Xiangyu Hu, Luhui Han, Chi Zhang and Zhentong Wang - */ - -#pragma once - -#include "base_material.h" - -namespace SPH { - - class CompressibleFluidParticles; - - /** - * @class CompressibleFluid - * @brief Ideal gas equation of state (EOS). - */ - class CompressibleFluid : public Fluid - { - protected: - Real gamma_; /** heat capacity ratio */ - CompressibleFluidParticles * compressible_fluid_particles_; - - virtual void assignDerivedMaterialParameters() override - { - Fluid::assignDerivedMaterialParameters(); - }; - public: - explicit CompressibleFluid() : Fluid(), gamma_(1.0) - { - material_name_ = "CompressibleFluid"; - }; - virtual ~CompressibleFluid() {}; - - void assignCompressibleFluidParticles(CompressibleFluidParticles* compressible_fluid_particles) - { - compressible_fluid_particles_ = compressible_fluid_particles; - }; - Real HeatCapacityRatio() { return gamma_; }; - virtual Real getPressure(Real rho, Real rho_e) override; - virtual Real getPressure(Real rho) override { return 0.0; }; - virtual Real DensityFromPressure(Real p) override { return 0.0; }; - virtual Real getSoundSpeed(Real p, Real rho) override; - virtual CompressibleFluid* ThisObjectPtr() override { return this; }; - }; -} diff --git a/SPHINXsys/src/shared/materials/diffusion_reaction.cpp b/SPHINXsys/src/shared/materials/diffusion_reaction.cpp deleted file mode 100644 index 0777d523ec..0000000000 --- a/SPHINXsys/src/shared/materials/diffusion_reaction.cpp +++ /dev/null @@ -1,149 +0,0 @@ -/** - * @file diffusion_reaction.cpp - * @brief These are classes for diffusion and reaction properties - * @author Chi Zhang and Xiangyu Hu - */ - -#include "diffusion_reaction.h" - -#include "diffusion_reaction_particles.h" - -namespace SPH -{ -//=================================================================================================// - void DirectionalDiffusion::initializeDirectionalDiffusivity(Real diff_cf, Real bias_diff_cf, Vecd bias_direction) - { - bias_diff_cf_ = bias_diff_cf; - bias_direction_ = bias_direction; - Matd diff_i = diff_cf_* Matd(1.0) - + bias_diff_cf_ * SimTK::outer(bias_direction_, bias_direction_); - transformed_diffusivity_ = inverseCholeskyDecomposition(diff_i); - }; - //=================================================================================================// - void LocalDirectionalDiffusion::assignBaseParticles(BaseParticles* base_particles) - { - DirectionalDiffusion::assignBaseParticles(base_particles); - initializeFiberDirection(); - }; - //=================================================================================================// - void LocalDirectionalDiffusion::initializeFiberDirection() - { - base_particles_->registerAVariable(local_bias_direction_, "Fiber"); - base_particles_->addAVariableNameToList(reload_local_parameters_, "Fiber"); - } - //=================================================================================================// - void LocalDirectionalDiffusion::readFromXmlForLocalParameters(std::string& filefullpath) - { - BaseMaterial::readFromXmlForLocalParameters(filefullpath); - size_t total_real_particles = base_particles_->total_real_particles_; - for (size_t i = 0; i != total_real_particles; i++) - { - Matd diff_i = diff_cf_ * Matd(1.0) - + bias_diff_cf_ * SimTK::outer(local_bias_direction_[i], local_bias_direction_[i]); - local_transformed_diffusivity_.push_back(inverseCholeskyDecomposition(diff_i)); - } - std::cout << "\n Local diffusion parameters setup finished " << std::endl; - }; - //=================================================================================================// - void ElectroPhysiologyReaction::initializeElectroPhysiologyReaction(size_t voltage, size_t gate_variable, - size_t active_contraction_stress) - { - voltage_ = voltage; - gate_variable_ = gate_variable; - active_contraction_stress_ = active_contraction_stress; - - reactive_species_.push_back(voltage); - reactive_species_.push_back(gate_variable); - reactive_species_.push_back(active_contraction_stress); - - get_production_rates_.push_back(std::bind(&ElectroPhysiologyReaction::getProductionRateIonicCurrent, this, _1, _2)); - get_production_rates_.push_back(std::bind(&ElectroPhysiologyReaction::getProductionRateGateVariable, this, _1, _2)); - get_production_rates_.push_back(std::bind(&ElectroPhysiologyReaction::getProductionActiveContractionStress, this, _1, _2)); - - get_loss_rates_.push_back(std::bind(&ElectroPhysiologyReaction::getLossRateIonicCurrent, this, _1, _2)); - get_loss_rates_.push_back(std::bind(&ElectroPhysiologyReaction::getLossRateGateVariable, this, _1, _2)); - get_loss_rates_.push_back(std::bind(&ElectroPhysiologyReaction::getLossRateActiveContractionStress, this, _1, _2)); - }; -//=================================================================================================// - Real ElectroPhysiologyReaction:: - getProductionActiveContractionStress(StdVec>& species, size_t particle_i) - { - Real voltage_dim = species[voltage_][particle_i] * 100.0 - 80.0; - Real factor = 0.1 + (1.0 - 0.1) * exp(-exp(-voltage_dim)); - return factor * k_a_ * (voltage_dim + 80.0); - } -//=================================================================================================// - Real ElectroPhysiologyReaction:: - getLossRateActiveContractionStress(StdVec>& species, size_t particle_i) - { - Real voltage_dim = species[voltage_][particle_i] * 100.0 - 80.0; - return 0.1 + (1.0 - 0.1) * exp(-exp(-voltage_dim)); - } -//=================================================================================================// - Real AlievPanfilowModel:: - getProductionRateIonicCurrent(StdVec>& species, size_t particle_i) - { - Real voltage = species[voltage_][particle_i]; - return - k_ * voltage * (voltage * voltage - a_ * voltage - voltage) / c_m_; - } -//=================================================================================================// - Real AlievPanfilowModel:: - getLossRateIonicCurrent(StdVec>& species, size_t particle_i) - { - Real gate_variable = species[gate_variable_][particle_i]; - return (k_ * a_ + gate_variable) / c_m_; - } -//=================================================================================================// - Real AlievPanfilowModel:: - getProductionRateGateVariable(StdVec>& species, size_t particle_i) - { - Real voltage = species[voltage_][particle_i]; - Real gate_variable = species[gate_variable_][particle_i]; - Real temp = epsilon_ + mu_1_ * gate_variable / (mu_2_ + voltage + Eps); - return - temp * k_ * voltage * (voltage - b_ - 1.0); - } -//=================================================================================================// - Real AlievPanfilowModel:: - getLossRateGateVariable(StdVec>& species, size_t particle_i) - { - Real voltage = species[voltage_][particle_i]; - Real gate_variable = species[gate_variable_][particle_i]; - return epsilon_ + mu_1_ * gate_variable / (mu_2_ + voltage + Eps); - } -//=================================================================================================// - MonoFieldElectroPhysiology::MonoFieldElectroPhysiology(ElectroPhysiologyReaction* electro_physiology_reaction) - : DiffusionReactionMaterial(electro_physiology_reaction), diff_cf_(1.0), bias_diff_cf_(0.0), - bias_direction_(FirstAxisVector(Vecd(0))) - { - material_name_ = "MonoFieldElectroPhysiology"; - insertASpecies("Voltage"); - insertASpecies("GateVariable"); - insertASpecies("ActiveContractionStress"); - - electro_physiology_reaction->initializeElectroPhysiologyReaction(species_indexes_map_["Voltage"], - species_indexes_map_["GateVariable"], species_indexes_map_["ActiveContractionStress"]); - }; -//=================================================================================================// - void MonoFieldElectroPhysiology::initializeDiffusion() - { - DirectionalDiffusion* voltage_diffusion - = new DirectionalDiffusion(species_indexes_map_["Voltage"], species_indexes_map_["Voltage"], - diff_cf_, bias_diff_cf_, bias_direction_); - species_diffusion_.push_back(voltage_diffusion); - } - //=================================================================================================// - void LocalMonoFieldElectroPhysiology::initializeDiffusion() - { - LocalDirectionalDiffusion* voltage_diffusion - = new LocalDirectionalDiffusion(species_indexes_map_["Voltage"], species_indexes_map_["Voltage"], - diff_cf_, bias_diff_cf_, bias_direction_); - species_diffusion_.push_back(voltage_diffusion); - } -//=================================================================================================// - void LocalMonoFieldElectroPhysiology::readFromXmlForLocalParameters(std::string& filefullpath) - { - species_diffusion_[0]->readFromXmlForLocalParameters(filefullpath); - } -//=================================================================================================// -} -//=================================================================================================// diff --git a/SPHINXsys/src/shared/materials/diffusion_reaction.h b/SPHINXsys/src/shared/materials/diffusion_reaction.h deleted file mode 100644 index ebed5c81d1..0000000000 --- a/SPHINXsys/src/shared/materials/diffusion_reaction.h +++ /dev/null @@ -1,338 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file diffussion_reaction.h - * @brief Describe the diffusive and reaction in which - * the dynamics is characterized by diffusion equation and reactive source terms. - * Typical physical processes are diffusion, heat conduction - * and chemical and biological reactions. - * @author Xiangyu Hu, Chi Zhang - */ - -#ifndef DIFFUSION_REACTION_H -#define DIFFUSION_REACTION_H - - - -#include "base_material.h" -#include "solid_particles.h" - -#include -#include -using namespace std::placeholders; - -namespace SPH -{ - template - class DiffusionReactionParticles; - class ElectroPhysiologyParticles; - /** - * @class BaseDiffusion - * @brief diffusion property abstract base class. - */ - class BaseDiffusion : public BaseMaterial - { - public: - BaseDiffusion(size_t diffusion_species_index, size_t gradient_species_index) : - BaseMaterial(), diffusion_species_index_(diffusion_species_index), - gradient_species_index_(gradient_species_index) {}; - virtual ~BaseDiffusion() {}; - - size_t diffusion_species_index_; - size_t gradient_species_index_; - - virtual Real getReferenceDiffusivity() = 0; - virtual Real getInterParticleDiffusionCoff(size_t particle_i, size_t particle_j, Vecd& direction_from_j_to_i) = 0; - }; - - /** - * @class IsotropicDiffusion - * @brief isotropic diffusion property. - */ - class IsotropicDiffusion : public BaseDiffusion - { - protected: - Real diff_cf_; /**< diffusion coefficient. */ - - public: - IsotropicDiffusion(size_t diffusion_species_index, size_t gradient_species_index, - Real diff_cf = 1.0) : BaseDiffusion(diffusion_species_index, gradient_species_index), - diff_cf_(diff_cf) {}; - virtual ~IsotropicDiffusion() {}; - - virtual Real getReferenceDiffusivity() override { return diff_cf_; }; - virtual Real getInterParticleDiffusionCoff(size_t particle_i, size_t particle_j, Vecd& direction_from_j_to_i) override - { - return diff_cf_; - }; - }; - - /** - * @class DirectionalDiffusion - * @brief Diffussion is biased along a specific direction. - */ - class DirectionalDiffusion : public IsotropicDiffusion - { - protected: - Vecd bias_direction_; /**< Reference bias direction. */ - Real bias_diff_cf_; /**< The bias diffusion coefficient along the fiber direction. */ - Matd transformed_diffusivity_; /**< The transformed diffusivity with inverse Cholesky decomposition. */ - - void initializeDirectionalDiffusivity(Real diff_cf, Real bias_diff_cf, Vecd bias_direction); - public: - DirectionalDiffusion(size_t diffusion_species_index, size_t gradient_species_index, - Real diff_cf, Real bias_diff_cf, Vecd bias_direction) : - IsotropicDiffusion(diffusion_species_index, gradient_species_index, diff_cf), - bias_direction_(bias_direction), bias_diff_cf_(bias_diff_cf), - transformed_diffusivity_(1.0) - { - initializeDirectionalDiffusivity(diff_cf, bias_diff_cf, bias_direction); - }; - virtual ~DirectionalDiffusion() {}; - - virtual Real getReferenceDiffusivity() override - { - return SMAX(diff_cf_, diff_cf_ + bias_diff_cf_); - }; - - virtual Real getInterParticleDiffusionCoff(size_t particle_index_i, - size_t particle_index_j, Vecd& inter_particle_direction) override - { - Vecd grad_ij = transformed_diffusivity_ * inter_particle_direction; - return 1.0 / grad_ij.scalarNormSqr(); - }; - }; - - /** - * @class LocalDirectionalDiffusion - * @brief Diffusion is biased along a specific direction. - */ - class LocalDirectionalDiffusion : public DirectionalDiffusion - { - protected: - StdLargeVec local_bias_direction_; - StdLargeVec local_transformed_diffusivity_; - - void initializeFiberDirection(); - public: - LocalDirectionalDiffusion(size_t diffusion_species_index, size_t gradient_species_index, - Real diff_cf, Real bias_diff_cf, Vecd bias_direction) - : DirectionalDiffusion(diffusion_species_index, gradient_species_index, diff_cf, bias_diff_cf, bias_direction) {}; - virtual ~LocalDirectionalDiffusion() {}; - virtual Real getInterParticleDiffusionCoff(size_t particle_index_i, size_t particle_index_j, Vecd& inter_particle_direction) override - { - Matd trans_diffusivity = getAverageValue(local_transformed_diffusivity_[particle_index_i], local_transformed_diffusivity_[particle_index_j]); - Vecd grad_ij = trans_diffusivity * inter_particle_direction; - return 1.0 / grad_ij.scalarNormSqr(); - }; - virtual void assignBaseParticles(BaseParticles* base_particles); - virtual void readFromXmlForLocalParameters(std::string& filefullpath) override; - }; - - /** Reaction functor . */ - typedef std::function>&, size_t particle_i)> ReactionFunctor; - /** - * @class BaseReactionModel - * @brief Base class for all reaction models. - */ - class BaseReactionModel - { - protected: - virtual void assignDerivedReactionParameters() = 0; - public: - BaseReactionModel() {}; - virtual ~BaseReactionModel() {}; - - IndexVector reactive_species_; - StdVec get_production_rates_; - StdVec get_loss_rates_; - }; - - /** - * @class AlievPanfilowModel - * @brief The simplest Electrophysiology Reaction model, - * which reduces the complex of array of ion currents to two variables that - * describe excitation and recovery. - */ - class ElectroPhysiologyReaction : public BaseReactionModel - { - protected: - Real k_a_; - size_t voltage_; - size_t gate_variable_; - size_t active_contraction_stress_; - - virtual Real getProductionRateIonicCurrent(StdVec>& species, size_t particle_i) = 0; - virtual Real getLossRateIonicCurrent(StdVec>& species, size_t particle_i) = 0; - virtual Real getProductionRateGateVariable(StdVec>& species, size_t particle_i) = 0; - virtual Real getLossRateGateVariable(StdVec>& species, size_t particle_i) = 0; - virtual Real getProductionActiveContractionStress(StdVec>& species, size_t particle_i); - virtual Real getLossRateActiveContractionStress(StdVec>& species, size_t particle_i); - virtual void assignDerivedReactionParameters() override {}; - public: - ElectroPhysiologyReaction() : BaseReactionModel(), k_a_(1.0), - voltage_(0), gate_variable_(1), active_contraction_stress_(2) {}; - virtual ~ElectroPhysiologyReaction() {}; - void initializeElectroPhysiologyReaction(size_t voltage, - size_t gate_variable, size_t active_contraction_stress); - }; - - class AlievPanfilowModel : public ElectroPhysiologyReaction - { - protected: - /** Parameters for two variable cell model. */ - Real k_, a_, b_, mu_1_, mu_2_, epsilon_, c_m_; - - virtual Real getProductionRateIonicCurrent(StdVec>& species, size_t particle_i) override; - virtual Real getLossRateIonicCurrent(StdVec>& species, size_t particle_i) override; - virtual Real getProductionRateGateVariable(StdVec>& species, size_t particle_i) override; - virtual Real getLossRateGateVariable(StdVec>& species, size_t particle_i) override; - virtual void assignDerivedReactionParameters() override - { - ElectroPhysiologyReaction::assignDerivedReactionParameters(); - }; - public: - AlievPanfilowModel() : ElectroPhysiologyReaction(), - k_(0.0), a_(0.0), b_(0.0), mu_1_(0.0), mu_2_(0.0), - epsilon_(0.0), c_m_(0.0) {}; - virtual ~AlievPanfilowModel() {}; - }; - - /** - * @class DiffusionReactionMaterial - * @brief Complex material for diffusion or/and reactions. - */ - template - class DiffusionReactionMaterial : public BaseMaterialType - { - protected: - - size_t number_of_species_; - size_t number_of_diffusion_species_; - std::map species_indexes_map_; - StdVec species_diffusion_; - BaseReactionModel* species_reaction_; - DiffusionReactionParticles* diffusion_reaction_particles_; - - virtual void assignDerivedMaterialParameters() override - { - BaseMaterialType::assignDerivedMaterialParameters(); - }; - - void insertASpecies(std::string species_name) - { - species_indexes_map_.insert(make_pair(species_name, number_of_species_)); - number_of_species_++; - }; - public: - /** Constructor for material only with diffusion. */ - DiffusionReactionMaterial() - : BaseMaterialType(), number_of_species_(0), species_reaction_(nullptr) - { - BaseMaterialType::material_name_ = "DiffusionMaterial"; - }; - /** Constructor for material with diffusion and reaction. */ - DiffusionReactionMaterial(BaseReactionModel* species_reaction) - : BaseMaterialType(), number_of_species_(0), species_reaction_(species_reaction) - { - BaseMaterialType::material_name_ = "DiffusionReactionMaterial"; - }; - virtual ~DiffusionReactionMaterial() {}; - - size_t NumberOfSpecies() { return number_of_species_; }; - size_t NumberOfSpeciesDiffusion() { return species_diffusion_.size(); }; - StdVec SpeciesDiffusion() { return species_diffusion_; }; - BaseReactionModel* SpeciesReaction() { return species_reaction_; }; - std::map SpeciesIndexMap() { return species_indexes_map_; }; - void assignDiffusionReactionParticles(DiffusionReactionParticles* diffusion_reaction_particles) - { - diffusion_reaction_particles_ = diffusion_reaction_particles; - for (size_t k = 0; k < species_diffusion_.size(); ++k) - species_diffusion_[k]->assignBaseParticles(diffusion_reaction_particles); - }; - /** - * @brief Get diffusion time step size. Here, I follow the reference: - * https://www.uni-muenster.de/imperia/md/content/physik_tp/lectures/ws2016-2017/num_methods_i/heat.pdf - */ - Real getDiffusionTimeStepSize(Real smoothing_length) - { - Real diff_coff_max = 0.0; - for (size_t k = 0; k < species_diffusion_.size(); ++k) - diff_coff_max = SMAX(diff_coff_max, species_diffusion_[k]->getReferenceDiffusivity()); - Real dimension = Real(Vecd(0).size()); - return 0.5 * smoothing_length * smoothing_length / diff_coff_max / dimension; - }; - /** Initialize diffusion material. */ - virtual void initializeDiffusion() = 0; - virtual DiffusionReactionMaterial* - ThisObjectPtr() override { return this; }; - }; - - /** - * @class MonoFieldElectroPhysiology - * @brief material class for electro_physiology. - */ - class MonoFieldElectroPhysiology - : public DiffusionReactionMaterial - { - protected: - Real diff_cf_; - Real bias_diff_cf_; - Vecd bias_direction_; - - virtual void assignDerivedMaterialParameters() override - { - DiffusionReactionMaterial::assignDerivedMaterialParameters(); - }; - public: - MonoFieldElectroPhysiology(ElectroPhysiologyReaction* electro_physiology_reaction); - virtual ~MonoFieldElectroPhysiology() {}; - - virtual void initializeDiffusion() override; - }; - - /** - * @class LocalMonoFieldElectroPhysiology - * @brief material class for electro_physiology with locally oriented fibers. - */ - class LocalMonoFieldElectroPhysiology - : public MonoFieldElectroPhysiology - { - protected: - virtual void assignDerivedMaterialParameters() override - { - MonoFieldElectroPhysiology::assignDerivedMaterialParameters(); - }; - public: - LocalMonoFieldElectroPhysiology(ElectroPhysiologyReaction* electro_physiology_reaction) - :MonoFieldElectroPhysiology(electro_physiology_reaction) { - MonoFieldElectroPhysiology::material_name_ = "LocalMonoFieldElectroPhysiology"; - }; - virtual ~LocalMonoFieldElectroPhysiology() {}; - - virtual void initializeDiffusion() override; - virtual void readFromXmlForLocalParameters(std::string& filefullpath) override; - }; -} -#endif //DIFFUSION_REACTION_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/materials/elastic_solid.cpp b/SPHINXsys/src/shared/materials/elastic_solid.cpp deleted file mode 100644 index 1094aac0da..0000000000 --- a/SPHINXsys/src/shared/materials/elastic_solid.cpp +++ /dev/null @@ -1,253 +0,0 @@ -/** - * @file elastic_solid.cpp - * @author Chi Zhang and Xiangyu Hu - */ - -#include "elastic_solid.h" - -#include "base_body.h" -#include "solid_particles.h" - -namespace SPH { - //=================================================================================================// - void ElasticSolid::assignElasticSolidParticles(ElasticSolidParticles* elastic_particles) - { - elastic_particles_ = elastic_particles; - } - //=================================================================================================// - void ElasticSolid::assignDerivedMaterialParameters() - { - Solid::assignDerivedMaterialParameters(); - setReferenceSoundSpeed(); - setTensileWaveSpeed(); - setShearWaveSpeed(); - setYoungsModulus(); - setShearModulus(); - setBulkModulus(); - setPoissonRatio(); - setContactStiffness(); - } - //=================================================================================================// - Matd ElasticSolid::NumericalDampingStress(Matd& F, Matd& dF_dt, Real smoothing_length, size_t particle_index_i) - { - Matd strain_rate = 0.5 * (~dF_dt * F + ~F * dF_dt); - Matd normal_rate = getDiagonal(strain_rate); - return 0.5 * rho0_ * (cs0_ * (strain_rate - normal_rate) + c0_ * normal_rate) * smoothing_length; - } - //=================================================================================================// - Real ElasticSolid::NumericalDamping(Real dE_dt_ij, Real smoothing_length) - { - return 0.5 * rho0_ * c0_ * dE_dt_ij * smoothing_length; - } - //=================================================================================================// - Real ElasticSolid::NumericalViscosity(Real smoothing_length) - { - return 0.5 * rho0_ * c0_ * smoothing_length; - } - //=================================================================================================// - Matd ElasticSolid::DeviatoricKirchhoff(const Matd& deviatoric_be) - { - return G0_ * deviatoric_be; - } - //=================================================================================================// - Real ElasticSolid::VolumetricKirchhoff(const Matd& deformation) - { - Real J = SimTK::det(deformation); - return 0.5 * K0_ * J * (J - 1); - } - //=================================================================================================// - Real LinearElasticSolid::getBulkModulus() - { - return youngs_modulus_ / 3.0 / (1.0 - 2.0 * poisson_ratio_); - } - //=================================================================================================// - Real LinearElasticSolid::getShearModulus() - { - return 0.5 * youngs_modulus_ / (1.0 + poisson_ratio_); - } - //=================================================================================================// - Real LinearElasticSolid::getLambda() - { - return nu_ * youngs_modulus_ / (1.0 + poisson_ratio_) / (1.0 - 2.0 * poisson_ratio_); - } - //=================================================================================================// - void LinearElasticSolid::setReferenceSoundSpeed() - { - c0_ = sqrt(getBulkModulus() / rho0_); - } - //=================================================================================================// - void LinearElasticSolid::setTensileWaveSpeed() - { - ct0_ = sqrt(youngs_modulus_ / rho0_); - } - //=================================================================================================// - void LinearElasticSolid::setShearWaveSpeed() - { - cs0_ = sqrt(getShearModulus() / rho0_); - } - //=================================================================================================// - void LinearElasticSolid::setShearModulus() - { - G0_ = getShearModulus(); - } - //=================================================================================================// - void LinearElasticSolid::setBulkModulus() - { - K0_ = getBulkModulus(); - } - //=================================================================================================// - void LinearElasticSolid::assignDerivedMaterialParameters() - { - ElasticSolid::assignDerivedMaterialParameters(); - lambda0_ = getLambda(); - std::cout << "The speed of sound: " << c0_ << std::endl; - std::cout << "The Lambda: " << lambda0_ << std::endl; - std::cout << "Contact stiffness: " << contact_stiffness_ << std::endl; - }; - //=================================================================================================// - Matd LinearElasticSolid::ConstitutiveRelation(Matd& F, size_t particle_index_i) - { - Matd strain = 0.5 * (~F * F - Matd(1.0)); - Matd sigmaPK2 = lambda0_ * strain.trace() * Matd(1.0) + 2.0 * G0_ * strain; - return sigmaPK2; - } - //=================================================================================================// - Matd NeoHookeanSolid::ConstitutiveRelation(Matd& F, size_t particle_index_i) - { - Matd right_cauchy = ~F * F; - Matd sigmaPK2 = G0_ * Matd(1.0) + (lambda0_ * log(det(F)) - G0_) * inverse(right_cauchy); - return sigmaPK2; - } - //=================================================================================================// - Matd FeneNeoHookeanSolid::ConstitutiveRelation(Matd& F, size_t particle_index_i) - { - Matd right_cauchy = ~F * F; - Matd strain = 0.5 * (right_cauchy - Matd(1.0)); - Matd sigmaPK2 = G0_ / (1.0 - 2.0 * strain.trace() / j1_m_) * Matd(1.0) - + (lambda0_ * log(det(F)) - G0_) * inverse(right_cauchy); - return sigmaPK2; - } - //=================================================================================================// - Real Muscle::getShearModulus() - { - return a0_[0] * b0_[0] + 2.0 * a0_[1] * b0_[1] + 2.0 * a0_[2] * b0_[2] + a0_[3] * b0_[3]; - } - //=================================================================================================// - Real Muscle::getPoissonRatio() - { - return 0.5 * (3.0 * bulk_modulus_ - 2.0 * getShearModulus()) / (3.0 * bulk_modulus_ + getShearModulus()); - } - //=================================================================================================// - Real Muscle::getLambda() - { - return bulk_modulus_ - 2.0 * getShearModulus() / 3.0; - } - //=================================================================================================// - Real Muscle::getYoungsModulus() - { - return 3.0 * bulk_modulus_ * (1.0 - 2.0 * getPoissonRatio()); - } - //=================================================================================================// - void Muscle::setReferenceSoundSpeed() - { - c0_ = sqrt(bulk_modulus_ / rho0_); - } - //=================================================================================================// - void Muscle::setTensileWaveSpeed() - { - ct0_ = sqrt(getYoungsModulus() / rho0_); - } - //=================================================================================================// - void Muscle::setShearWaveSpeed() - { - cs0_ = sqrt(getShearModulus() / rho0_); - } - //=================================================================================================// - void Muscle::setYoungsModulus() - { - E0_ = getYoungsModulus(); - } - //=================================================================================================// - void Muscle::setShearModulus() - { - G0_ = getShearModulus(); - } - //=================================================================================================// - void Muscle::setPoissonRatio() - { - nu_ = getPoissonRatio(); - } - //=================================================================================================// - void Muscle::assignDerivedMaterialParameters() - { - ElasticSolid::assignDerivedMaterialParameters(); - lambda0_ = getLambda(); - f0f0_ = SimTK::outer(f0_, f0_); - f0s0_ = SimTK::outer(f0_, s0_); - s0s0_ = SimTK::outer(s0_, s0_); - std::cout << "The speed of sound: " << c0_ << std::endl; - std::cout << "The Lambda: " << lambda0_ << std::endl; - std::cout << "Contact stiffness: " << contact_stiffness_ << std::endl; - } - //=================================================================================================// - Matd Muscle::ConstitutiveRelation(Matd& F, size_t i) - { - Matd right_cauchy = ~F * F; - Real I_ff_1 = SimTK::dot(right_cauchy * f0_, f0_) - 1.0; - Real I_ss_1 = SimTK::dot(right_cauchy * s0_, s0_) - 1.0; - Real I_fs = SimTK::dot(right_cauchy * f0_, s0_); - Real ln_J = log(det(F)); - Real I_1_1 = right_cauchy.trace() - Real(f0_.size()); - Matd sigmaPK2 = a0_[0] * exp(b0_[0] * I_1_1) * Matd(1.0) - + (lambda0_ * ln_J - a0_[0]) * inverse(right_cauchy) - + 2.0 * a0_[1] * I_ff_1 * exp(b0_[1] * I_ff_1 * I_ff_1) * f0f0_ - + 2.0 * a0_[2] * I_ss_1 * exp(b0_[2] * I_ss_1 * I_ss_1) * s0s0_ - + a0_[3] * I_fs * exp(b0_[3] * I_fs * I_fs) * f0s0_; - - return sigmaPK2; - } - //=================================================================================================// - Matd LocallyOrthotropicMuscle::ConstitutiveRelation(Matd& F, size_t i) - { - Matd right_cauchy = ~F * F; - Real I_ff_1 = SimTK::dot(right_cauchy * local_f0_[i], local_f0_[i]) - 1.0; - Real I_ss_1 = SimTK::dot(right_cauchy * local_s0_[i], local_s0_[i]) - 1.0; - Real I_fs = SimTK::dot(right_cauchy * local_f0_[i], local_s0_[i]); - Real ln_J = log(det(F)); - Real I_1_1 = right_cauchy.trace() - Real(Vecd(0).size()); - Matd sigmaPK2 = a0_[0] * exp(b0_[0] * I_1_1) * Matd(1.0) - + (lambda0_ * ln_J - a0_[0]) * inverse(right_cauchy) - + 2.0 * a0_[1] * I_ff_1 * exp(b0_[1] * I_ff_1 * I_ff_1) * local_f0f0_[i] - + 2.0 * a0_[2] * I_ss_1 * exp(b0_[2] * I_ss_1 * I_ss_1) * local_s0s0_[i] - + a0_[3] * I_fs * exp(b0_[3] * I_fs * I_fs) * local_f0s0_[i]; - - return sigmaPK2; - } - //=================================================================================================// - void LocallyOrthotropicMuscle::assignElasticSolidParticles(ElasticSolidParticles* elastic_particles) - { - Muscle::assignElasticSolidParticles(elastic_particles); - initializeFiberAndSheet(); - } - //=================================================================================================// - void LocallyOrthotropicMuscle::initializeFiberAndSheet() - { - base_particles_->registerAVariable(local_f0_, "Fiber"); - base_particles_->registerAVariable(local_s0_, "Sheet"); - base_particles_->addAVariableNameToList(reload_local_parameters_, "Fiber"); - base_particles_->addAVariableNameToList(reload_local_parameters_, "Sheet"); - } - //=================================================================================================// - void LocallyOrthotropicMuscle::readFromXmlForLocalParameters(std::string &filefullpath) - { - BaseMaterial::readFromXmlForLocalParameters(filefullpath); - size_t total_real_particles = base_particles_->total_real_particles_; - for(size_t i = 0; i != total_real_particles; i++) - { - local_f0f0_.push_back(SimTK::outer(local_f0_[i], local_f0_[i])); - local_s0s0_.push_back(SimTK::outer(local_s0_[i], local_s0_[i])); - local_f0s0_.push_back(SimTK::outer(local_f0_[i], local_s0_[i])); - } - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/materials/elastic_solid.h b/SPHINXsys/src/shared/materials/elastic_solid.h deleted file mode 100644 index 7ac0cfdd22..0000000000 --- a/SPHINXsys/src/shared/materials/elastic_solid.h +++ /dev/null @@ -1,263 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file elastic_solid.h -* @brief These are classes for define properties of elastic solid materials. -* These classes are based on isotropic linear elastic solid. -* Several more complex materials, including neo-hookean, FENE noe-hookean -* and anisotropic muscle, are derived from the basic elastic solid class. -* @author Xiangyu Hu and Chi Zhang -*/ - -#ifndef ELASTIC_SOLID_H -#define ELASTIC_SOLID_H - - -#include "base_material.h" -#include - -namespace SPH { - - //---------------------------------------------------------------------- - // preclaimed classes - //---------------------------------------------------------------------- - class ElasticSolidParticles; - - /** - * @class ElasticSolid - * @brief Abstract class for a generalized elastic solid - */ - class ElasticSolid : public Solid - { - protected: - Real c0_; /*< sound wave speed */ - Real ct0_; /*< tensile wave speed */ - Real cs0_; /*< shear wave speed */ - Real E0_; /*< Youngs or tensile modules */ - Real G0_; /*< shearmodules */ - Real K0_; /*< bulkmodules */ - Real nu_; /*< Poisson ratio */ - ElasticSolidParticles* elastic_particles_; - - virtual void setReferenceSoundSpeed() = 0; - virtual void setTensileWaveSpeed() = 0; - virtual void setShearWaveSpeed() = 0; - virtual void setYoungsModulus() = 0; - virtual void setShearModulus() = 0; - virtual void setBulkModulus() = 0; - virtual void setPoissonRatio() = 0; - void setContactStiffness() { contact_stiffness_ = c0_* c0_; }; - - virtual void assignDerivedMaterialParameters() override; - public: - ElasticSolid() : Solid(), c0_(1.0), ct0_(1.0), cs0_(0.0717), - E0_(1.0), G0_(0.5), K0_(1.0), nu_(0.0), elastic_particles_(nullptr) {}; - virtual ~ElasticSolid() {}; - - virtual void assignElasticSolidParticles(ElasticSolidParticles* elastic_particles); - Real ReferenceSoundSpeed() { return c0_; }; - Real TensileWaveSpeed() { return ct0_; }; - Real ShearWaveSpeed() { return cs0_; }; - Real YoungsModulus() { return E0_; }; - Real ShearModulus() { return G0_; }; - Real BulkModulus() { return K0_; }; - Real PoissonRatio() { return nu_; }; - - /** compute the stress through defoemation, which can be green-lagrangian tensor, left or right cauchy tensor. */ - virtual Matd ConstitutiveRelation(Matd& deformation, size_t particle_index_i) = 0; - //TODO: the NumericalViscosity and NumericalDampingStress to be delete after shell model done. - virtual Real NumericalViscosity(Real smoothing_length); - /** Compute numerical damping stress. */ - virtual Matd NumericalDampingStress(Matd& deformation, Matd& deformation_rate, Real smoothing_length, size_t particle_index_i); - /** numerical demaping is computed between particles i and j */ - virtual Real NumericalDamping(Real dE_dt_ij, Real smoothing_length); - - /** Deviatoric Kirchhoff stress related with the deviatoric part of left cauchy-green deformation tensor. - * Note that, dependent of the normalizeation of the later, the returned stress can be normalized or non-normalized. */ - virtual Matd DeviatoricKirchhoff(const Matd& deviatoric_be); - /** Volumetric Kirchhoff stress related with green-lagrangian tensor */ - virtual Real VolumetricKirchhoff(const Matd& deformation); - - virtual ElasticSolid* ThisObjectPtr() override {return this;}; - }; - - /** - * @class LinearElasticSolid - * @brief Isotropic linear elastic solid. - * Note that only basic parameters are used to set ElasticSolid parmaters - */ - class LinearElasticSolid : public ElasticSolid - { - public: - LinearElasticSolid() : ElasticSolid(), youngs_modulus_(1.0), poisson_ratio_(0), lambda0_(1.0) - { - material_name_ = "LinearElasticSolid"; - }; - LinearElasticSolid(Real rho_0, Real Youngs_modulus, Real poisson) : ElasticSolid() - { - material_name_ = "LinearElasticSolid"; - rho0_ = rho_0; - youngs_modulus_ = Youngs_modulus; - poisson_ratio_ = poisson; - - assignDerivedMaterialParameters(); - }; - virtual ~LinearElasticSolid() {}; - - virtual Matd ConstitutiveRelation(Matd& deformation, size_t particle_index_i) override; - protected: - Real youngs_modulus_; /*< Youngs modules as basic inpiut parameter */ - Real poisson_ratio_; /*< Poisson ratio as basic inpiut parameter */ - Real lambda0_; /*< first Lame parameter */ - - virtual void setReferenceSoundSpeed() override; - virtual void setTensileWaveSpeed() override; - virtual void setShearWaveSpeed() override; - virtual void setYoungsModulus() override { E0_ = youngs_modulus_; }; - virtual void setShearModulus() override; - virtual void setBulkModulus() override; - virtual void setPoissonRatio() override { nu_ = poisson_ratio_; }; - virtual void assignDerivedMaterialParameters() override; - private: - Real getBulkModulus(); - Real getShearModulus(); - Real getLambda(); - }; - - /** - * @class NeoHookeanSolid - * @brief Neo-Hookean solid - */ - class NeoHookeanSolid : public LinearElasticSolid - { - public: - NeoHookeanSolid() : LinearElasticSolid() - { - material_name_ = "NeoHookeanSolid"; - }; - NeoHookeanSolid(Real rho_0, Real Youngs_modulus, Real poisson) - : LinearElasticSolid(rho_0, Youngs_modulus, poisson) - { - material_name_ = "NeoHookeanSolid"; - }; - virtual ~NeoHookeanSolid() {}; - - /** second Piola-Kirchhoff stress related with green-lagrangian defomeation tensor */ - virtual Matd ConstitutiveRelation(Matd& deformation, size_t particle_index_i) override; - }; - - /** - * @class FeneNeoHookeanSolid - * @brief Neo-Hookean solid with finite extension - */ - class FeneNeoHookeanSolid : public LinearElasticSolid - { - protected: - Real j1_m_; /**< reference extension as basic paramter */ - public: - FeneNeoHookeanSolid() : LinearElasticSolid(), j1_m_(1.0) { - material_name_ = "FeneNeoHookeanSolid"; - }; - virtual ~FeneNeoHookeanSolid() {}; - virtual Matd ConstitutiveRelation(Matd& deformation, size_t particle_index_i) override; - }; - - /** - * @class Muscle - * @brief Globally orthotropic muscle. - */ - class Muscle : public ElasticSolid - { - public: - Muscle() : ElasticSolid(), - f0_(0), s0_(0), f0f0_(0), s0s0_(0), f0s0_(0), - a0_{ 1.0, 0.0, 0.0, 0.0 }, b0_{ 1.0, 0.0, 0.0, 0.0 }, bulk_modulus_(30.0) - { - material_name_ = "Muscle"; - }; - virtual ~Muscle() {}; - - virtual Matd MuscleFiberDirection(size_t particle_index_i) { return f0f0_; }; - /** compute the stress through Constitutive relation. */ - virtual Matd ConstitutiveRelation(Matd& deformation, size_t particle_index_i) override; - - virtual Muscle* ThisObjectPtr() override { return this; }; - protected: - Vecd f0_, s0_; /**< Reference fiber and sheet directions as basic parameter. */ - Matd f0f0_, s0s0_, f0s0_; /**< Tensor products of fiber and sheet directions as basic parameter.. */ - Real a0_[4], b0_[4]; /**< constitutive parameters as basic parameter.*/ - Real bulk_modulus_; /**< to achieve weakly compressible condition as basic parameter.*/ - Real lambda0_; /*< first Lame parameter */ - - virtual void setReferenceSoundSpeed() override; - virtual void setTensileWaveSpeed() override; - virtual void setShearWaveSpeed() override; - virtual void setYoungsModulus() override; - virtual void setShearModulus() override; - virtual void setBulkModulus() override { K0_ = bulk_modulus_; }; - virtual void setPoissonRatio() override; - virtual void assignDerivedMaterialParameters() override; - private: - Real getPoissonRatio(); - Real getShearModulus(); - Real getYoungsModulus(); - Real getLambda(); - }; - - /** - * @class LocallyOrthotropicMuscle - * @brief muscle model is a anisotropic material in which - * there are local fiber direction and cross-fiber sheet direction. - * the model here is from - * Holzapfel and Ogden, 2009, Phil. Trans. R. Soc. 367:3445-3475 - * we consider a neo-hookean model for the background isotropic contribution. - */ - class LocallyOrthotropicMuscle : public Muscle - { - protected: - StdLargeVec local_f0f0_, local_s0s0_, local_f0s0_; /**< Sheet direction. */ - virtual void assignDerivedMaterialParameters() override - { - Muscle::assignDerivedMaterialParameters(); - }; - /** initialize the local properties, fiber and sheet direction. */ - void initializeFiberAndSheet(); - public: - StdLargeVec local_f0_; /**< local fiber direction. */ - StdLargeVec local_s0_; /**< local sheet direction. */ - - LocallyOrthotropicMuscle() : Muscle() - { - material_name_ = "LocallyOrthotropicMuscle"; - parameters_name_ = "LocalFiberAndSheet"; - }; - virtual ~LocallyOrthotropicMuscle() {}; - - virtual void assignElasticSolidParticles(ElasticSolidParticles* elastic_particles) override; - virtual Matd MuscleFiberDirection(size_t particle_index_i) override { return local_f0f0_[particle_index_i]; }; - /** Compute the stress through Constitutive relation. */ - virtual Matd ConstitutiveRelation(Matd& deformation, size_t particle_index_i) override; - virtual void readFromXmlForLocalParameters(std::string &filefullpath) override; - }; -} -#endif //ELASTIC_SOLID_H diff --git a/SPHINXsys/src/shared/materials/inelastic_solid.cpp b/SPHINXsys/src/shared/materials/inelastic_solid.cpp deleted file mode 100644 index d6cf4abb23..0000000000 --- a/SPHINXsys/src/shared/materials/inelastic_solid.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @file elastic_solid.cpp - * @author Chi Zhang and Xiangyu Hu - */ - -#include "inelastic_solid.h" - -namespace SPH { - //=================================================================================================// - void HardeningPlasticSolid::initializePlasticParameters() - { - base_particles_->registerAVariable(inverse_plastic_strain_, "InversePlasticRightCauchyStrain", Matd(1.0)); - base_particles_->registerAVariable(hardening_parameter_, "HardeningParameter"); - base_particles_->addAVariableToRestart("InversePlasticRightCauchyStrain"); - base_particles_->addAVariableToRestart("HardeningParameter"); - } - //=================================================================================================// - void HardeningPlasticSolid::assignElasticSolidParticles(ElasticSolidParticles* elastic_particles) - { - ElasticSolid::assignElasticSolidParticles(elastic_particles); - initializePlasticParameters(); - } - //=================================================================================================// - Matd HardeningPlasticSolid::PlasticConstitutiveRelation(const Matd& F, size_t index_i, Real dt) - { - Matd be = F * inverse_plastic_strain_[index_i] * (~F); - Matd normalized_be = be * pow(SimTK::det(be), -one_over_dimensions_); - Real normalized_be_isentropic = normalized_be.trace() * one_over_dimensions_; - Matd deviatoric_PK = DeviatoricKirchhoff(normalized_be - normalized_be_isentropic * Matd(1.0)); - Real deviatoric_PK_norm = deviatoric_PK.norm(); - Real trial_function = - deviatoric_PK_norm - sqrt_2_over_3_ * (hardening_modulus_ * hardening_parameter_[index_i] + yield_stress_); - if (trial_function > 0.0) - { - Real renormalized_shear_modulus = normalized_be_isentropic * G0_; - Real relax_increment = 0.5 * trial_function / (renormalized_shear_modulus + hardening_modulus_ / 3.0); - hardening_parameter_[index_i] += sqrt_2_over_3_ * relax_increment; - deviatoric_PK -= 2.0 * renormalized_shear_modulus * relax_increment * deviatoric_PK / deviatoric_PK_norm; - Matd relaxed_be = deviatoric_PK / G0_ + normalized_be_isentropic; - normalized_be = relaxed_be * pow(det(relaxed_be), -one_over_dimensions_); - } - Matd inverse_F = SimTK::inverse(F); - Matd inverse_F_T = ~inverse_F; - inverse_plastic_strain_[index_i] = inverse_F * normalized_be * inverse_F_T; - - return (deviatoric_PK + VolumetricKirchhoff(F) * Matd(1.0)) * inverse_F_T; - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/materials/inelastic_solid.h b/SPHINXsys/src/shared/materials/inelastic_solid.h deleted file mode 100644 index 3ef7658bf0..0000000000 --- a/SPHINXsys/src/shared/materials/inelastic_solid.h +++ /dev/null @@ -1,92 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file inelastic_solid.h -* @brief These are classes for define properties of elastic solid materials. -* These classes are based on isotropic linear elastic solid. -* Several more complex materials, including neo-hookean, FENE noe-hookean -* and anisotropic muscle, are derived from the basic elastic solid class. -* @author Xiangyu Hu and Chi Zhang -*/ -#pragma once - -#include "elastic_solid.h" - -namespace SPH { - - /** - * @class PlasticSolid - * @brief Abstract class for a generalized plastic solid - */ - class PlasticSolid : public LinearElasticSolid - { - protected: - Real yield_stress_; - - virtual void initializePlasticParameters() = 0; - public: - /** Constructor */ - PlasticSolid() :LinearElasticSolid() - { - material_name_ = "PlasticSolid"; - }; - virtual ~PlasticSolid() {}; - - Real YieldStress() { return yield_stress_; }; - /** compute the stress through defoemation, and plastic relaxation. */ - virtual Matd PlasticConstitutiveRelation(const Matd& deformation, size_t index_i, Real dt = 0.0) = 0; - - virtual PlasticSolid* ThisObjectPtr() override { return this; }; - }; - - /** - * @class HardeningPlasticSolid - * @brief Class for a generalized plastic solid - */ - class HardeningPlasticSolid : public PlasticSolid - { - protected: - Real hardening_modulus_; - const Real one_over_dimensions_ = 1.0 / (Real)Dimensions; - const Real sqrt_2_over_3_ = sqrt(2.0 / 3.0); - StdLargeVec inverse_plastic_strain_; /**< inverse of plastic right cauchy green strain tensor */ - StdLargeVec hardening_parameter_; /**< hardening parameter */ - - virtual void initializePlasticParameters() override; - public: - /** Constructor */ - HardeningPlasticSolid() :PlasticSolid() - { - material_name_ = "HardeningPlasticSolid"; - }; - virtual ~HardeningPlasticSolid() {}; - - Real HardeningModulus() { return hardening_modulus_; }; - /** assign particles to this material */ - virtual void assignElasticSolidParticles(ElasticSolidParticles* elastic_particles) override; - /** compute the stress through defoemation, and plastic relaxation. */ - virtual Matd PlasticConstitutiveRelation(const Matd& deformation, size_t index_i, Real dt = 0.0) override; - - virtual HardeningPlasticSolid* ThisObjectPtr() override { return this; }; - }; -} diff --git a/SPHINXsys/src/shared/materials/riemann_solver.cpp b/SPHINXsys/src/shared/materials/riemann_solver.cpp deleted file mode 100644 index f147cd5eda..0000000000 --- a/SPHINXsys/src/shared/materials/riemann_solver.cpp +++ /dev/null @@ -1,233 +0,0 @@ -/** - * @file riemann_solver.cpp - * @author Xiangyu Hu - */ - -#include "riemann_solver.h" - -#include "base_material.h" - -#include "compressible_fluid.h" - -namespace SPH { - //=================================================================================================// - Real NoRiemannSolver:: - getPStar(const FluidState& state_i, const FluidState& state_j, const Vecd& e_ij) - { - return (state_i.p_ * state_j.rho_ + state_j.p_ * state_i.rho_) - / (state_i.rho_ + state_j.rho_); - } - //=================================================================================================// - Vecd NoRiemannSolver:: - getVStar(const FluidState& state_i, const FluidState& state_j, const Vecd& e_ij) - { - return (state_i.vel_ * state_i.rho_ + state_j.vel_ * state_j.rho_) - / (state_i.rho_ + state_j.rho_); - } - //=================================================================================================// - Real AcousticRiemannSolver:: - getPStar(const FluidState& state_i, const FluidState& state_j, const Vecd& e_ij) - { - Real ul = dot(-e_ij, state_i.vel_); - Real ur = dot(-e_ij, state_j.vel_); - - Real rhol_cl = fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_) * state_i.rho_; - Real rhor_cr = fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_) * state_j.rho_; - Real clr = (rhol_cl + rhor_cr) / (state_i.rho_ + state_j.rho_); - - return (rhol_cl * state_j.p_ + rhor_cr * state_i.p_ + rhol_cl * rhor_cr * (ul - ur) - * SMIN(3.0 * SMAX((ul - ur) / clr, 0.0), 1.0)) / (rhol_cl + rhor_cr); - } - //=================================================================================================// - Vecd AcousticRiemannSolver:: - getVStar(const FluidState& state_i, const FluidState& state_j, const Vecd& e_ij) - { - Real ul = dot(-e_ij, state_i.vel_); - Real ur = dot(-e_ij, state_j.vel_); - Real rhol_cl = fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_) * state_i.rho_; - Real rhor_cr = fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_) * state_j.rho_; - Real u_star = (rhol_cl * ul + rhor_cr * ur + state_i.p_ - state_j.p_) / (rhol_cl + rhor_cr); - - return (state_i.vel_ * state_i.rho_ + state_j.vel_ * state_j.rho_) / (state_i.rho_ + state_j.rho_) - - e_ij * (u_star - (ul * state_i.rho_ + ur * state_j.rho_) / (state_i.rho_ + state_j.rho_)); - } - //=================================================================================================// - Real DissipativeRiemannSolver:: - getPStar(const FluidState& state_i, const FluidState& state_j, const Vecd& e_ij) - { - Real ul = dot(-e_ij, state_i.vel_); - Real ur = dot(-e_ij, state_j.vel_); - - Real rhol_cl = fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_) * state_i.rho_; - Real rhor_cr = fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_) * state_j.rho_; - - return (rhol_cl * state_j.p_ + rhor_cr * state_i.p_ + rhol_cl * rhor_cr * (ul - ur)) / (rhol_cl + rhor_cr); - } - //=================================================================================================// - Vecd DissipativeRiemannSolver:: - getVStar(const FluidState& state_i, const FluidState& state_j, const Vecd& e_ij) - { - Real ul = dot(-e_ij, state_i.vel_); - Real ur = dot(-e_ij, state_j.vel_); - Real rhol_cl = fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_) * state_i.rho_; - Real rhor_cr = fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_) * state_j.rho_; - Real u_star = (rhol_cl * ul + rhor_cr * ur + state_i.p_ - state_j.p_) / (rhol_cl + rhor_cr); - - return (state_i.vel_ * state_i.rho_ + state_j.vel_ * state_j.rho_) / (state_i.rho_ + state_j.rho_) - - e_ij * (u_star - (ul * state_i.rho_ + ur * state_j.rho_) / (state_i.rho_ + state_j.rho_)); - } - //=================================================================================================// - Real HLLCRiemannSolver:: - getPStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& e_ij) - { - Real ul = dot(-e_ij, state_i.vel_); - Real ur = dot(-e_ij, state_j.vel_); - Real s_l = ul - compressible_fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_); - Real s_r = ur + compressible_fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_); - Real s_star = (state_j.rho_ * ur * (s_r - ur) + state_i.rho_ * ul * (ul - s_l) + state_i.p_ - state_j.p_) / (state_j.rho_ * (s_r - ur) + state_i.rho_ * (ul - s_l)); - Real p_star = 0.0; - if (0.0 < s_l) { p_star = state_i.p_; } - if (s_l <= 0.0 && s_r >= 0.0) { p_star = state_i.p_ + state_i.rho_*(s_l - ul)*(s_star - ul); } - if (s_r < 0.0) { p_star = state_j.p_; } - - return p_star; - } - //=================================================================================================// - Vecd HLLCRiemannSolver:: - getVStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& e_ij) - { - Real ul = dot(-e_ij, state_i.vel_); - Real ur = dot(-e_ij, state_j.vel_); - Real s_l = ul - compressible_fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_); - Real s_r = ur + compressible_fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_); - Real s_star = (state_j.rho_ * ur * (s_r - ur) + state_i.rho_ * ul * (ul - s_l) + state_i.p_ - state_j.p_) / (state_j.rho_ * (s_r - ur) + state_i.rho_ * (ul - s_l)); - Vecd v_star(0); - if (0.0 < s_l) { v_star = state_i.vel_; } - if (s_l <= 0.0 && 0.0 <= s_star) { v_star = state_i.vel_ - e_ij * (s_star - ul); } - if (s_star <= 0.0 && 0.0 <= s_r) { v_star = state_j.vel_ - e_ij * (s_star - ur); } - if (s_r < 0.0) { v_star = state_j.vel_; } - - return v_star; - } - //=================================================================================================// - Real HLLCRiemannSolver:: - getRhoStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& e_ij) - { - Real ul = dot(-e_ij, state_i.vel_); - Real ur = dot(-e_ij, state_j.vel_); - Real s_l = ul - compressible_fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_); - Real s_r = ur + compressible_fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_); - Real s_star = (state_j.rho_ * ur * (s_r - ur) + state_i.rho_ * ul * (ul - s_l) + state_i.p_ - state_j.p_) / (state_j.rho_ * (s_r - ur) + state_i.rho_ * (ul - s_l)); - Real rho_star = 0.0; - if (0.0 < s_l) { rho_star = state_i.rho_; } - if (s_l <= 0.0 && 0.0 <= s_star) { rho_star = state_i.rho_ * (s_l - ul) / (s_l - s_star); } - if (s_star <= 0.0 && 0.0 <= s_r) { rho_star = state_j.rho_ * (s_r - ur) / (s_r - s_star); } - if (s_r < 0.0) { rho_star = state_j.rho_; } - - return rho_star; - } - //=================================================================================================// - Real HLLCRiemannSolver:: - getEStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& e_ij) - { - Real ul = dot(-e_ij, state_i.vel_); - Real ur = dot(-e_ij, state_j.vel_); - Real s_l = ul - compressible_fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_); - Real s_r = ur + compressible_fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_); - Real s_star = (state_j.rho_ * ur * (s_r - ur) + state_i.rho_ * ul * (ul - s_l) + state_i.p_ - state_j.p_) / (state_j.rho_ * (s_r - ur) + state_i.rho_ * (ul - s_l)); - Real energy_star = 0.0; - if (0.0 < s_l) { energy_star = state_i.E_; } - if (s_l <= 0.0 && 0.0 <= s_star) - { - energy_star = state_i.rho_ * (s_l - ul) / (s_l - s_star) * (state_i.E_ / state_i.rho_ + (s_star - ul) * (s_star + state_i.p_ / state_i.rho_ / (s_l - ul))); - } - if (s_star <= 0.0 && 0.0 <= s_r) - { - energy_star = state_j.rho_ * (s_r - ur) / (s_r - s_star) * (state_j.E_ / state_j.rho_ + (s_star - ur) * (s_star + state_j.p_ / state_j.rho_ / (s_r - ur))); - } - if (s_r < 0.0) { energy_star = state_j.E_; } - - return energy_star; - } - //=================================================================================================// - Real HLLCWithLimiterRiemannSolver:: - getPStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& e_ij) - { - Real ul = dot(-e_ij, state_i.vel_); - Real ur = dot(-e_ij, state_j.vel_); - Real s_l = ul - compressible_fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_); - Real s_r = ur + compressible_fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_); - Real s_star = (state_j.rho_ * ur * (s_r - ur) + state_i.rho_ * ul * (ul - s_l) + state_i.p_ - state_j.p_) / (state_j.rho_ * (s_r - ur) + state_i.rho_ * (ul - s_l)); - Real p_star = 0.0; - if (0.0 < s_l) { p_star = state_i.p_; } - if (s_l <= 0.0 && s_r >= 0.0) - { - Real rho_ave = 2 * state_i.rho_*state_j.rho_ / (state_i.rho_ + state_j.rho_); - Real rho_cl = state_i.rho_*compressible_fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_); - Real rho_cr = state_j.rho_*compressible_fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_); - Real rho_clr = (rho_cl*state_i.rho_ + rho_cr * state_j.rho_) / (state_i.rho_ + state_j.rho_); - p_star = 0.5*(state_i.p_ + state_j.p_) + 0.5*(SMIN(3.0 * SMAX(rho_ave*(ul - ur), 0.0), rho_clr)*(ul - ur) + s_star * (rho_cr - rho_cl)); - } - if (s_r < 0.0) { p_star = state_j.p_; } - - return p_star; - } - //=================================================================================================// - Vecd HLLCWithLimiterRiemannSolver:: - getVStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& e_ij) - { - Real ul = dot(-e_ij, state_i.vel_); - Real ur = dot(-e_ij, state_j.vel_); - Real s_l = ul - compressible_fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_); - Real s_r = ur + compressible_fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_); - Real s_star = (state_j.rho_ * ur * (s_r - ur) + state_i.rho_ * ul * (ul - s_l) + state_i.p_ - state_j.p_) / (state_j.rho_ * (s_r - ur) + state_i.rho_ * (ul - s_l)); - Vecd v_star(0); - if (0.0 < s_l) { v_star = state_i.vel_; } - if (s_l <= 0.0 && 0.0 <= s_star) { v_star = state_i.vel_ - e_ij * (s_star - ul); } - if (s_star <= 0.0 && 0.0 <= s_r) { v_star = state_j.vel_ - e_ij * (s_star - ur); } - if (s_r < 0.0) { v_star = state_j.vel_; } - - return v_star; - } - //=================================================================================================// - Real HLLCWithLimiterRiemannSolver:: - getRhoStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& e_ij) - { - Real ul = dot(-e_ij, state_i.vel_); - Real ur = dot(-e_ij, state_j.vel_); - Real s_l = ul - compressible_fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_); - Real s_r = ur + compressible_fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_); - Real s_star = (state_j.rho_ * ur * (s_r - ur) + state_i.rho_ * ul * (ul - s_l) + state_i.p_ - state_j.p_) / (state_j.rho_ * (s_r - ur) + state_i.rho_ * (ul - s_l)); - Real rho_star = 0.0; - if (0.0 < s_l) { rho_star = state_i.rho_; } - if (s_l <= 0.0 && 0.0 <= s_star) { rho_star = state_i.rho_ * (s_l - ul) / (s_l - s_star); } - if (s_star <= 0.0 && 0.0 <= s_r) { rho_star = state_j.rho_ * (s_r - ur) / (s_r - s_star); } - if (s_r < 0.0) { rho_star = state_j.rho_; } - - return rho_star; - } - //=================================================================================================// - Real HLLCWithLimiterRiemannSolver:: - getEStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& e_ij) - { - Real ul = dot(-e_ij, state_i.vel_); - Real ur = dot(-e_ij, state_j.vel_); - Real s_l = ul - compressible_fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_); - Real s_r = ur + compressible_fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_); - Real s_star = (state_j.rho_ * ur * (s_r - ur) + state_i.rho_ * ul * (ul - s_l) + state_i.p_ - state_j.p_) / (state_j.rho_ * (s_r - ur) + state_i.rho_ * (ul - s_l)); - Real energy_star = 0.0; - if (0.0 < s_l) { energy_star = state_i.E_; } - if (s_l <= 0.0 && 0.0 <= s_star) - { - energy_star = state_i.rho_ * (s_l - ul) / (s_l - s_star) * (state_i.E_ / state_i.rho_ + (s_star - ul) * (s_star + state_i.p_ / state_i.rho_ / (s_l - ul))); - } - if (s_star <= 0.0 && 0.0 <= s_r) - { - energy_star = state_j.rho_ * (s_r - ur) / (s_r - s_star) * (state_j.E_ / state_j.rho_ + (s_star - ur) * (s_star + state_j.p_ / state_j.rho_ / (s_r - ur))); - } - if (s_r < 0.0) { energy_star = state_j.E_; } - - return energy_star; - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/materials/riemann_solver.h b/SPHINXsys/src/shared/materials/riemann_solver.h deleted file mode 100644 index 8541fe7d45..0000000000 --- a/SPHINXsys/src/shared/materials/riemann_solver.h +++ /dev/null @@ -1,108 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file riemann_solvers.h - * @brief This is the collection of Riemann solvers. - * @author Xiangyu Hu - */ - - -#ifndef RIEMANN_SOLVER_H -#define RIEMANN_SOLVER_H - -#include "base_data_package.h" - -namespace SPH -{ - struct FluidState - { - Vecd vel_; - Real rho_, p_; - FluidState(Real& rho, Vecd& vel, Real& p) : - vel_(vel), rho_(rho), p_(p) {}; - }; - - struct CompressibleFluidState - { - Vecd vel_; - Real rho_, p_, E_; - CompressibleFluidState(Real& rho, Vecd& vel, Real& p, Real& E) : - vel_(vel), rho_(rho), p_(p), E_(E) {}; - }; - - class Fluid; - class CompressibleFluid; - - class NoRiemannSolver - { - Fluid& fluid_l_, & fluid_r_; - public: - NoRiemannSolver(Fluid& fluid_i, Fluid& fluid_j) : fluid_l_(fluid_i), fluid_r_(fluid_j) {}; - Real getPStar(const FluidState& state_i, const FluidState& state_j, const Vecd& direction_to_i); - Vecd getVStar(const FluidState& state_i, const FluidState& state_j, const Vecd& direction_to_i); - }; - - class AcousticRiemannSolver - { - Fluid& fluid_i_, & fluid_j_; - public: - AcousticRiemannSolver(Fluid& fluid_i, Fluid& fluid_j) : fluid_i_(fluid_i), fluid_j_(fluid_j) {}; - Real getPStar(const FluidState& state_i, const FluidState& state_j, const Vecd& direction_to_i); - Vecd getVStar(const FluidState& state_i, const FluidState& state_j, const Vecd& direction_to_i); - }; - - class DissipativeRiemannSolver - { - Fluid& fluid_i_, & fluid_j_; - public: - DissipativeRiemannSolver(Fluid& fluid_i, Fluid& fluid_j) : fluid_i_(fluid_i), fluid_j_(fluid_j) {}; - Real getPStar(const FluidState& state_i, const FluidState& state_j, const Vecd& direction_to_i); - Vecd getVStar(const FluidState& state_i, const FluidState& state_j, const Vecd& direction_to_i); - }; - - class HLLCRiemannSolver - { - CompressibleFluid& compressible_fluid_i_, &compressible_fluid_j_; - public: - HLLCRiemannSolver(CompressibleFluid& compressible_fluid_i, CompressibleFluid& compressible_fluid_j) : - compressible_fluid_i_(compressible_fluid_i), compressible_fluid_j_(compressible_fluid_j) {}; - Real getPStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& direction_to_i); - Vecd getVStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& direction_to_i); - Real getRhoStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& direction_to_i); - Real getEStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& direction_to_i); - }; - - class HLLCWithLimiterRiemannSolver - { - CompressibleFluid& compressible_fluid_i_, &compressible_fluid_j_; - public: - HLLCWithLimiterRiemannSolver(CompressibleFluid& compressible_fluid_i, CompressibleFluid& compressible_fluid_j) : - compressible_fluid_i_(compressible_fluid_i), compressible_fluid_j_(compressible_fluid_j) {}; - Real getPStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& direction_to_i); - Vecd getVStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& direction_to_i); - Real getRhoStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& direction_to_i); - Real getEStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& direction_to_i); - }; -} - -#endif //RIEMANN_SOLVER_H diff --git a/SPHINXsys/src/shared/materials/weakly_compressible_fluid.cpp b/SPHINXsys/src/shared/materials/weakly_compressible_fluid.cpp deleted file mode 100644 index d302c8c1a8..0000000000 --- a/SPHINXsys/src/shared/materials/weakly_compressible_fluid.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @file weakly_compressible_fluid.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#include "weakly_compressible_fluid.h" - -namespace SPH { - //===============================================================// - Real WeaklyCompressibleFluid::getPressure(Real rho) - { - return p0_ * (rho / rho0_ - 1.0); - } - //===============================================================// - Real WeaklyCompressibleFluid::DensityFromPressure(Real p) - { - return rho0_ * (p / p0_ + 1.0); - } - //===============================================================// - Real WeaklyCompressibleFluid::getSoundSpeed(Real p, Real rho) - { - return c0_; - } - //===============================================================// - Real SymmetricTaitFluid::getPressure(Real rho) - { - Real rho_ratio = rho / rho0_; - return rho_ratio > 1.0 - ? p0_ * (powerN(rho_ratio, gamma_) - 1.0) / Real(gamma_) - : -p0_ * (powerN(1.0 / rho_ratio, gamma_) - 1.0) / Real(gamma_); - } - //===============================================================// - Real SymmetricTaitFluid::DensityFromPressure(Real p) - { - return p > 0.0 - ? rho0_ * pow(1.0 + Real(gamma_) * p / p0_, 1.0 / Real(gamma_)) - : rho0_ / pow(1.0 - Real(gamma_) * p / p0_, 1.0 / Real(gamma_)); - } - //===============================================================// - Real SymmetricTaitFluid::getSoundSpeed(Real p, Real rho) - { - Real rho_ratio = rho / rho0_; - return rho_ratio > 1.0 - ? sqrt((p0_ + Real(gamma_) * p) / rho) - : sqrt((p0_ - Real(gamma_) * p) / rho); - } - //===============================================================// -} diff --git a/SPHINXsys/src/shared/materials/weakly_compressible_fluid.h b/SPHINXsys/src/shared/materials/weakly_compressible_fluid.h deleted file mode 100644 index 0d6e346951..0000000000 --- a/SPHINXsys/src/shared/materials/weakly_compressible_fluid.h +++ /dev/null @@ -1,158 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file weakly_compressible_fluid.h - * @brief Describe the weakly compressible fluid which is used - * model incompressible fluids. Here, we have included several equation of states. - * Futhermore, A typical non-newtonian fluid model is included. - * @author Xiangyu Hu, Luhui Han and Chi Zhang - */ - - -#ifndef WEAKLY_COMPRESSIBLE_FLUID_H -#define WEAKLY_COMPRESSIBLE_FLUID_H - - - -#include "base_material.h" - -namespace SPH { - - class ViscoelasticFluidParticles; - - /** - * @class WeaklyCompressibleFluid - * @brief Linear equation of state (EOS). - */ - class WeaklyCompressibleFluid : public Fluid - { - protected: - Real p0_; /**< reference pressure */ - - virtual void assignDerivedMaterialParameters() override - { - Fluid::assignDerivedMaterialParameters(); - p0_ = rho0_ * c0_ * c0_; - }; - public: - explicit WeaklyCompressibleFluid() : Fluid(), p0_(1.0) { - material_name_ = "WeaklyCompressibleFluid"; - }; - virtual ~WeaklyCompressibleFluid() {}; - - virtual Real getPressure(Real rho) override; - virtual Real DensityFromPressure(Real p) override; - virtual Real getSoundSpeed(Real p = 0.0, Real rho = 1.0) override; - virtual WeaklyCompressibleFluid* ThisObjectPtr() override {return this;}; - }; - - /** - * @class WeaklyCompressibleFluidFreeSurface - * @brief Equation of state (EOS) with cut-off pressure. - */ - template - class WeaklyCompressibleFluidFreeSurface : public WeaklyCompressibleFluid - { - protected: - WeaklyCompressibleFluidType* fluid_; - Real cutoff_pressure_, cutoff_density_; - - virtual void assignDerivedMaterialParameters() { - WeaklyCompressibleFluid::assignDerivedMaterialParameters(); - }; - public: - WeaklyCompressibleFluidFreeSurface(Real cutoff_pressure) - : WeaklyCompressibleFluid(), - cutoff_pressure_(cutoff_pressure) { - fluid_ = new WeaklyCompressibleFluidType(); - material_name_ = fluid_->material_name_ + "FreeSurface"; - cutoff_density_ = fluid_->DensityFromPressure(cutoff_pressure); - }; - virtual ~WeaklyCompressibleFluidFreeSurface() {}; - - virtual Real getPressure(Real rho) override { - return rho < cutoff_density_ ? cutoff_pressure_ : fluid_->getPressure(rho); - }; - }; - - /** - * @class SymmetricTaitFluid - * @brief Tait EOS for positive and negative pressure symmetrically. - */ - class SymmetricTaitFluid : public WeaklyCompressibleFluid - { - protected: - - int gamma_; /**< determine the stiffness of the fluid */ - - /** assign derived material properties*/ - virtual void assignDerivedMaterialParameters() override - { - WeaklyCompressibleFluid::assignDerivedMaterialParameters(); - }; - - public: - SymmetricTaitFluid() : WeaklyCompressibleFluid(), gamma_(2) - { - material_name_ = "SymmetricTaitFluid"; - }; - virtual ~SymmetricTaitFluid() {}; - - virtual Real getPressure(Real rho) override; - virtual Real DensityFromPressure(Real p) override; - virtual Real getSoundSpeed(Real p = 0.0, Real rho = 1.0) override; - }; - - /** - * @class Oldroyd_B_Fluid - * @brief linear EOS with relaxation time and polymetric viscosity. - */ - class Oldroyd_B_Fluid : public WeaklyCompressibleFluid - { - protected: - Real lambda_; /**< relaxation time */ - Real mu_p_; /**< polymeric viscosity */ - ViscoelasticFluidParticles* viscoelastic_fluid_particles_; - - virtual void assignDerivedMaterialParameters() override - { - WeaklyCompressibleFluid::assignDerivedMaterialParameters(); - }; - public: - explicit Oldroyd_B_Fluid() : WeaklyCompressibleFluid(), - lambda_(1.0), mu_p_(0.0) - { - material_name_ = "Oldroyd_B_Fluid"; - }; - virtual ~Oldroyd_B_Fluid() {}; - - void assignViscoelasticFluidParticles(ViscoelasticFluidParticles* viscoelastic_fluid_particles) - { - viscoelastic_fluid_particles_ = viscoelastic_fluid_particles; - }; - Real getReferenceRelaxationTime() { return lambda_; }; - Real ReferencePolymericViscosity() { return mu_p_; }; - virtual Oldroyd_B_Fluid* ThisObjectPtr() override {return this;}; - }; -} -#endif //WEAKLY_COMPRESSIBLE_FLUID_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/meshes/CMakeLists.txt b/SPHINXsys/src/shared/meshes/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/meshes/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/meshes/base_mesh.cpp b/SPHINXsys/src/shared/meshes/base_mesh.cpp deleted file mode 100644 index fe9b3c0159..0000000000 --- a/SPHINXsys/src/shared/meshes/base_mesh.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/** - * @file base_mesh.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#include "base_mesh.h" - -namespace SPH { - //=================================================================================================// - BaseMesh::BaseMesh() : mesh_lower_bound_(0), grid_spacing_(1.0), - number_of_grid_points_(0) {}; - //=================================================================================================// - BaseMesh::BaseMesh(Vecu number_of_grid_points) : - mesh_lower_bound_(0), grid_spacing_(1.0), - number_of_grid_points_(number_of_grid_points) {}; - //=================================================================================================// - Vecu BaseMesh::CellIndexFromPosition(const Vecd& position) - { - Vecd rltpos = position - mesh_lower_bound_; - Vecu cell_index(0); - for (int n = 0; n < rltpos.size(); n++) - { - cell_index[n] = clamp((int)floor(rltpos[n] / grid_spacing_), - 0, int(number_of_grid_points_[n]) - 2); - } - return cell_index; - } - //=================================================================================================// - Vecd BaseMesh::GridPositionFromIndex(Vecu grid_index) - { - Vecd grid_position; - for (int n = 0; n < grid_position.size(); n++) - { - grid_position[n] = mesh_lower_bound_[n] - + Real(grid_index[n]) * grid_spacing_; - } - return grid_position; - } - //=================================================================================================// - size_t BaseMesh::MortonCode(const size_t& i) - { - size_t x = i; - x &= 0x3ff; - x = (x | x << 16) & 0x30000ff; - x = (x | x << 8) & 0x300f00f; - x = (x | x << 4) & 0x30c30c3; - x = (x | x << 2) & 0x9249249; - return x; - } - //=================================================================================================// - Mesh::Mesh(BoundingBox tentative_bounds, Real grid_spacing, size_t buffer_width) : - BaseMesh(), name_("Mesh"), buffer_width_(buffer_width), number_of_cells_(0) - { - initializeWithBoundingBox(tentative_bounds, grid_spacing, buffer_width); - } - //=================================================================================================// - Mesh::Mesh(Vecd mesh_lower_bound, Vecu number_of_cells, Real grid_spacing) : - BaseMesh(), name_("Mesh"), buffer_width_(0), number_of_cells_(number_of_cells) - { - mesh_lower_bound_ = mesh_lower_bound; - grid_spacing_ = grid_spacing; - number_of_grid_points_ = NumberOfGridPointsFromNumberOfCells(number_of_cells_); - } - //=================================================================================================// - void Mesh:: - initializeWithBoundingBox(BoundingBox tentative_bounds, Real grid_spacing, size_t buffer_width) - { - grid_spacing_ = grid_spacing; - Vecd mesh_buffer = Vecd(Real(buffer_width) * grid_spacing); - mesh_lower_bound_ = tentative_bounds.first - mesh_buffer; - Vecd tentative_upper_bound = tentative_bounds.second + mesh_buffer; - - for (int i = 0; i != Dimensions; ++i) { - number_of_cells_[i] = - static_cast(ceil((tentative_upper_bound[i] - mesh_lower_bound_[i]) / grid_spacing)); - } - number_of_grid_points_ = number_of_cells_ + Vecu(1); - } - //=================================================================================================// - void Mesh::copyMeshProperties(Mesh* another_mesh) - { - mesh_lower_bound_ = another_mesh->mesh_lower_bound_; - grid_spacing_ = another_mesh->grid_spacing_; - number_of_grid_points_ = another_mesh->number_of_grid_points_; - number_of_cells_ = another_mesh->number_of_cells_; - buffer_width_ = another_mesh->buffer_width_; - } - //=================================================================================================// - Vecd Mesh::CellPositionFromIndex(Vecu cell_index) - { - Vecd cell_position; - for (int n = 0; n < cell_position.size(); n++) - { - cell_position[n] = mesh_lower_bound_[n] + (Real(cell_index[n]) + 0.5)* grid_spacing_; - } - return cell_position; - } - //=================================================================================================// - bool Mesh::isWithinMeshBound(Vecd position) - { - bool is_bounded = true; - Vecu cell_pos = CellIndexFromPosition(position); - for (int i = 0; i != position.size(); ++i) { - if (cell_pos[i] < 2) is_bounded = false; - if (cell_pos[i] > (number_of_cells_[i] - 2)) is_bounded = false; - } - return is_bounded; - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/meshes/base_mesh.h b/SPHINXsys/src/shared/meshes/base_mesh.h deleted file mode 100644 index 06fc818c2e..0000000000 --- a/SPHINXsys/src/shared/meshes/base_mesh.h +++ /dev/null @@ -1,183 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file base_mesh.h -* @brief This is the base classes of mesh, which describe ordered and indexed -* data sets. Depending on application, there are different data -* saved on the mesh. The intersection points of mesh lines are called -* grid points, the element enclosed by mesh lines (2D) or faces (3D) called -* cells. The mesh line or face are also called cell faces. Grid points are -* also called cell corners. -* @author Chi ZHang and Xiangyu Hu -*/ - - -#ifndef BASE_MESH_H -#define BASE_MESH_H - - - -#include "base_data_package.h" -#include "sph_data_conainers.h" -#include "my_memory_pool.h" - -#include -#include -#include -#include -using namespace std::placeholders; - -namespace SPH -{ - class Kernel; - class ParticleAdaptation; - - /** Functor for operation on the mesh. */ - typedef std::function MeshFunctor; - /** Iterator on the mesh by looping index. sequential computing. */ - void MeshIterator(Vecu index_begin, Vecu index_end, MeshFunctor& mesh_functor, Real dt = 0.0); - /** Iterator on the mesh by looping index. parallel computing. */ - void MeshIterator_parallel(Vecu index_begin, Vecu index_end, MeshFunctor& mesh_functor, Real dt = 0.0); - - /** - * @class BaseMesh - * @brief Base class for all meshes - */ - class BaseMesh - { - protected: - Vecd mesh_lower_bound_; /**< mesh lower bound as reference coordinate */ - Real grid_spacing_; /**< grid_spacing */ - Vecu number_of_grid_points_; /**< number of grid points by dimension */ - public: - BaseMesh(); - BaseMesh(Vecu number_of_grid_points); - virtual ~BaseMesh() {}; - - Vecd MeshLowerBound() { return mesh_lower_bound_; }; - Real GridSpacing() { return grid_spacing_; }; - Vecu NumberOfGridPoints() { return number_of_grid_points_; }; - Vecu NumberOfCells() { return number_of_grid_points_ - Vecu(1); }; - - Vecu CellIndexFromPosition(const Vecd& position); - Vecd GridPositionFromIndex(Vecu grid_index); - Vecu transfer1DtoMeshIndex(Vecu number_of_grid_points, size_t i); - size_t transferMeshIndexTo1D(Vecu number_of_grid_points, Vecu grid_index); - /** converts mesh index into a Morton order. - * Interleave a 10 bit number in 32 bits, fill one bit and leave the other 2 as zeros - * https://stackoverflow.com/questions/18529057/ - * produce-interleaving-bit-patterns-morton-keys-for-32-bit-64-bit-and-128bit - */ - size_t MortonCode(const size_t &i); - /** This function converts mesh index into a Morton order. */ - size_t transferMeshIndexToMortonOrder(Vecu grid_index); - }; - - /** - * @class Mesh - * @brief Abstract base class for cell-based mesh properties. - * The mesh is proposed for several functions. - * First, it is used in cell linked list for neighbor search. - * Second, it is used for background maps such as level sets. - * This class is the counterpart of the class particles. - */ - class Mesh : public BaseMesh - { - protected: - std::string name_; - size_t buffer_width_; /**< buffer width to avoid bound check.*/ - Vecu number_of_cells_; /**< number of cells by dimension */ - Vecu NumberOfGridPointsFromNumberOfCells(Vecu number_of_cells) { return number_of_cells + Vecu(1); }; - Vecu NumberOfCellsFromNumberOfGridPoints(Vecu number_of_grid_points) { return number_of_grid_points - Vecu(1); }; - void copyMeshProperties(Mesh* another_mesh); - Vecd GridPositionFromCellPosition(Vecd& cell_position) - { - return cell_position - Vecd(0.5 * grid_spacing_); - }; - void initializeWithBoundingBox(BoundingBox tentative_bounds, Real grid_spacing, size_t buffer_width); - public: - Mesh(BoundingBox tentative_bounds, Real grid_spacing, size_t buffer_width); - Mesh(Vecd mesh_lower_bound, Vecu number_of_cells, Real grid_spacing); - virtual ~Mesh() {}; - - std::string Name() { return name_; }; - Vecu NumberOfCells() { return number_of_cells_; }; - size_t MeshBufferSize() { return buffer_width_; }; - /** This function check whether a position well within in the mesh bounds */ - bool isWithinMeshBound(Vecd position); - Vecd CellPositionFromIndex(Vecu cell_index); - - /** output mesh data for Tecplot visualization */ - virtual void writeMeshToPltFile(std::ofstream& output_file) {}; - - /** allocate memories for the mesh data matrix*/ - virtual void allocateMeshDataMatrix() {}; - /** delete memories for mesh data */ - virtual void deleteMeshDataMatrix() {}; - }; - - /** - * @class MultilevelMesh - * @brief Multi level Meshes with successively double the resolutions - */ - template - class MultilevelMesh : public BaseMeshType - { - protected: - size_t total_levels_; /**< level 0 is the coarsest */ - StdVec mesh_levels_; - public: - /**template parameter pack is used with rvalue reference and perfect forwarding to keep - * the type of arguments when called by another function with template parameter pack too. */ - template - MultilevelMesh(BoundingBox tentative_bounds, Real reference_spacing, size_t total_levels, - Real maximum_spacing_ratio, Args&&... args) - : BaseMeshType(tentative_bounds, reference_spacing, std::forward(args)...), - total_levels_(total_levels) - { - Real zero_level_spacing = reference_spacing * maximum_spacing_ratio; - for (size_t level = 0; level != total_levels_; ++level) { - Real spacing_level = zero_level_spacing * powerN(0.5, (int)level); - /** all mesh levels aligned at the lower bound of tentative_bounds */ - MeshLevelType* mesh_level = - new MeshLevelType(tentative_bounds, spacing_level, std::forward(args)...); - mesh_levels_.push_back(mesh_level); - } - }; - - virtual ~MultilevelMesh() - { - for (size_t l = 0; l != total_levels_; ++l) mesh_levels_[l]->~MeshLevelType(); - }; - - StdVec getMeshLevels() { return mesh_levels_; }; - - void writeMeshToPltFile(std::ofstream& output_file) override - { - for (size_t l = 0; l != total_levels_; ++l) { - mesh_levels_[l]->writeMeshToPltFile(output_file); - } - } - }; -} -#endif //BASE_MESH_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/meshes/mesh_cell_linked_list.cpp b/SPHINXsys/src/shared/meshes/mesh_cell_linked_list.cpp deleted file mode 100644 index 2c1ec771e4..0000000000 --- a/SPHINXsys/src/shared/meshes/mesh_cell_linked_list.cpp +++ /dev/null @@ -1,139 +0,0 @@ -/** - * @file mesh_cell_linked_list.cpp - * @author Yongchuan Yu, Chi ZHang and Xiangyu Hu - */ - -#include "mesh_cell_linked_list.h" -#include "base_kernel.h" -#include "base_body.h" -#include "particle_adaptation.h" -#include "base_particles.h" - - -namespace SPH { - //=================================================================================================// - BaseMeshCellLinkedList:: - BaseMeshCellLinkedList(BoundingBox tentative_bounds, Real grid_spacing, - SPHBody& sph_body, ParticleAdaptation& particle_adaptation) - : Mesh(tentative_bounds, grid_spacing, 2), - sph_body_(sph_body), kernel_(*particle_adaptation.getKernel()), base_particles_(nullptr) {} - //=================================================================================================// - void BaseMeshCellLinkedList::clearSplitCellLists(SplitCellLists& split_cell_lists) - { - for (size_t i = 0; i < split_cell_lists.size(); i++) - split_cell_lists[i].clear(); - } - //=================================================================================================// - MeshCellLinkedList::MeshCellLinkedList(BoundingBox tentative_bounds, Real grid_spacing, - SPHBody& sph_body, ParticleAdaptation& particle_adaptation) - : BaseMeshCellLinkedList(tentative_bounds, grid_spacing, sph_body, particle_adaptation) - { - name_ = "MeshCellLinkedList"; - allocateMeshDataMatrix(); - } - //=================================================================================================// - void MeshCellLinkedList::UpdateCellLists() - { - clearCellLists(); - StdLargeVec& pos_n = base_particles_->pos_n_; - size_t total_real_particles = base_particles_->total_real_particles_; - parallel_for(blocked_range(0, total_real_particles), - [&](const blocked_range& r) { - for (size_t i = r.begin(); i != r.end(); ++i) { - insertACellLinkedParticleIndex(i, pos_n[i]); - } - }, ap); - UpdateCellListData(); - updateSplitCellLists(sph_body_.split_cell_lists_); - } - //=================================================================================================// - void MeshCellLinkedList::assignBaseParticles(BaseParticles* base_particles) - { - base_particles_ = base_particles; - }; - //=================================================================================================// - void MeshCellLinkedList::computingSequence(StdLargeVec& sequence) - { - StdLargeVec& positions = base_particles_->pos_n_; - size_t total_real_particles = base_particles_->total_real_particles_; - parallel_for(blocked_range(0, total_real_particles), - [&](const blocked_range& r) { - for (size_t i = r.begin(); i != r.end(); ++i) { - sequence[i] = transferMeshIndexToMortonOrder(CellIndexFromPosition(positions[i])); - } - }, ap); - } - //=================================================================================================// - MultilevelMeshCellLinkedList - ::MultilevelMeshCellLinkedList(BoundingBox tentative_bounds, Real reference_grid_spacing, - size_t total_levels, Real maximum_spacing_ratio, - SPHBody& sph_body, ParticleAdaptation& particle_adaptation) - : MultilevelMesh(tentative_bounds, - reference_grid_spacing, total_levels, maximum_spacing_ratio, sph_body, particle_adaptation), - h_ratio_(dynamic_cast(particle_adaptation).h_ratio_) - { - name_ = "MultilevelMeshCellLinkedList"; - } - //=================================================================================================// - size_t MultilevelMeshCellLinkedList::getMeshLevel(Real particle_cutoff_radius) - { - for (size_t level = total_levels_; level != 0; --level) - if (particle_cutoff_radius - mesh_levels_[level - 1]->GridSpacing() < Eps) return level - 1; //jump out the loop! - - std::cout << "\n Error: MeshCellLinkedList level searching out of bound!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - return 999; //means an error in level searching - }; - //=================================================================================================// - void MultilevelMeshCellLinkedList:: - insertACellLinkedParticleIndex(size_t particle_index, Vecd particle_position) - { - size_t level = getMeshLevel(kernel_.CutOffRadius(h_ratio_[particle_index])); - mesh_levels_[level]->insertACellLinkedParticleIndex(particle_index, particle_position); - } - //=================================================================================================// - void MultilevelMeshCellLinkedList:: - InsertACellLinkedListDataEntry(size_t particle_index, Vecd particle_position) - { - size_t level = getMeshLevel(kernel_.CutOffRadius(h_ratio_[particle_index])); - mesh_levels_[level]->InsertACellLinkedListDataEntry(particle_index, particle_position); - } - //=================================================================================================// - void MultilevelMeshCellLinkedList::assignBaseParticles(BaseParticles* base_particles) - { - base_particles_ = base_particles; - for (size_t l = 0; l != total_levels_; ++l) { - mesh_levels_[l]->assignBaseParticles(base_particles); - } - }; - //=================================================================================================// - void MultilevelMeshCellLinkedList::UpdateCellLists() - { - for (size_t level = 0; level != total_levels_; ++level) - mesh_levels_[level]->clearCellLists(); - - StdLargeVec& pos_n = base_particles_->pos_n_; - size_t total_real_particles = base_particles_->total_real_particles_; - //rebuild the corresponding particle list. - parallel_for(blocked_range(0, total_real_particles), - [&](const blocked_range& r) { - for (size_t i = r.begin(); i != r.end(); ++i) { - insertACellLinkedParticleIndex(i, pos_n[i]); - } - }, ap); - - for (size_t level = 0; level != total_levels_; ++level) - mesh_levels_[level]->UpdateCellListData(); - updateSplitCellLists(sph_body_.split_cell_lists_); - } - //=================================================================================================// - void MultilevelMeshCellLinkedList:: - tagBodyPartByCell(CellLists& cell_lists, std::function& check_included) - { - for (size_t l = 0; l != total_levels_; ++l) { - mesh_levels_[l]->tagBodyPartByCell(cell_lists, check_included); - } - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/meshes/mesh_cell_linked_list.h b/SPHINXsys/src/shared/meshes/mesh_cell_linked_list.h deleted file mode 100644 index cee1fca036..0000000000 --- a/SPHINXsys/src/shared/meshes/mesh_cell_linked_list.h +++ /dev/null @@ -1,187 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ - -/** -* @file mesh_cell_linked_list.h -* @brief Here gives the classes for managing cell linked lists. This is the basic class -* for building the particle configurations. -* @details The cell linked list saves for each body a list of particles -* located within the cell. -* @author Yongchuan Yu, Chi ZHang and Xiangyu Hu -*/ - - -#ifndef MESH_CELL_LINKED_LIST_H -#define MESH_CELL_LINKED_LIST_H - - - -#include "base_mesh.h" -#include "neighbor_relation.h" - -namespace SPH { - - class SPHSystem; - class SPHBody; - class BaseParticles; - class Kernel; - - /** - * @class CellList - * @brief The linked list for one cell - */ - class CellList - { - public: - /** using concurrent vectors due to writting conflicts when building the list */ - ConcurrentIndexVector concurrent_particle_indexes_; - /** non-concurrent cell linked list rewritten for building neighbor list */ - ListDataVector cell_list_data_; - /** the index vector for real particles. */ - IndexVector real_particle_indexes_; - - CellList(); - ~CellList() {}; - }; - - /** - * @class BaseMeshCellLinkedList - * @brief Abstract class for mesh cell linked list. - */ - class BaseMeshCellLinkedList : public Mesh - { - protected: - SPHBody& sph_body_; - Kernel& kernel_; - BaseParticles* base_particles_; - - /** clear split cell lists in this mesh*/ - virtual void clearSplitCellLists(SplitCellLists& split_cell_lists); - /** update split particle list in this mesh */ - virtual void updateSplitCellLists(SplitCellLists& split_cell_lists) = 0; - public: - /** The buffer size 2 used to expand computational domian for particle searching. */ - BaseMeshCellLinkedList(BoundingBox tentative_bounds, Real grid_spacing, - SPHBody& sph_body, ParticleAdaptation& particle_adaptation); - virtual ~BaseMeshCellLinkedList() {}; - - /** Assign base particles to the mesh cell linked list, - * and is important because particles are not defined in the constructor. */ - virtual void assignBaseParticles(BaseParticles* base_particles) = 0; - - /** update the cell lists */ - virtual void UpdateCellLists() = 0; - /** Insert a cell-linked_list entry to the concurrent index list. */ - virtual void insertACellLinkedParticleIndex(size_t particle_index, Vecd particle_position) = 0; - /** Insert a cell-linked_list entry of the index and particle position pair. */ - virtual void InsertACellLinkedListDataEntry(size_t particle_index, Vecd particle_position) = 0; - /** find the nearest list data entry */ - virtual ListData findNearestListDataEntry(Vecd& position) = 0; - /** computing the sequence which indicate the order of sorted particle data */ - virtual void computingSequence(StdLargeVec& sequence) = 0; - /** Tag body part by cell, call by body part */ - virtual void tagBodyPartByCell(CellLists& cell_lists, std::function& check_included) = 0; - /** Tag domain bounding cells in an axis direction, called by domain bounding classes */ - virtual void tagBodyDomainBoundingCells(StdVec& cell_lists, BoundingBox& body_domain_bounds, int axis) = 0; - /** Tag mirror bounding cells, called by mirror boundary condition */ - virtual void tagMirrorBoundingCells(CellLists& cell_lists, BoundingBox& body_domain_bounds, int axis, bool positive) = 0; - }; - - /** - * @class MeshCellLinkedList - * @brief Defining a mesh cell linked list for a body. - * The meshes for all bodies share the same global coordinates. - */ - class MeshCellLinkedList : public BaseMeshCellLinkedList - { - protected: - /** The array for of mesh cells, i.e. mesh data. - * Within each cell, a list is saved with the indexes of particles.*/ - MeshDataMatrix cell_linked_lists_; - - virtual void updateSplitCellLists(SplitCellLists& split_cell_lists) override; - public: - MeshCellLinkedList(BoundingBox tentative_bounds, Real grid_spacing, - SPHBody& sph_body, ParticleAdaptation& particle_adaptation); - virtual ~MeshCellLinkedList() { deleteMeshDataMatrix(); }; - - virtual void allocateMeshDataMatrix() override; - virtual void deleteMeshDataMatrix() override; - virtual void assignBaseParticles(BaseParticles* base_particles) override; - - void clearCellLists(); - void UpdateCellListData(); - virtual void UpdateCellLists() override; - void insertACellLinkedParticleIndex(size_t particle_index, Vecd particle_position) override; - void InsertACellLinkedListDataEntry(size_t particle_index, Vecd particle_position) override; - virtual ListData findNearestListDataEntry(Vecd& position) override; - virtual void computingSequence(StdLargeVec& sequence) override; - virtual void tagBodyPartByCell(CellLists& cell_lists, std::function& check_included) override; - virtual void tagBodyDomainBoundingCells(StdVec& cell_lists, BoundingBox& body_domain_bounds, int axis) override; - virtual void tagMirrorBoundingCells(CellLists& cell_lists, BoundingBox& body_domain_bounds, int axis, bool positive) override; - virtual void writeMeshToPltFile(std::ofstream& output_file) override; - - /** generalized particle search algorithm */ - template - void searchNeighborsByParticles(size_t total_real_particles, BaseParticles& source_particles, - ParticleConfiguration& particle_configuration, GetParticleIndex& get_particle_index, - GetSearchRange& get_search_range, GetNeighborRelation& get_neighbor_relation); - - /** generalized particle search algorithm for searching body part */ - template - void searchNeighborPartsByParticles(size_t total_real_particles, BaseParticles& source_particles, - ParticleConfiguration& particle_configuration, GetParticleIndex& get_particle_index, - GetSearchRange& get_search_range, GetNeighborRelation& get_neighbor_relation, PartParticleCheck& part_check); - }; - - /** - * @class MultilevelMeshCellLinkedList - * @brief Defining a multilevel mesh cell linked list for a body - * for multiresolution particle configuration. - */ - class MultilevelMeshCellLinkedList : - public MultilevelMesh - { - protected: - StdLargeVec& h_ratio_; - virtual void updateSplitCellLists(SplitCellLists& split_cell_lists) override {}; - /** determine mesh level from particle cutoff radius */ - inline size_t getMeshLevel(Real particle_cutoff_radius); - public: - MultilevelMeshCellLinkedList(BoundingBox tentative_bounds, Real reference_grid_spacing, - size_t total_levels, Real maximum_spacing_ratio, - SPHBody& sph_body, ParticleAdaptation& particle_adaptation); - virtual ~MultilevelMeshCellLinkedList() {}; - - virtual void assignBaseParticles(BaseParticles* base_particles) override; - virtual void UpdateCellLists() override; - void insertACellLinkedParticleIndex(size_t particle_index, Vecd particle_position) override; - void InsertACellLinkedListDataEntry(size_t particle_index, Vecd particle_position) override; - virtual ListData findNearestListDataEntry(Vecd& position) override { return ListData(0, Vecd(0)); }; - virtual void computingSequence(StdLargeVec& sequence) override {}; - virtual void tagBodyPartByCell(CellLists& cell_lists, std::function& check_included) override; - virtual void tagBodyDomainBoundingCells(StdVec& cell_lists, BoundingBox& body_domain_bounds, int axis) override {}; - virtual void tagMirrorBoundingCells(CellLists& cell_lists, BoundingBox& body_domain_bounds, int axis, bool positive) override {}; - }; -} -#endif //MESH_CELL_LINKED_LIST_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/meshes/mesh_with_data_packages.h b/SPHINXsys/src/shared/meshes/mesh_with_data_packages.h deleted file mode 100644 index 4c4758f58b..0000000000 --- a/SPHINXsys/src/shared/meshes/mesh_with_data_packages.h +++ /dev/null @@ -1,258 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file base_mesh.h -* @brief This is the base classes of mesh, which describe ordered and indexed -* data sets. Depending on application, there are different data -* saved on the mesh. The intersection points of mesh lines are called -* grid points, the element enclosed by mesh lines (2D) or faces (3D) called -* cells. The mesh line or face are also called cell faces. Grid points are -* also called cell corners. -* @author Chi ZHang and Xiangyu Hu -*/ - - -#ifndef MESH_WITH_DATA_PACKAGES_H -#define MESH_WITH_DATA_PACKAGES_H - - - -#include "base_mesh.h" -#include "my_memory_pool.h" - -#include -#include -#include -#include -using namespace std::placeholders; - -namespace SPH -{ - class Kernel; - - /** Functor for operation on the mesh data package. */ - template - using PackageFunctor = std::function; - /** Iterator on a collection of mesh data packages. sequential computing. */ - template - void PackageIterator(ConcurrentVector data_pkgs, - PackageFunctor& pkg_functor, Real dt = 0.0) - { - for (size_t i = 0; i != data_pkgs.size(); ++i) - pkg_functor(data_pkgs[i], dt); - - }; - /** Iterator on a collection of mesh data packages. parallel computing. */ - template - void PackageIterator_parallel(ConcurrentVector data_pkgs, - PackageFunctor& pkg_functor, Real dt = 0.0) - { - parallel_for(blocked_range(0, data_pkgs.size()), - [&](const blocked_range& r) { - for (size_t i = r.begin(); i != r.end(); ++i) { - pkg_functor(data_pkgs[i], dt); - } - }, ap); - }; - /** Package iterator for reducing. sequential computing. */ - template - ReturnType ReducePackageIterator(ConcurrentVector data_pkgs, ReturnType temp, - PackageFunctor& reduce_pkg_functor, ReduceOperation& reduce_operation, Real dt = 0.0) - { - for (size_t i = 0; i < data_pkgs.size(); ++i) - { - temp = reduce_operation(temp, reduce_pkg_functor(data_pkgs[i], dt)); - } - return temp; - }; - /** Package iterator for reducing. parallel computing. */ - template - ReturnType ReducePackageIterator_parallel(ConcurrentVector data_pkgs, ReturnType temp, - PackageFunctor& reduce_pkg_functor, ReduceOperation& reduce_operation, Real dt = 0.0) { - return parallel_reduce(blocked_range(0, data_pkgs.size()), - temp, [&](const blocked_range& r, ReturnType temp0)->ReturnType - { - for (size_t i = r.begin(); i != r.end(); ++i) { - temp0 = reduce_operation(temp, reduce_pkg_functor(data_pkgs[i], dt)); - } - return temp0; - }, - [&](ReturnType x, ReturnType y)->ReturnType { - return reduce_operation(x, y); - } - ); - }; - - /** - * @class BaseDataPackage - * @brief Abstract base class for a data package - * which is given by a small mesh patch. - * note tha ADDRS_SIZE = PKG_SIZE + 2 * pkg_addrs_buffer_; - */ - template - class BaseDataPackage : public BaseMesh - { - public: - Vecd data_lower_bound_; /**< lower bound coordinate for the data as reference */ - Vecu pkg_index_; /**< index of the inner packages in the mesh, 0 for far-field packages. */ - bool is_inner_pkg_; /**< If true, its data saved in memory pool. */ - /** define package data type */ - template - using PackageData = PackageDataMatrix; - /** define package data address type */ - template - using PackageDataAddress = PackageDataMatrix; - /** define matrix data for temporary usage*/ - template - using PackageTemporaryData = PackageDataMatrix; - - BaseDataPackage() : BaseMesh(Vecu(ADDRS_SIZE)), - data_lower_bound_(0), pkg_index_(0), is_inner_pkg_(false) {}; - virtual ~BaseDataPackage() {}; - - constexpr int PackageSize() { return PKG_SIZE; }; - constexpr int AddressSize() { return ADDRS_SIZE; }; - constexpr int AddressBufferWidth() { return (ADDRS_SIZE - PKG_SIZE) / 2; }; - constexpr int OperationUpperBound() { return PKG_SIZE + AddressBufferWidth(); }; - /** initialize package mesh geometric information. */ - void initializePackageGeometry(Vecd& pkg_lower_bound, Real data_spacing) { - mesh_lower_bound_ = pkg_lower_bound - Vecd(data_spacing * 0.5);; - grid_spacing_ = data_spacing; - data_lower_bound_ = pkg_lower_bound + Vecd(data_spacing * 0.5); - }; - /** This function probes by applying Bi and tri-linear interpolation within the package. */ - template - DataType probeDataPackage(PackageDataAddress& pkg_data_addrs, const Vecd& position); - /** This function compute gradient transform within data package */ - template - void computeGradient(PackageDataAddress& in_pkg_data_addrs, - PackageDataAddress out_pkg_data_addrs, Real dt = 0.0); - /** This function compute normalized gradient transform within data package */ - template - void computeNormalizedGradient(PackageDataAddress& in_pkg_data_addrs, - PackageDataAddress out_pkg_data_addrs, Real dt = 0.0); - - protected: - /** initialize package data address within a derived class constructor */ - template - void initializePackageDataAddress(PackageData& pkg_data, - PackageDataAddress& pkg_data_addrs); - /** assign address for a package data when the package is an inner one */ - template - void assignPackageDataAddress(PackageDataAddress& pkg_data_addrs, Vecu& addrs_index, - PackageData& pkg_data, Vecu& data_index); - template - /** obtain averaged value at a corner of a data cell */ - DataType CornerAverage(PackageDataAddress& pkg_data_addrs, Veci addrs_index, Veci corner_direction); - }; - - /** - * @class MeshWithDataPackages - * @brief Abstract class fpr mesh with data packages - */ - template - class MeshWithDataPackages : public BaseMeshType - { - public: - MyMemoryPool data_pkg_pool_; /**< memory pool for all packages in the mesh. */ - MeshDataMatrix data_pkg_addrs_; /**< Address of data packages. */ - ConcurrentVector inner_data_pkgs_; /**< Inner data packages which is able to carry out spatial operations. */ - - virtual void allocateMeshDataMatrix() override; /**< allocate memories for addresses of data packages. */ - virtual void deleteMeshDataMatrix() override; /**< delete memories for addresses of data packages. */ - - template - explicit MeshWithDataPackages(BoundingBox tentative_bounds, Real data_spacing, Args&&... args) - : BaseMeshType(tentative_bounds, data_spacing, std::forward(args)...), - data_spacing_(data_spacing), - pkg_size_((int)DataPackageType().PackageSize()), - pkg_addrs_buffer_((int)DataPackageType().AddressBufferWidth()), - pkg_operations_(pkg_size_ + pkg_addrs_buffer_), - pkg_addrs_size_(pkg_size_ + 2 * pkg_addrs_buffer_), - total_data_points_(this->number_of_cells_ * pkg_size_) - { - allocateMeshDataMatrix(); - }; - virtual ~MeshWithDataPackages() { deleteMeshDataMatrix(); }; - - /** This function probe a mesh value */ - template - DataType probeMesh(const Vecd& position); - protected: - Real data_spacing_; /**< spacing of data in the data packages*/ - int pkg_size_; /**< the size of the data package matrix*/ - int pkg_addrs_buffer_; /**< the size of address buffer, a value less than the package size. */ - int pkg_operations_; /**< the size of operation loops. */ - int pkg_addrs_size_; /**< the size of address matrix in the data packages. */ - Vecu total_data_points_; /**< total numer of data points in the packages. */ - StdVec singular_data_pkgs_addrs; /**< singular data packages. prodvied for far field condition. */ - std::mutex mutex_my_pool; /**< mutex exclusion for memory pool */ - - virtual void initializeDataInACell(Vecu cell_index, Real dt) = 0; - virtual void initializeAddressesInACell(Vecu cell_index, Real dt) = 0; - /** This function tag if a data package is inner package. */ - virtual void tagACellIsInnerPackage(Vecu cell_index, Real dt) = 0; - /** This function initialize the data packages with external information */ - virtual void initializeDataPackages() = 0; - - /*find the data index global index from its position*/ - Vecu DataGlobalIndexFromPosition(Vecd position) - { - Vecd rltpos(0); - Vecu data_global_index(0); - for (int n = 0; n < rltpos.size(); n++) - { - rltpos[n] = position[n] - this->mesh_lower_bound_[n] - 0.5 * data_spacing_; - data_global_index[n] = clamp((int)floor(rltpos[n] / data_spacing_), - 0, int(total_data_points_[n]) - 1); - } - return data_global_index; - } - - /** find the position data from its global index */ - Vecd DataPositionFromGlobalIndex(Vecu global_data_index) - { - Vecd data_position; - for (int n = 0; n < data_position.size(); n++) - { - data_position[n] = this->mesh_lower_bound_[n] + 0.5 * data_spacing_ - + Real(global_data_index[n]) * data_spacing_; - } - return data_position; - }; - /** This function find the value of data from its global index. */ - template - DataType DataValueFromGlobalIndex(Vecu global_data_index); - void initializePackageAddressesInACell(Vecu cell_index); - /** find related cell index and data index for a data package address matrix */ - std::pair CellShiftAndDataIndex(int data_addrs_index_component) - { - std::pair shift_and_index; - int signed_date_index = data_addrs_index_component - pkg_addrs_buffer_; - shift_and_index.first = (signed_date_index + pkg_size_) / pkg_size_ - 1; - shift_and_index.second = signed_date_index - shift_and_index.first * pkg_size_; - return shift_and_index; - } - }; -} -#endif //MESH_WITH_DATA_PACKAGES_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/meshes/my_memory_pool.h b/SPHINXsys/src/shared/meshes/my_memory_pool.h deleted file mode 100644 index e6d036ba28..0000000000 --- a/SPHINXsys/src/shared/meshes/my_memory_pool.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef MY_MEMORY_POOL_H -#define MY_MEMORY_POOL_H - -#define TBB_PREVIEW_MEMORY_POOL 1 - -#include "tbb/memory_pool.h" -#include "tbb/enumerable_thread_specific.h" - -#include - -using namespace tbb; -//------------------------------------------------------------------------------------------------- -//my memory pool -//------------------------------------------------------------------------------------------------- -template -class MyMemoryPool { - T sample; - tbb::memory_pool< std::allocator > my_pool; //memory pool - typedef tbb::memory_pool_allocator pool_allocator_t; //memory allocator - std::list data_list; //list of all nodes allocated - std::list free_list; //list of all free nodes - -public: - - //constructor - MyMemoryPool() : data_list((pool_allocator_t(my_pool))) {}; - //deconstructor - ~MyMemoryPool() { - //my_pool.recycle(); - }; - //prepare an avaliable node - T* malloc() - { - if (free_list.empty()) { - data_list.push_back(sample); - return (&data_list.back()); - } - else - { - T* result = free_list.front(); - free_list.pop_front(); - return result; - } - }; - //relinquish an unused node - void free(T* ptr) - { - free_list.push_back(ptr); - }; - //return the total number of nodes allocated - int capicity() - { - return data_list.size(); - }; - //return the number of current available nodes - int available_node() - { - return free_list.size(); - }; -}; - -#endif //MY_MEMORY_POOL_H diff --git a/SPHINXsys/src/shared/particle_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/active_muscle_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/active_muscle_dynamics.cpp deleted file mode 100644 index 07e14a8020..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/active_muscle_dynamics.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @file active_muscle_dynamics.cpp - * @brief In is file, we define functions decleared in active muscle dynamics.h - * @author Chi Zhang and Xiangyu Hu - */ -#include "active_muscle_dynamics.h" - -using namespace SimTK; - -namespace SPH -{ - namespace active_muscle_dynamics - { - //=================================================================================================// - MuscleActivation:: - MuscleActivation(SolidBody* body) : - ParticleDynamicsSimple(body), ActiveMuscleDataDelegateSimple(body), - pos_0_(particles_->pos_0_), active_contraction_stress_(particles_->active_contraction_stress_) {}; - //=================================================================================================// - SpringConstrainMuscleRegion:: - SpringConstrainMuscleRegion(SolidBody* body, BodyPartByParticle* body_part) : - PartSimpleDynamicsByParticle(body, body_part), - ActiveMuscleDataDelegateSimple(body), mass_(particles_->mass_), - pos_n_(particles_->pos_n_), pos_0_(particles_->pos_0_), - vel_n_(particles_->vel_n_) {} - //=================================================================================================// - Vecd SpringConstrainMuscleRegion::getAcceleration(Vecd &disp, Real mass) - { - Vecd spring_force(0); - for(int i = 0; i < disp.size(); i++) - { - spring_force[i] = -stiffness_[i] * disp[i] / mass; - } - return spring_force; - } - //=================================================================================================// - void SpringConstrainMuscleRegion::Update(size_t index_i, Real dt) - { - Vecd disp_from_0 = pos_n_[index_i] - pos_0_[index_i]; - vel_n_[index_i] += dt * getAcceleration(disp_from_0, mass_[index_i]); - pos_n_[index_i] += dt * dt * getAcceleration(disp_from_0, mass_[index_i]); - } - //=================================================================================================// - ImposingStress:: - ImposingStress(SolidBody* body, SolidBodyPartForSimbody* body_part) : - PartSimpleDynamicsByParticle(body, body_part), - ActiveMuscleDataDelegateSimple(body), - pos_0_(particles_->pos_0_), active_stress_(particles_->active_stress_) {} - //=================================================================================================// - void ImposingStress - ::Update(size_t index_i, Real dt) - { - active_stress_[index_i] = getStress(pos_0_[index_i]); - } - //=================================================================================================// - } -} diff --git a/SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/active_muscle_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/active_muscle_dynamics.h deleted file mode 100644 index 057122c01c..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/active_muscle_dynamics.h +++ /dev/null @@ -1,106 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file active_muscle_dynamics.h - * @brief In is file, we declear muscle dynamics which is driven by an external injection of energy. - * @author Chi Zhang and Xiangyu Hu - * @version 0.3.1 - * Here, we need identify the physical differences between electrophysiology and active muscle. - * The former is on the diffusion and electro-chemical reaction happens in tissue. - * The latter is for muscle dynamics which is driven by an external injection of energy. - * As the naming of class, function and variables in this code need be based on physics. - * We will identify physical differences by properly choosing names. - * Xiangyu Hu - */ - -#ifndef ACTIVE_MUSCLE_DYNAMICS_H -#define ACTIVE_MUSCLE_DYNAMICS_H - - - -#include "all_particle_dynamics.h" -#include "elastic_solid.h" -#include "base_kernel.h" -#include "solid_body.h" -#include "solid_particles.h" - -namespace SPH -{ - namespace active_muscle_dynamics - { - typedef DataDelegateSimple ActiveMuscleDataDelegateSimple; - - /** - * @class MuscleActivation - * @brief impose cases specific muscle activation - * This is a abstract class to be override for case specific activation - */ - class MuscleActivation : - public ParticleDynamicsSimple, public ActiveMuscleDataDelegateSimple - { - public: - MuscleActivation(SolidBody *body); - virtual ~MuscleActivation() {}; - protected: - StdLargeVec& pos_0_; - StdLargeVec& active_contraction_stress_; - }; - - /**@class SpringConstrainMuscleRegion - * @brief Constrain a solid body part with a spring force - * towards each constrained particles' original position. - */ - class SpringConstrainMuscleRegion : - public PartSimpleDynamicsByParticle, public ActiveMuscleDataDelegateSimple - { - public: - SpringConstrainMuscleRegion(SolidBody *body, BodyPartByParticle*body_part); - virtual ~SpringConstrainMuscleRegion() {}; - void setUpSpringStiffness(Vecd stiffness){stiffness_ = stiffness;} - protected: - StdLargeVec& mass_; - StdLargeVec& pos_n_, & pos_0_, & vel_n_; - Vecd stiffness_; - virtual Vecd getAcceleration(Vecd& disp, Real mass); - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /**@class ImposingStress - * @brief impose activation stress on a solid body part - */ - class ImposingStress : - public PartSimpleDynamicsByParticle, public ActiveMuscleDataDelegateSimple - { - public: - ImposingStress(SolidBody *body, SolidBodyPartForSimbody *body_part); - virtual ~ImposingStress() {}; - protected: - StdLargeVec& pos_0_; - StdLargeVec& active_stress_; - - virtual Matd getStress(Vecd& pos) = 0; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - } -} -#endif //ACTIVE_MUSCLE_DYNAMICS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/all_particle_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/all_particle_dynamics.h deleted file mode 100644 index f3564a5dc2..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/all_particle_dynamics.h +++ /dev/null @@ -1,9 +0,0 @@ - -#ifndef ALL_PARTICLE_DYNAMICS_H -#define ALL_PARTICLE_DYNAMICS_H - - - -#include "particle_dynamics_algorithms.h" -#include "particle_dynamics_bodypart.h" -#endif //ALL_PARTICLE_DYNAMICS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/all_physical_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/all_physical_dynamics.h deleted file mode 100644 index 11b8fd9bbb..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/all_physical_dynamics.h +++ /dev/null @@ -1,25 +0,0 @@ - -#ifndef ALL_PHYSICAL_DYNAMICS_H -#define ALL_PHYSICAL_DYNAMICS_H - - - -/** @file -This is the header file that user code should include to pick up all -particle dynamics capabilities. **/ - -#include "external_force.h" -#include "general_dynamics.h" -#include "all_fluid_dynamics.h" -#include "all_solid_dynamics.h" -#include "observer_dynamics.h" -#include "relax_dynamics.h" -#include "electro_physiology.h" -#include "active_muscle_dynamics.h" -#include "particle_dynamics_diffusion_reaction.h" -#include "particle_dynamics_diffusion_reaction.hpp" -#include "particle_dynamics_dissipation.h" -#include "particle_dynamics_dissipation.hpp" - - -#endif //ALL_PHYSICAL_DYNAMICS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.cpp deleted file mode 100644 index 0e7530ffc4..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/** - * @file base_particle_dynamics.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ -#include "base_particle_dynamics.h" -#include "base_particle_dynamics.hpp" -//=============================================================================================// -namespace SPH -{ - Real GlobalStaticVariables::physical_time_ = 0.0; - //=============================================================================================// - void ParticleIterator(size_t total_real_particles, ParticleFunctor &particle_functor, Real dt) - { - for (size_t i = 0; i < total_real_particles; ++i) - particle_functor(i, dt); - } - //=============================================================================================// - void ParticleIterator_parallel(size_t total_real_particles, ParticleFunctor &particle_functor, Real dt) - { - parallel_for(blocked_range(0, total_real_particles), - [&](const blocked_range& r) { - for (size_t i = r.begin(); i < r.end(); ++i) { - particle_functor(i, dt); - } - }, ap); - } - //=================================================================================================// - void ParticleIteratorSplittingSweep(SplitCellLists& split_cell_lists, - ParticleFunctor& particle_functor, Real dt) - { - Real dt2 = dt * 0.5; - //forward sweeping - for (size_t k = 0; k != split_cell_lists.size(); ++k) { - ConcurrentCellLists& cell_lists = split_cell_lists[k]; - for (size_t l = 0; l != cell_lists.size(); ++l) - { - IndexVector& particle_indexes - = cell_lists[l]->real_particle_indexes_; - for (size_t i = 0; i != particle_indexes.size(); ++i) - { - particle_functor(particle_indexes[i], dt2); - } - } - } - - //backward sweeping - for (size_t k = split_cell_lists.size(); k != 0; --k) { - ConcurrentCellLists& cell_lists = split_cell_lists[k - 1]; - for (size_t l = 0; l != cell_lists.size(); ++l) - { - IndexVector& particle_indexes - = cell_lists[l]->real_particle_indexes_; - for (size_t i = particle_indexes.size(); i != 0; --i) - { - particle_functor(particle_indexes[i - 1], dt2); - } - } - } - } - //=================================================================================================// - void ParticleIteratorSplittingSweep_parallel(SplitCellLists& split_cell_lists, - ParticleFunctor &particle_functor, Real dt) - { - Real dt2 = dt * 0.5; - //forward sweeping - for (size_t k = 0; k != split_cell_lists.size(); ++k) { - ConcurrentCellLists& cell_lists = split_cell_lists[k]; - parallel_for(blocked_range(0, cell_lists.size()), - [&](const blocked_range& r) { - for (size_t l = r.begin(); l < r.end(); ++l) { - IndexVector& particle_indexes - = cell_lists[l]->real_particle_indexes_; - for (size_t i = 0; i < particle_indexes.size(); ++i) - { - particle_functor(particle_indexes[i], dt2); - } - } - }, ap); - } - - //backward sweeping - for (size_t k = split_cell_lists.size(); k != 0; --k) { - ConcurrentCellLists& cell_lists = split_cell_lists[k - 1]; - parallel_for(blocked_range(0, cell_lists.size()), - [&](const blocked_range& r) { - for (size_t l = r.begin(); l < r.end(); ++l) { - IndexVector& particle_indexes - = cell_lists[l]->real_particle_indexes_; - for (size_t i = particle_indexes.size(); i != 0; --i) - { - particle_functor(particle_indexes[i - 1], dt2); - } - } - }, ap); - } - } - //=============================================================================================// -} -//=============================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.h deleted file mode 100644 index f5d7afd003..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.h +++ /dev/null @@ -1,272 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file base_particle_dynamics.h -* @brief This is for the base classes of particle dynamics, which describe the -* interaction between particles. These interactions are used to define -* differential operators for surface forces or fluxes in continuum mechanics -* @author Xiangyu Hu, Luhui Han and Chi Zhang -*/ - -#ifndef BASE_PARTICLE_DYNAMICS_H -#define BASE_PARTICLE_DYNAMICS_H - - -#include "base_data_package.h" -#include "sph_data_conainers.h" -#include "all_particles.h" -#include "all_materials.h" -#include "neighbor_relation.h" -#include "all_bodies.h" -#include "mesh_cell_linked_list.h" -#include "external_force.h" -#include "body_relation.h" -#include - -using namespace std::placeholders; - -namespace SPH -{ - /** Functor for operation on particles. */ - typedef std::function ParticleFunctor; - /** Functors for reducing operation on particles. */ - template - using ReduceFunctor = std::function; - - /** Iterators for particle functors. sequential computing. */ - void ParticleIterator(size_t total_real_particles, ParticleFunctor &particle_functor, Real dt = 0.0); - /** Iterators for particle functors. parallel computing. */ - void ParticleIterator_parallel(size_t total_real_particles, ParticleFunctor &particle_functor, Real dt = 0.0); - - /** Iterators for reduce functors. sequential computing. */ - template - ReturnType ReduceIterator(size_t total_real_particles, ReturnType temp, - ReduceFunctor &reduce_functor, ReduceOperation &reduce_operation, Real dt = 0.0); - /** Iterators for reduce functors. parallel computing. */ - template - ReturnType ReduceIterator_parallel(size_t total_real_particles, ReturnType temp, - ReduceFunctor &reduce_functor, ReduceOperation &reduce_operation, Real dt = 0.0); - - /** Iterators for particle functors with splitting. sequential computing. */ - void ParticleIteratorSplittingSweep(SplitCellLists& split_cell_lists, - ParticleFunctor& particle_functor, Real dt = 0.0); - /** Iterators for particle functors with splitting. parallel computing. */ - void ParticleIteratorSplittingSweep_parallel(SplitCellLists& split_cell_lists, - ParticleFunctor& particle_functor, Real dt = 0.0); - - - /** A Functor for Summation */ - template - struct ReduceSum { ReturnType operator () (const ReturnType& x, const ReturnType& y) const { return x + y; }; }; - /** A Functor for Maximum */ - struct ReduceMax { Real operator () (Real x, Real y) const { return SMAX(x, y); }; }; - /** A Functor for Minimum */ - struct ReduceMin { Real operator () (Real x, Real y) const { return SMIN(x, y); }; }; - /** A Functor for OR operator */ - struct ReduceOR { bool operator () (bool x, bool y) const { return x || y; }; }; - /** A Functor for lower bound */ - struct ReduceLowerBound { - Vecd operator () (const Vecd& x, const Vecd& y) const { - Vecd lower_bound; - for (int i = 0; i < lower_bound.size(); ++i) lower_bound[i] = SMIN(x[i], y[i]); - return lower_bound; - }; - }; - /** A Functor for upper bound */ - struct ReduceUpperBound { - Vecd operator () (const Vecd& x, const Vecd& y) const { - Vecd upper_bound; - for (int i = 0; i < upper_bound.size(); ++i) upper_bound[i] = SMAX(x[i], y[i]); - return upper_bound; - }; - }; - - /** - * @class GlobalStaticVariables - * @brief A place to put all global variables - */ - class GlobalStaticVariables - { - public: - explicit GlobalStaticVariables() {}; - virtual ~GlobalStaticVariables() {}; - - /** the physical time is global value for all dynamics */ - static Real physical_time_; - }; - - /** - * @class ParticleDynamics - * @brief The base class for all particle dynamics - * This class contains the only two interface functions available - * for particle dynamics. An specific implementation should be realized. - */ - template - class ParticleDynamics : public GlobalStaticVariables - { - public: - explicit ParticleDynamics(SPHBody* sph_body) - : GlobalStaticVariables(), sph_body_(sph_body), - particle_adaptation_(sph_body->particle_adaptation_), - base_particles_(sph_body->base_particles_) {}; - virtual ~ParticleDynamics() {}; - - SPHBody* getSPHBody() { return sph_body_; }; - /** The only two functions can be called from outside - * One is for sequential execution, the other is for parallel. */ - virtual ReturnType exec(Real dt = 0.0) = 0; - virtual ReturnType parallel_exec(Real dt = 0.0) = 0; - protected: - SPHBody* sph_body_; - ParticleAdaptation* particle_adaptation_; - BaseParticles* base_particles_; - - void setBodyUpdated() { sph_body_->setNewlyUpdated(); }; - /** the function for set global parameters for the particle dynamics */ - virtual void setupDynamics(Real dt = 0.0) {}; - }; - - /** - * @class DataDelegateBase - * @brief empty base class mixin template. - */ - class DataDelegateEmptyBase - { - public: - explicit DataDelegateEmptyBase(SPHBody* sph_body) {}; - virtual ~DataDelegateEmptyBase() {}; - }; - - /** - * @class DataDelegateSimple - * @brief prepare data for simple particle dynamics. - */ - template - class DataDelegateSimple - { - public: - explicit DataDelegateSimple(SPHBody* body) : - body_(dynamic_cast(body)), - particles_(dynamic_cast(body->base_particles_)), - material_(dynamic_cast(body->base_particles_->base_material_)), - sorted_id_(body_->base_particles_->sorted_id_), - unsorted_id_(body_->base_particles_->unsorted_id_) {}; - virtual ~DataDelegateSimple() {}; - protected: - BodyType* body_; - ParticlesType* particles_; - MaterialType* material_; - StdLargeVec& sorted_id_; - StdLargeVec& unsorted_id_; - }; - - /** - * @class DataDelegateInner - * @brief prepare data for inner particle dynamics - */ - template > - class DataDelegateInner : public BaseDataDelegateType - { - public: - explicit DataDelegateInner(BaseBodyRelationInner* body_inner_relation) : - BaseDataDelegateType(body_inner_relation->sph_body_), - inner_configuration_(body_inner_relation->inner_configuration_) {}; - virtual ~DataDelegateInner() {}; - protected: - /** inner configuration of the designated body */ - ParticleConfiguration& inner_configuration_; - }; - - /** - * @class DataDelegateContact - * @brief prepare data for contact particle dynamics - */ - template > - class DataDelegateContact : public BaseDataDelegateType - { - public: - explicit DataDelegateContact(BaseBodyRelationContact* body_contact_relation); - virtual ~DataDelegateContact() {}; - protected: - StdVec contact_bodies_; - StdVec contact_particles_; - StdVec contact_material_; - /** Configurations for particle interaction between bodies. */ - StdVec contact_configuration_; - }; - - /** - * @class DataDelegateComplex - * @brief prepare data for complex particle dynamics - */ - template - class DataDelegateComplex : - public DataDelegateInner, - public DataDelegateContact - { - public: - explicit DataDelegateComplex(ComplexBodyRelation* body_complex_relation) : - DataDelegateInner(body_complex_relation->inner_relation_), - DataDelegateContact(body_complex_relation->contact_relation_) {}; - virtual ~DataDelegateComplex() {}; - }; - - /** - * @class ParticleDynamicsComplex - * @brief particle dynamics by considering contribution from extra contact bodies - */ - template - class ParticleDynamicsComplex : public ParticleDynamicsInnerType, public ContactDataType - { - public: - ParticleDynamicsComplex(BaseBodyRelationInner* inner_relation, - BaseBodyRelationContact* contact_relation) : - ParticleDynamicsInnerType(inner_relation), ContactDataType(contact_relation) {}; - - ParticleDynamicsComplex(ComplexBodyRelation* complex_relation, - BaseBodyRelationContact* extra_contact_relation); - - virtual ~ParticleDynamicsComplex() {}; - protected: - virtual void prepareContactData() = 0; - }; -} -#endif //BASE_PARTICLE_DYNAMICS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.hpp b/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.hpp deleted file mode 100644 index 7bfb8bc779..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.hpp +++ /dev/null @@ -1,95 +0,0 @@ -/** -* @file base_particle_dynamics.hpp -* @brief This is the implementation of the template class for 3D build -* @author Chi ZHang and Xiangyu Hu -*/ - -#ifndef BASE_PARTICLE_DYNAMICS_HPP -#define BASE_PARTICLE_DYNAMICS_HPP - - - -#include "base_particle_dynamics.h" -//=================================================================================================// -namespace SPH { - //=================================================================================================// - template - DataDelegateContact - ::DataDelegateContact(BaseBodyRelationContact* body_contact_relation) : - BaseDataDelegateType(body_contact_relation->sph_body_) - { - RealBodyVector contact_sph_bodies = body_contact_relation->contact_bodies_; - for (size_t i = 0; i != contact_sph_bodies.size(); ++i) { - contact_bodies_.push_back(dynamic_cast(contact_sph_bodies[i])); - contact_particles_.push_back(dynamic_cast(contact_sph_bodies[i]->base_particles_)); - contact_material_.push_back(dynamic_cast(contact_sph_bodies[i]->base_particles_->base_material_)); - contact_configuration_.push_back(&body_contact_relation->contact_configuration_[i]); - } - } - //=================================================================================================// - template - ReturnType ReduceIterator(size_t total_real_particles, ReturnType temp, - ReduceFunctor& reduce_functor, ReduceOperation& reduce_operation, Real dt) - { - for (size_t i = 0; i < total_real_particles; ++i) - { - temp = reduce_operation(temp, reduce_functor(i, dt)); - } - return temp; - } - //=================================================================================================// - template - ReturnType ReduceIterator_parallel(size_t total_real_particles, ReturnType temp, - ReduceFunctor& reduce_functor, ReduceOperation& reduce_operation, Real dt) - { - return parallel_reduce(blocked_range(0, total_real_particles), - temp, [&](const blocked_range& r, ReturnType temp0)->ReturnType { - for (size_t i = r.begin(); i != r.end(); ++i) { - temp0 = reduce_operation(temp0, reduce_functor(i, dt)); - } - return temp0; - }, - [&](ReturnType x, ReturnType y)->ReturnType { - return reduce_operation(x, y); - } - ); - } - //=================================================================================================// - template - ParticleDynamicsComplex:: - ParticleDynamicsComplex(ComplexBodyRelation* complex_relation, - BaseBodyRelationContact* extra_contact_relation) : - ParticleDynamicsInnerType(complex_relation->inner_relation_), - ContactDataType(complex_relation->contact_relation_) - { - if (complex_relation->sph_body_ != extra_contact_relation->sph_body_) - { - std::cout << "\n Error: the two body_realtions do not have the same source body!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - - for (auto& extra_body : extra_contact_relation->contact_bodies_) - { - // here we first obtain the pointer to the most derived class and then implicitly downcast it to - // the types defined in the base complex dynamics - this->contact_bodies_.push_back(extra_body->ThisObjectPtr()); - this->contact_particles_.push_back(extra_body->base_particles_->ThisObjectPtr()); - this->contact_material_.push_back(extra_body->base_particles_->base_material_->ThisObjectPtr()); - } - - for (size_t i = 0; i != extra_contact_relation->contact_bodies_.size(); ++i) - { - this->contact_configuration_.push_back(&extra_contact_relation->contact_configuration_[i]); - } - } - //=================================================================================================// -} -//=================================================================================================// -#endif //BASE_PARTICLE_DYNAMICS_HPP \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/particle_dynamics_diffusion_reaction.h b/SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/particle_dynamics_diffusion_reaction.h deleted file mode 100644 index fe89730c06..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/particle_dynamics_diffusion_reaction.h +++ /dev/null @@ -1,297 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file particle_dynamics_diffusion_reaction.h -* @brief This is the particle dynamics aplliable for all type bodies -* @author Xiaojing Tang, Chi ZHang and Xiangyu Hu -*/ - - -#ifndef PARTICLE_DYNAMICS_DIFFUSION_REACTION_H -#define PARTICLE_DYNAMICS_DIFFUSION_REACTION_H - - - -#include "all_particle_dynamics.h" -#include "diffusion_reaction_particles.h" -#include "diffusion_reaction.h" - - -namespace SPH -{ - template - using DiffusionReactionSimpleData = DataDelegateSimple, - DiffusionReactionMaterial>; - - template - using DiffusionReactionInnerData = DataDelegateInner, - DiffusionReactionMaterial>; - - template - using DiffusionReactionContactData = DataDelegateContact, - DiffusionReactionMaterial, - ContactBodyType, DiffusionReactionParticles, - ContactBaseMaterialType, DataDelegateEmptyBase>; - - /** - * @class DiffusionReactionInitialCondition - * @brief pure abstract class for initial conditions - */ - template - class DiffusionReactionInitialCondition : - public ParticleDynamicsSimple, - public DiffusionReactionSimpleData - { - public: - DiffusionReactionInitialCondition(BodyType* diffusion_body); - virtual ~DiffusionReactionInitialCondition() {}; - protected: - StdLargeVec& pos_n_; - StdVec>& species_n_; - }; - - /** - * @class GetDiffusionTimeStepSize - * @brief Computing the time step size based on diffusion coefficient and particle smoothing length - */ - template - class GetDiffusionTimeStepSize : - public ParticleDynamics, - public DiffusionReactionSimpleData - { - public: - explicit GetDiffusionTimeStepSize(BodyType* body); - virtual ~GetDiffusionTimeStepSize() {}; - - virtual Real exec(Real dt = 0.0) override { return diff_time_step_; }; - virtual Real parallel_exec(Real dt = 0.0) override { return exec(dt); }; - protected: - Real diff_time_step_; - }; - - /** - * @class RelaxationOfAllDiffussionSpeciesInner - * @brief Compute the diffusion relaxation process of all species - */ - template - class RelaxationOfAllDiffussionSpeciesInner : - public InteractionDynamicsWithUpdate, - public DiffusionReactionInnerData - { - /** all diffusion species and diffusion relation. */ - StdVec species_diffusion_; - StdVec>& species_n_; - StdVec>& diffusion_dt_; - StdLargeVec& Vol_; - protected: - void initializeDiffusionChangeRate(size_t particle_i); - void getDiffusionChangeRate(size_t particle_i, size_t particle_j, Vecd& e_ij, Real surface_area_ij); - virtual void updateSpeciesDiffusion(size_t particle_i, Real dt); - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - public: - RelaxationOfAllDiffussionSpeciesInner(BaseBodyRelationInner* body_inner_relation); - virtual ~RelaxationOfAllDiffussionSpeciesInner() {}; - }; - - /** - * @class RelaxationOfAllDiffussionSpeciesComplex - * Complex diffusion relaxation between two different bodies - */ - template - class RelaxationOfAllDiffussionSpeciesComplex : - public RelaxationOfAllDiffussionSpeciesInner, - public DiffusionReactionContactData - { - StdVec species_diffusion_; - StdVec>& species_n_; - StdVec>& diffusion_dt_; - StdVec*> contact_Vol_; - StdVec>*> contact_species_n_; - protected: - void getDiffusionChangeRateContact(size_t particle_i, size_t particle_j, Vecd& e_ij, - Real surface_area_ij, StdVec>& species_n_k); - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - public: - RelaxationOfAllDiffussionSpeciesComplex(ComplexBodyRelation* body_complex_relation); - virtual ~RelaxationOfAllDiffussionSpeciesComplex() {}; - }; - - /** - * @class RungeKuttaInitialization - * @brief initialization of a runge-kutta integration scheme - */ - template - class RungeKuttaInitialization : - public ParticleDynamicsSimple, - public DiffusionReactionSimpleData - { - StdVec species_diffusion_; - StdVec>& species_n_, & species_s_; - - void initializeIntermediateValue(size_t particle_i); - virtual void Update(size_t index_i, Real dt = 0.0) override; - public: - RungeKuttaInitialization(SPHBody* body, StdVec>& species_s); - virtual ~RungeKuttaInitialization() {}; - }; - - /** - * @class RungeKutta2Stages2ndStage - * @brief the second stage of the second runge-kutta scheme - */ - template - class RungeKutta2Stages2ndStage : public RungeKutta2Stages1stStageType - { - StdVec species_diffusion_; - StdVec>& species_n_; - StdVec>& diffusion_dt_; - protected: - StdVec>& species_s_; - virtual void updateSpeciesDiffusion(size_t particle_i, Real dt) override; - public: - RungeKutta2Stages2ndStage(BodyRelationType* body_relation, StdVec>& species_s); - virtual ~RungeKutta2Stages2ndStage() {}; - }; - - /** - * @class RelaxationOfAllDiffusionSpeciesRK2 - * @brief Compute the diffusion relaxation process of all species - * with second order Runge-Kutta time stepping - */ - template - class RelaxationOfAllDiffusionSpeciesRK2 : public ParticleDynamics, - public DiffusionReactionSimpleData - { - protected: - StdVec species_diffusion_; - /** Intermediate Value */ - StdVec> species_s_; - - RungeKuttaInitialization runge_kutta_initialization_; - RungeKutta2Stages1stStageType runge_kutta_1st_stage_; - RungeKutta2Stages2ndStage runge_kutta_2nd_stage_; - public: - RelaxationOfAllDiffusionSpeciesRK2(BodyRelationType* body_relation); - virtual ~RelaxationOfAllDiffusionSpeciesRK2() {}; - - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override; - }; - - - struct UpdateAReactionSpecies - { - Real operator () (Real input, Real production_rate, Real loss_rate, Real dt) const - { - return input * exp(-loss_rate * dt) + production_rate * (1.0 - exp(-loss_rate * dt)) / (loss_rate + TinyReal); - }; - }; - - /** - * @class RelaxationOfAllReactionsForward - * @brief Compute the reaction process of all species by forward splitting - */ - template - class RelaxationOfAllReactionsForward : - public ParticleDynamicsSimple, - public DiffusionReactionSimpleData - { - BaseReactionModel* species_reaction_; - StdVec>& species_n_; - UpdateAReactionSpecies updateAReactionSpecies; - protected: - virtual void Update(size_t index_i, Real dt = 0.0) override; - public: - RelaxationOfAllReactionsForward(BodyType* body); - virtual ~RelaxationOfAllReactionsForward() {}; - }; - - /** - * @class RelaxationOfAllReactionsBackward - * @brief Compute the reaction process of all species by backward splitting - */ - template - class RelaxationOfAllReactionsBackward : - public ParticleDynamicsSimple, - public DiffusionReactionSimpleData - { - BaseReactionModel* species_reaction_; - StdVec>& species_n_; - UpdateAReactionSpecies updateAReactionSpecies; - protected: - virtual void Update(size_t index_i, Real dt = 0.0) override; - public: - RelaxationOfAllReactionsBackward(BodyType* body); - virtual ~RelaxationOfAllReactionsBackward() {}; - }; - - /** - * @class ConstrainDiffusionBodyRegion - * @brief set boundary condition for diffusion problem - */ - template - class ConstrainDiffusionBodyRegion : - public PartSimpleDynamicsByParticle, - public DiffusionReactionSimpleData - { - public: - ConstrainDiffusionBodyRegion(BodyType* body, BodyPartByParticleType* body_part) : - PartSimpleDynamicsByParticle(body, body_part), - DiffusionReactionSimpleData(body), - pos_n_(this->particles_->pos_n_), species_n_(this->particles_->species_n_) {}; - virtual ~ConstrainDiffusionBodyRegion() {}; - protected: - StdLargeVec& pos_n_; - StdVec>& species_n_; - }; - - /** - * @class DiffusionBasedMapping - * @brief Mapping inside of body according to diffusion. - * This is a abstract class to be override for case specific implementation - */ - template - class DiffusionBasedMapping : - public ParticleDynamicsSimple, - public DiffusionReactionSimpleData - { - public: - DiffusionBasedMapping(BodyType* body) : - ParticleDynamicsSimple(body), - DiffusionReactionSimpleData(body), - pos_n_(this->particles_->pos_n_), species_n_(this->particles_->species_n_) {}; - virtual ~DiffusionBasedMapping() {}; - protected: - StdLargeVec& pos_n_; - StdVec>& species_n_; - }; -} -#endif //PARTICLE_DYNAMICS_DIFFUSION_REACTION_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/particle_dynamics_diffusion_reaction.hpp b/SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/particle_dynamics_diffusion_reaction.hpp deleted file mode 100644 index 7247ccfdd7..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/particle_dynamics_diffusion_reaction.hpp +++ /dev/null @@ -1,314 +0,0 @@ -/** -* @file particle_dynamics_diffusion_reaction.hpp -* @author Xiaojing Tang, Chi ZHang and Xiangyu Hu -*/ - - -#ifndef PARTICLE_DYNAMICS_DIFFUSION_REACTION_HPP -#define PARTICLE_DYNAMICS_DIFFUSION_REACTION_HPP - - - -#include "particle_dynamics_diffusion_reaction.h" - -namespace SPH -{ - //=================================================================================================// - template - DiffusionReactionInitialCondition:: - DiffusionReactionInitialCondition(BodyType* diffusion_body) : - ParticleDynamicsSimple(diffusion_body), - DiffusionReactionSimpleData(diffusion_body), - pos_n_(this->particles_->pos_n_), species_n_(this->particles_->species_n_) {} - //=================================================================================================// - template - GetDiffusionTimeStepSize:: - GetDiffusionTimeStepSize(BodyType* body) : - ParticleDynamics(body), - DiffusionReactionSimpleData(body) - { - Real smoothing_length = body->particle_adaptation_->ReferenceSmoothingLength(); - diff_time_step_ = this->material_->getDiffusionTimeStepSize(smoothing_length); - } - //=================================================================================================// - template - RelaxationOfAllDiffussionSpeciesInner:: - RelaxationOfAllDiffussionSpeciesInner(BaseBodyRelationInner* body_inner_relation) : - InteractionDynamicsWithUpdate(body_inner_relation->sph_body_), - DiffusionReactionInnerData(body_inner_relation), - species_n_(this->particles_->species_n_), - diffusion_dt_(this->particles_->diffusion_dt_), Vol_(this->particles_->Vol_) - { - species_diffusion_ = this->material_->SpeciesDiffusion(); - } - //=================================================================================================// - template - void RelaxationOfAllDiffussionSpeciesInner:: - initializeDiffusionChangeRate(size_t particle_i) - { - for (size_t m = 0; m < species_diffusion_.size(); ++m) - { - diffusion_dt_[m][particle_i] = 0; - } - } - //=================================================================================================// - template - void RelaxationOfAllDiffussionSpeciesInner:: - getDiffusionChangeRate(size_t particle_i, size_t particle_j, Vecd& e_ij, Real surface_area_ij) - { - for (size_t m = 0; m < species_diffusion_.size(); ++m) - { - Real diff_coff_ij = species_diffusion_[m]->getInterParticleDiffusionCoff(particle_i, particle_j, e_ij); - size_t l = species_diffusion_[m]->gradient_species_index_; - Real phi_ij = species_n_[l][particle_i] - species_n_[l][particle_j]; - diffusion_dt_[m][particle_i] += diff_coff_ij * phi_ij * surface_area_ij; - } - } - //=================================================================================================// - template - void RelaxationOfAllDiffussionSpeciesInner:: - updateSpeciesDiffusion(size_t particle_i, Real dt) - { - for (size_t m = 0; m < species_diffusion_.size(); ++m) - { - size_t k = species_diffusion_[m]->diffusion_species_index_; - species_n_[k][particle_i] += dt * diffusion_dt_[m][particle_i]; - } - } - //=================================================================================================// - template - void RelaxationOfAllDiffussionSpeciesInner:: - Interaction(size_t index_i, Real dt) - { - DiffusionReactionParticles* particles = this->particles_; - Neighborhood& inner_neighborhood = this->inner_configuration_[index_i]; - - initializeDiffusionChangeRate(index_i); - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - Real dW_ij_ = inner_neighborhood.dW_ij_[n]; - Real r_ij_ = inner_neighborhood.r_ij_[n]; - Vecd& e_ij = inner_neighborhood.e_ij_[n]; - - const Vecd& gradi_ij = particles->getKernelGradient(index_i, index_j, dW_ij_, e_ij); - Real area_ij = 2.0 * Vol_[index_j] * dot(gradi_ij, e_ij) / r_ij_; - getDiffusionChangeRate(index_i, index_j, e_ij, area_ij); - } - } - //=================================================================================================// - template - void RelaxationOfAllDiffussionSpeciesInner:: - Update(size_t index_i, Real dt) - { - updateSpeciesDiffusion(index_i, dt); - } - //=================================================================================================// - template - RelaxationOfAllDiffussionSpeciesComplex:: - RelaxationOfAllDiffussionSpeciesComplex(ComplexBodyRelation* body_complex_relation) : - RelaxationOfAllDiffussionSpeciesInner(body_complex_relation->inner_relation_), - DiffusionReactionContactData(body_complex_relation->contact_relation_), - species_n_(this->particles_->species_n_), diffusion_dt_(this->particles_->diffusion_dt_) - { - species_diffusion_ = this->material_->SpeciesDiffusion(); - - for (size_t k = 0; k != this->contact_particles_.size(); ++k) - { - contact_Vol_.push_back(&(this->contact_particles_[k]->Vol_)); - contact_species_n_.push_back(&(this->contact_particles_[k]->species_n_)); - } - } - //=================================================================================================// - template - void RelaxationOfAllDiffussionSpeciesComplex:: - getDiffusionChangeRateContact(size_t particle_i, size_t particle_j, Vecd& e_ij, - Real surface_area_ij, StdVec>& species_n_k) - { - for (size_t m = 0; m < species_diffusion_.size(); ++m) - { - Real diff_coff_ij = species_diffusion_[m]->getInterParticleDiffusionCoff(particle_i, particle_j, e_ij); - size_t l = species_diffusion_[m]->gradient_species_index_; - Real phi_ij = species_n_[l][particle_i] - species_n_k[l][particle_j]; - diffusion_dt_[m][particle_i] += diff_coff_ij * phi_ij * surface_area_ij; - } - } - //=================================================================================================// - template - void RelaxationOfAllDiffussionSpeciesComplex:: - Interaction(size_t index_i, Real dt) - { - RelaxationOfAllDiffussionSpeciesInner::Interaction(index_i, dt); - DiffusionReactionParticles* particles = this->particles_; - - for (size_t k = 0; k < this->contact_configuration_.size(); ++k) - { - StdLargeVec& Vol_k = *(contact_Vol_[k]); - StdVec>& species_n_k = *(contact_species_n_[k]); - - Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - Real r_ij_ = contact_neighborhood.r_ij_[n]; - Real dW_ij_ = contact_neighborhood.dW_ij_[n]; - Vecd& e_ij = contact_neighborhood.e_ij_[n]; - - const Vecd& gradi_ij = particles->getKernelGradient(index_i, index_j, dW_ij_, e_ij); - Real area_ij = 2.0 * Vol_k[index_j] * dot(gradi_ij, e_ij) / r_ij_; - getDiffusionChangeRateContact(index_i, index_j, e_ij, area_ij, species_n_k); - } - } - } - //=================================================================================================// - template - RungeKuttaInitialization:: - RungeKuttaInitialization(SPHBody* body, StdVec>& species_s) : - ParticleDynamicsSimple(body), - DiffusionReactionSimpleData(body), - species_n_(this->particles_->species_n_), species_s_(species_s) - { - species_diffusion_ = this->material_->SpeciesDiffusion(); - } - //=================================================================================================// - template - void RungeKuttaInitialization:: - initializeIntermediateValue(size_t particle_i) - { - for (size_t m = 0; m < species_diffusion_.size(); ++m) - { - size_t k = species_diffusion_[m]->diffusion_species_index_; - species_s_[m][particle_i] = species_n_[k][particle_i]; - } - } - //=================================================================================================// - template - void RungeKuttaInitialization:: - Update(size_t index_i, Real dt) - { - initializeIntermediateValue(index_i); - } - //=================================================================================================// - template - RungeKutta2Stages2ndStage:: - RungeKutta2Stages2ndStage(BodyRelationType* body_relation, StdVec>& species_s) : - RungeKutta2Stages1stStageType(body_relation), - species_n_(this->particles_->species_n_), diffusion_dt_(this->particles_->diffusion_dt_), - species_s_(species_s) - { - species_diffusion_ = this->material_->SpeciesDiffusion(); - } - //=================================================================================================// - template - void RungeKutta2Stages2ndStage:: - updateSpeciesDiffusion(size_t particle_i, Real dt) - { - for (size_t m = 0; m < this->species_diffusion_.size(); ++m) - { - size_t k = species_diffusion_[m]->diffusion_species_index_; - species_n_[k][particle_i] = 0.5 * species_s_[m][particle_i] - + 0.5 * (species_n_[k][particle_i] + dt * diffusion_dt_[m][particle_i]); - } - } - //=================================================================================================// - template - RelaxationOfAllDiffusionSpeciesRK2:: - RelaxationOfAllDiffusionSpeciesRK2(BodyRelationType* body_relation) : - ParticleDynamics(body_relation->sph_body_), - DiffusionReactionSimpleData(body_relation->sph_body_), - runge_kutta_initialization_(body_relation->sph_body_, species_s_), - runge_kutta_1st_stage_(body_relation), - runge_kutta_2nd_stage_(body_relation, species_s_) - { - StdVec species_diffusion_ = this->material_->SpeciesDiffusion(); - - size_t number_of_diffusion_species = species_diffusion_.size(); - species_s_.resize(number_of_diffusion_species); - for (size_t m = 0; m < number_of_diffusion_species; ++m) - { - //the size should be the same as that in the base particles - species_s_[m].resize(this->particles_->real_particles_bound_); - //register data in base particles - std::get(this->particles_->all_particle_data_).push_back(&species_s_[m]); - } - } - //=================================================================================================// - template - void RelaxationOfAllDiffusionSpeciesRK2::exec(Real dt) - { - runge_kutta_initialization_.exec(); - runge_kutta_1st_stage_.exec(dt); - runge_kutta_2nd_stage_.exec(dt); - } - //=================================================================================================// - template - void RelaxationOfAllDiffusionSpeciesRK2::parallel_exec(Real dt) - { - runge_kutta_initialization_.parallel_exec(); - runge_kutta_1st_stage_.parallel_exec(dt); - runge_kutta_2nd_stage_.parallel_exec(dt); - } - //=================================================================================================// - template - RelaxationOfAllReactionsForward:: - RelaxationOfAllReactionsForward(BodyType* body) : - ParticleDynamicsSimple(body), - DiffusionReactionSimpleData(body), - species_n_(this->particles_->species_n_) - { - species_reaction_ = this->material_->SpeciesReaction(); - } - //=================================================================================================// - template - void RelaxationOfAllReactionsForward:: - Update(size_t index_i, Real dt) - { - IndexVector& reactive_species = species_reaction_->reactive_species_; - - for (size_t m = 0; m != reactive_species.size(); ++m) { - size_t k = reactive_species[m]; - Real production_rate = species_reaction_->get_production_rates_[k](species_n_, index_i); - Real loss_rate = species_reaction_->get_loss_rates_[k](species_n_, index_i); - species_n_[k][index_i] = updateAReactionSpecies(species_n_[k][index_i], production_rate, loss_rate, dt); - } - } - //=================================================================================================// - template - RelaxationOfAllReactionsBackward:: - RelaxationOfAllReactionsBackward(BodyType* body) : - ParticleDynamicsSimple(body), - DiffusionReactionSimpleData(body), - species_n_(this->particles_->species_n_) - { - species_reaction_ = this->material_->SpeciesReaction(); - } - //=================================================================================================// - template - void RelaxationOfAllReactionsBackward:: - Update(size_t index_i, Real dt) - { - IndexVector& reactive_species = species_reaction_->reactive_species_; - - for (size_t m = reactive_species.size(); m != 0; --m) { - size_t k = reactive_species[m - 1]; - Real production_rate = species_reaction_->get_production_rates_[k](species_n_, index_i); - Real loss_rate = species_reaction_->get_loss_rates_[k](species_n_, index_i); - species_n_[k][index_i] = updateAReactionSpecies(species_n_[k][index_i], production_rate, loss_rate, dt); - } - } - //=================================================================================================// -} -#endif //PARTICLE_DYNAMICS_DIFFUSION_REACTION_HPP \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/particle_dynamics_dissipation.h b/SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/particle_dynamics_dissipation.h deleted file mode 100644 index 9fdc4c47fa..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/particle_dynamics_dissipation.h +++ /dev/null @@ -1,201 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file particle_dynamics_dissipation.h -* @brief This is the particle dynamics aplliable for all type bodies -* @author Chi ZHang and Xiangyu Hu -*/ - - -#ifndef PARTICLE_DYNAMICS_DISSIPATION_H -#define PARTICLE_DYNAMICS_DISSIPATION_H - - - -#include "all_particle_dynamics.h" - -namespace SPH -{ - typedef DataDelegateInner DissipationDataInner; - typedef DataDelegateContact DissipationDataContact; - typedef DataDelegateContact DissipationDataWithWall; - - template - struct ErrorAndParameters - { - VariableType error_; - Real a_, c_; - ErrorAndParameters(Real zero = 0.0) : - error_(zero), a_(zero), c_(zero) {}; - }; - - /** - * @class DampingBySplittingAlgorithm - * @brief A quantity damping by splitting scheme - * this method modifies the quantity directly. - * Note that, if periodic boundary condition is applied, - * the parallelized version of the method requires the one using ghost particles - * because the splitting partition only works in this case. - */ - template - class DampingBySplittingInner : - public InteractionDynamicsSplitting, public DissipationDataInner - { - protected: - public: - DampingBySplittingInner(BaseBodyRelationInner* body_inner_relation, - std::string variable_name, Real eta); - virtual ~DampingBySplittingInner() {}; - void resetDampingCoefficient(Real reset_ratio) { eta_ *= reset_ratio; }; - protected: - Real eta_; /**< damping coefficient */ - StdLargeVec& Vol_, & mass_; - StdLargeVec& variable_; - - virtual ErrorAndParameters computeErrorAndParameters(size_t index_i, Real dt = 0.0); - virtual void updateStates(size_t index_i, Real dt, const ErrorAndParameters& error_and_parameters); - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - template - class DampingBySplittingComplex : - public DampingBySplittingInner, public DissipationDataContact - { - public: - DampingBySplittingComplex(ComplexBodyRelation* body_complex_relation, - std::string variable_name, Real eta); - virtual ~DampingBySplittingComplex() {}; - protected: - virtual ErrorAndParameters computeErrorAndParameters(size_t index_i, Real dt = 0.0) override; - virtual void updateStates(size_t index_i, Real dt, const ErrorAndParameters& error_and_parameters) override; - private: - StdVec*> contact_Vol_, contact_mass_; - StdVec*> contact_variable_; - }; - - template - class BaseDampingBySplittingType> - class DampingBySplittingWithWall : - public BaseDampingBySplittingType, public DissipationDataWithWall - { - public: - DampingBySplittingWithWall(ComplexBodyRelation* body_wall_relation, - std::string variable_name, Real eta); - virtual ~DampingBySplittingWithWall() {}; - protected: - virtual ErrorAndParameters computeErrorAndParameters(size_t index_i, Real dt = 0.0) override; - private: - StdVec*> wall_Vol_; - StdVec*> wall_variable_; - }; - - /** - * @class DampingPairwiseInner - * @brief A quantity damping by a pairwise splitting scheme - * this method modifies the quantity directly - * Note that, if periodic boundary condition is applied, - * the parallelized version of the method requires the one using ghost particles - * because the splitting partition only works in this case. - */ - template - class DampingPairwiseInner : - public InteractionDynamicsSplitting, public DissipationDataInner - { - public: - DampingPairwiseInner(BaseBodyRelationInner* body_inner_relation, - std::string variable_name, Real eta); - virtual ~DampingPairwiseInner() {}; - void resetDampingCoefficient(Real reset_ratio) { eta_ *= reset_ratio; }; - protected: - StdLargeVec& Vol_, & mass_; - StdLargeVec& variable_; - Real eta_; /**< damping coefficient */ - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - template - class DampingPairwiseComplex : - public DampingPairwiseInner, public DissipationDataContact - { - public: - DampingPairwiseComplex(ComplexBodyRelation* body_complex_relation, - std::string variable_name, Real eta); - virtual ~DampingPairwiseComplex() {}; - protected: - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - private: - StdVec*> contact_Vol_, contact_mass_; - StdVec*> contact_variable_; - }; - - /** - * @class DampingPairwiseWithWall - * @brief Damping with wall by which the wall velocity is not updated - * and the mass of wall particle is not considered. - */ - template - class BaseDampingPairwiseType> - class DampingPairwiseWithWall : - public BaseDampingPairwiseType, - public DissipationDataWithWall - { - public: - DampingPairwiseWithWall(ComplexBodyRelation* body_wall_relation, - std::string variable_name, Real eta); - virtual ~DampingPairwiseWithWall() {}; - protected: - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - private: - StdVec*> wall_Vol_; - StdVec*> wall_variable_; - }; - - /** - * @class DampingWithRandomChoice - * @brief A random choice method for obstaining static equilibrium state - * Note that, if periodic boundary condition is applied, - * the parallelized version of the method requires the one using ghost particles - * because the splitting partition only works in this case. - */ - template - class DampingWithRandomChoice : public DampingAlgorithmType - { - protected: - Real random_ratio_; - bool RandomChoice(); - public: - template - DampingWithRandomChoice(BodyRelationType* body_relation, - Real random_ratio, std::string variable_name, Real eta); - virtual ~DampingWithRandomChoice() {}; - - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override; - }; -} -#endif //PARTICLE_DYNAMICS_DISSIPATION_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/particle_dynamics_dissipation.hpp b/SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/particle_dynamics_dissipation.hpp deleted file mode 100644 index 8304ffb871..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/particle_dynamics_dissipation.hpp +++ /dev/null @@ -1,418 +0,0 @@ -/** -* @file particle_dynamics_dissipation.hpp -* @brief This is the particle dynamics aplliable for all type bodies -* @author Chi ZHang and Xiangyu Hu -*/ - - -#ifndef PARTICLE_DYNAMICS_DISSIPATION_HPP -#define PARTICLE_DYNAMICS_DISSIPATION_HPP - - - -#include "particle_dynamics_dissipation.h" - -namespace SPH -{ - //=================================================================================================// - template - DampingBySplittingInner:: - DampingBySplittingInner(BaseBodyRelationInner* body_inner_relation, - std::string variable_name, Real eta) : - InteractionDynamicsSplitting(body_inner_relation->sph_body_), - DissipationDataInner(body_inner_relation), eta_(eta), - Vol_(particles_->Vol_), mass_(particles_->mass_), - variable_(*particles_->getVariableByName(variable_name)) {} - //=================================================================================================// - template - ErrorAndParameters - DampingBySplittingInner::computeErrorAndParameters(size_t index_i, Real dt) - { - Real Vol_i = Vol_[index_i]; - Real mass_i = mass_[index_i]; - VariableType& variable_i = variable_[index_i]; - ErrorAndParameters error_and_parameters(0); - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - //linear projection - VariableType variable_derivative = (variable_i - variable_[index_j]); - Real parameter_b = 2.0 * eta_ * inner_neighborhood.dW_ij_[n] - * Vol_i * Vol_[index_j] * dt / inner_neighborhood.r_ij_[n]; - - error_and_parameters.error_ -= variable_derivative * parameter_b; - error_and_parameters.a_ += parameter_b; - error_and_parameters.c_ += parameter_b * parameter_b; - } - error_and_parameters.a_ -= mass_i; - return error_and_parameters; - } - //=================================================================================================// - template - void DampingBySplittingInner:: - updateStates(size_t index_i, Real dt, const ErrorAndParameters& error_and_parameters) - { - Real parameter_l = error_and_parameters.a_ * error_and_parameters.a_ + error_and_parameters.c_; - VariableType parameter_k = error_and_parameters.error_ / (parameter_l + TinyReal); - variable_[index_i] += parameter_k * error_and_parameters.a_; - - Real Vol_i = Vol_[index_i]; - VariableType& variable_i = variable_[index_i]; - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - - Real parameter_b = 2.0 * eta_ * inner_neighborhood.dW_ij_[n] - * Vol_i * Vol_[index_j] * dt / inner_neighborhood.r_ij_[n]; - - //predicted quantity at particle j - VariableType variable_j = variable_[index_j] - parameter_k * parameter_b; - VariableType variable_derivative = (variable_i - variable_j); - - //exchange in conservation form - variable_[index_j] -= variable_derivative * parameter_b / mass_[index_j]; - } - } - //=================================================================================================// - template - void DampingBySplittingInner::Interaction(size_t index_i, Real dt) - { - ErrorAndParameters error_and_parameters = computeErrorAndParameters(index_i, dt); - updateStates(index_i, dt, error_and_parameters); - } - //=================================================================================================// - template - DampingBySplittingComplex:: - DampingBySplittingComplex(ComplexBodyRelation* body_complex_relation, - std::string variable_name, Real eta) : - DampingBySplittingInner(body_complex_relation->inner_relation_, variable_name, eta), - DissipationDataContact(body_complex_relation->contact_relation_) - { - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); - contact_mass_.push_back(&(contact_particles_[k]->mass_)); - contact_variable_.push_back(contact_particles_[k]->template getVariableByName(variable_name)); - } - } - //=================================================================================================// - template - ErrorAndParameters - DampingBySplittingComplex::computeErrorAndParameters(size_t index_i, Real dt) - { - ErrorAndParameters error_and_parameters - = DampingBySplittingInner::computeErrorAndParameters(index_i, dt); - - VariableType& variable_i = this->variable_[index_i]; - Real Vol_i = this->Vol_[index_i]; - /** Contact interaction. */ - for (size_t k = 0; k < this->contact_configuration_.size(); ++k) - { - StdLargeVec& Vol_k = *(this->contact_Vol_[k]); - StdLargeVec& mass_k = *(this->contact_mass_[k]); - StdLargeVec& variable_k = *(this->contact_variable_[k]); - Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - - //linear projection - VariableType variable_derivative = (variable_i - variable_k[index_j]); - Real parameter_b = 2.0 * this->eta_ * contact_neighborhood.dW_ij_[n] - * Vol_i * Vol_k[index_j] * dt / contact_neighborhood.r_ij_[n]; - - error_and_parameters.error_ -= variable_derivative * parameter_b; - error_and_parameters.a_ += parameter_b; - error_and_parameters.c_ += parameter_b * parameter_b; - } - return error_and_parameters; - } - } - //=================================================================================================// - template - void DampingBySplittingComplex:: - updateStates(size_t index_i, Real dt, const ErrorAndParameters& error_and_parameters) - { - DampingBySplittingInner::updateStates(index_i, dt, error_and_parameters); - - Real parameter_l = error_and_parameters.a_ * error_and_parameters.a_ + error_and_parameters.c_; - VariableType parameter_k = error_and_parameters.error_ / (parameter_l + TinyReal); - VariableType& variable_i = this->variable_[index_i]; - Real Vol_i = this->Vol_[index_i]; - /** Contact interaction. */ - for (size_t k = 0; k < this->contact_configuration_.size(); ++k) - { - StdLargeVec& Vol_k = *(this->contact_Vol_[k]); - StdLargeVec& mass_k = *(this->contact_mass_[k]); - StdLargeVec& variable_k = *(this->contact_variable_[k]); - Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - - //linear projection - Real parameter_b = 2.0 * this->eta_ * contact_neighborhood.dW_ij_[n] - * Vol_i * Vol_k[index_j] * dt / contact_neighborhood.r_ij_[n]; - - //predicted quantity at particle j - VariableType variable_j = this->variable_k[index_j] - parameter_k * parameter_b; - VariableType variable_derivative = (variable_i - variable_j); - - //exchange in conservation form - this->variable_k[index_j] -= variable_derivative * parameter_b / mass_k[index_j]; - } - } - } - //=================================================================================================// - template class BaseDampingBySplittingType> - DampingBySplittingWithWall:: - DampingBySplittingWithWall(ComplexBodyRelation* body_wall_relation, - std::string variable_name, Real eta) : - BaseDampingBySplittingType(body_wall_relation->inner_relation_, variable_name, eta), - DissipationDataWithWall(body_wall_relation->contact_relation_) - { - for (size_t k = 0; k != DissipationDataWithWall::contact_particles_.size(); ++k) - { - wall_Vol_.push_back(&(contact_particles_[k]->Vol_)); - wall_variable_.push_back(contact_particles_[k]->template getVariableByName(variable_name)); - } - } - //=================================================================================================// - template class BaseDampingBySplittingType> - ErrorAndParameters - DampingBySplittingWithWall:: - computeErrorAndParameters(size_t index_i, Real dt) - { - ErrorAndParameters error_and_parameters - = BaseDampingBySplittingType::computeErrorAndParameters(index_i, dt); - - VariableType& variable_i = this->variable_[index_i]; - Real Vol_i = this->Vol_[index_i]; - /** Contact interaction. */ - for (size_t k = 0; k < this->contact_configuration_.size(); ++k) - { - StdLargeVec& Vol_k = *(this->wall_Vol_[k]); - StdLargeVec& variable_k = *(this->wall_variable_[k]); - Neighborhood& contact_neighborhood = (*DissipationDataWithWall::contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - - //linear projection - VariableType variable_derivative = (variable_i - variable_k[index_j]); - Real parameter_b = 2.0 * this->eta_ * contact_neighborhood.dW_ij_[n] - * Vol_i * Vol_k[index_j] * dt / contact_neighborhood.r_ij_[n]; - - error_and_parameters.error_ -= variable_derivative * parameter_b; - error_and_parameters.a_ += parameter_b; - error_and_parameters.c_ += parameter_b * parameter_b; - } - } - return error_and_parameters; - } - //=================================================================================================// - template - DampingPairwiseInner:: - DampingPairwiseInner(BaseBodyRelationInner* body_inner_relation, - std::string variable_name, Real eta) : - InteractionDynamicsSplitting(body_inner_relation->sph_body_), - DissipationDataInner(body_inner_relation), - Vol_(particles_->Vol_), mass_(particles_->mass_), - variable_(*particles_->getVariableByName(variable_name)), - eta_(eta) {} - //=================================================================================================// - template - void DampingPairwiseInner::Interaction(size_t index_i, Real dt) - { - Real Vol_i = Vol_[index_i]; - Real mass_i = mass_[index_i]; - VariableType& variable_i = variable_[index_i]; - - std::array parameter_b; - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - //forward sweep - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - Real mass_j = mass_[index_j]; - - VariableType variable_derivative = (variable_i - variable_[index_j]); - parameter_b[n] = eta_ * inner_neighborhood.dW_ij_[n] - * Vol_i * Vol_[index_j] * dt / inner_neighborhood.r_ij_[n]; - - VariableType increment = parameter_b[n] * variable_derivative - / (mass_i * mass_j - parameter_b[n] * (mass_i + mass_j)); - variable_[index_i] += increment * mass_j; - variable_[index_j] -= increment * mass_i; - } - - //backward sweep - for (size_t n = inner_neighborhood.current_size_; n != 0; --n) - { - size_t index_j = inner_neighborhood.j_[n - 1]; - Real mass_j = mass_[index_j]; - - VariableType variable_derivative = (variable_i - variable_[index_j]); - VariableType increment = parameter_b[n - 1] * variable_derivative - / (mass_i * mass_j - parameter_b[n - 1] * (mass_i + mass_j)); - - variable_[index_i] += increment * mass_j; - variable_[index_j] -= increment * mass_i; - } - } - //=================================================================================================// - template - DampingPairwiseComplex:: - DampingPairwiseComplex(ComplexBodyRelation* body_complex_relation, - std::string variable_name, Real eta) : - DampingPairwiseInner(body_complex_relation->inner_relation_, variable_name, eta), - DissipationDataContact(body_complex_relation->contact_relation_) - { - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); - contact_mass_.push_back(&(contact_particles_[k]->mass_)); - contact_variable_.push_back(contact_particles_[k]->template getVariableByName(variable_name)); - } - } - //=================================================================================================// - template - void DampingPairwiseComplex::Interaction(size_t index_i, Real dt) - { - DampingPairwiseInner::Interaction(index_i, dt); - - Real Vol_i = this->Vol_[index_i]; - Real mass_i = this->mass_[index_i]; - VariableType& variable_i = this->variable_[index_i]; - - std::array parameter_b; - - /** Contact interaction. */ - for (size_t k = 0; k < this->contact_configuration_.size(); ++k) - { - StdLargeVec& Vol_k = *(this->contact_Vol_[k]); - StdLargeVec& mass_k = *(this->contact_mass_[k]); - StdLargeVec& variable_k = *(this->contact_variable_[k]); - Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; - //forward sweep - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - Real mass_j = mass_k[index_j]; - - VariableType variable_derivative = (variable_i - variable_k[index_j]); - parameter_b[n] = this->eta_ * contact_neighborhood.dW_ij_[n] - * Vol_i * Vol_k[index_j] * dt / contact_neighborhood.r_ij_[n]; - - VariableType increment = parameter_b[n] * variable_derivative - / (mass_i * mass_j - parameter_b[n] * (mass_i + mass_j)); - this->variable_[index_i] += increment * mass_j; - variable_k[index_j] -= increment * mass_i; - - } - //backward sweep - for (size_t n = contact_neighborhood.current_size_; n != 0; --n) - { - size_t index_j = contact_neighborhood.j_[n - 1]; - Real mass_j = mass_k[index_j]; - - VariableType variable_derivative = (variable_i - variable_k[index_j]); - VariableType increment = parameter_b[n - 1] * variable_derivative - / (mass_i * mass_j - parameter_b[n - 1] * (mass_i + mass_j)); - - this->variable_[index_i] += increment * mass_j; - variable_k[index_j] -= increment * mass_i; - } - } - } - //=================================================================================================// - template class BaseDampingPairwiseType> - DampingPairwiseWithWall:: - DampingPairwiseWithWall(ComplexBodyRelation* body_wall_relation, - std::string variable_name, Real eta) : - BaseDampingPairwiseType(body_wall_relation->inner_relation_, variable_name, eta), - DissipationDataWithWall(body_wall_relation->contact_relation_) - { - for (size_t k = 0; k != DissipationDataWithWall::contact_particles_.size(); ++k) - { - wall_Vol_.push_back(&(contact_particles_[k]->Vol_)); - wall_variable_.push_back(contact_particles_[k]->template getVariableByName(variable_name)); - } - } - //=================================================================================================// - template class BaseDampingPairwiseType> - void DampingPairwiseWithWall:: - Interaction(size_t index_i, Real dt) - { - BaseDampingPairwiseType::Interaction(index_i, dt); - - Real Vol_i = this->Vol_[index_i]; - Real mass_i = this->mass_[index_i]; - VariableType& variable_i = this->variable_[index_i]; - - std::array parameter_b; - - /** Contact interaction. */ - for (size_t k = 0; k < this->contact_configuration_.size(); ++k) - { - StdLargeVec& Vol_k = *(this->wall_Vol_[k]); - StdLargeVec& variable_k = *(this->wall_variable_[k]); - Neighborhood& contact_neighborhood = (*DissipationDataWithWall::contact_configuration_[k])[index_i]; - //forward sweep - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - - parameter_b[n] = this->eta_ * contact_neighborhood.dW_ij_[n] - * Vol_i * Vol_k[index_j] * dt / contact_neighborhood.r_ij_[n]; - - //only update particle i - this->variable_[index_i] += parameter_b[n] * (variable_i - variable_k[index_j]) - / (mass_i - 2.0 * parameter_b[n]); - } - //backward sweep - for (size_t n = contact_neighborhood.current_size_; n != 0; --n) - { - size_t index_j = contact_neighborhood.j_[n - 1]; - - //only update particle i - this->variable_[index_i] += parameter_b[n - 1] * (variable_i - variable_k[index_j]) - / (mass_i - 2.0 * parameter_b[n - 1]); - } - } - } - //=================================================================================================// - template - template - DampingWithRandomChoice:: - DampingWithRandomChoice(BodyRelationType* body_relation, - Real random_ratio, std::string variable_name, Real eta) : - DampingAlgorithmType(body_relation, variable_name, eta / random_ratio), - random_ratio_(random_ratio) {} - //=================================================================================================// - template - bool DampingWithRandomChoice::RandomChoice() - { - return ((double)rand() / (RAND_MAX)) < random_ratio_ ? true : false; - } - //=================================================================================================// - template - void DampingWithRandomChoice::exec(Real dt) - { - if (RandomChoice()) DampingAlgorithmType::exec(dt); - } - //=================================================================================================// - template - void DampingWithRandomChoice::parallel_exec(Real dt) - { - if (RandomChoice()) DampingAlgorithmType::parallel_exec(dt); - } - //=================================================================================================// -} -#endif //PARTICLE_DYNAMICS_DISSIPATION_HPP \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/electro_physiology/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/electro_physiology/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/electro_physiology/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/electro_physiology/electro_physiology.cpp b/SPHINXsys/src/shared/particle_dynamics/electro_physiology/electro_physiology.cpp deleted file mode 100644 index 2c1f4bdd16..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/electro_physiology/electro_physiology.cpp +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @file electro_physiology.cpp - * @author Chi Zhang and Xiangyu Hu - */ - -#include "electro_physiology.h" - -using namespace SimTK; - -namespace SPH -{ - namespace electro_physiology - { - //=================================================================================================// - ElectroPhysiologyInitialCondition:: - ElectroPhysiologyInitialCondition(SolidBody* body) : - ParticleDynamicsSimple(body), - ElectroPhysiologyDataDelegateSimple(body), - pos_n_(particles_->pos_n_), species_n_(particles_->species_n_) - { - } - //=================================================================================================// - } -} \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/electro_physiology/electro_physiology.h b/SPHINXsys/src/shared/particle_dynamics/electro_physiology/electro_physiology.h deleted file mode 100644 index 7288d752fd..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/electro_physiology/electro_physiology.h +++ /dev/null @@ -1,149 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file eletro_physiology.h - * @brief In is file, we declaim the dynamics relevant to electrophysiology, - * including diffusion, reaction and muscle activation. - * @author Chi Zhang and Xiangyu Hu - */ - -#ifndef ELECTRO_PHYSIOLOGY_H -#define ELECTRO_PHYSIOLOGY_H - - - -#include "all_particle_dynamics.h" -#include "diffusion_reaction_particles.h" -#include "particle_dynamics_diffusion_reaction.h" -#include "diffusion_reaction.h" -#include "elastic_solid.h" -#include "base_kernel.h" -#include "solid_body.h" -#include "solid_particles.h" - -namespace SPH -{ - namespace electro_physiology - { - typedef DiffusionReactionSimpleData ElectroPhysiologyDataDelegateSimple; - typedef DiffusionReactionInnerData ElectroPhysiologyDataDelegateInner; - /** - * @class ElectroPhysiologyInitialCondition - * @brief set initial condition for a muscle body - * This is a abstract class to be override for case specific initial conditions. - */ - class ElectroPhysiologyInitialCondition : - public ParticleDynamicsSimple, - public ElectroPhysiologyDataDelegateSimple - { - public: - ElectroPhysiologyInitialCondition(SolidBody *body); - virtual ~ElectroPhysiologyInitialCondition() {}; - protected: - StdLargeVec& pos_n_; - StdVec>& species_n_; - }; - /** - * @class GetElectroPhysiologyTimeStepSize - * @brief Computing the time step size from diffusion criteria - */ - class GetElectroPhysiologyTimeStepSize - : public GetDiffusionTimeStepSize - { - public: - explicit GetElectroPhysiologyTimeStepSize(SolidBody* body) - : GetDiffusionTimeStepSize(body) {}; - virtual ~GetElectroPhysiologyTimeStepSize() {}; - }; - /** - * @class ElectroPhysiologyDiffusionRelaxationInner - * @brief Compute the diffusion relaxation process - */ - class ElectroPhysiologyDiffusionRelaxationInner : - public RelaxationOfAllDiffusionSpeciesRK2, - BaseBodyRelationInner> - { - public: - ElectroPhysiologyDiffusionRelaxationInner(BaseBodyRelationInner* body_inner_relation) - : RelaxationOfAllDiffusionSpeciesRK2(body_inner_relation) {}; - virtual ~ElectroPhysiologyDiffusionRelaxationInner() {}; - }; - /** - * @class ElectroPhysiologyDiffusionRelaxationComplex - * @brief Compute the diffusion relaxation process - */ - class ElectroPhysiologyDiffusionRelaxationComplex : - public RelaxationOfAllDiffusionSpeciesRK2, - ComplexBodyRelation> - { - public: - ElectroPhysiologyDiffusionRelaxationComplex(ComplexBodyRelation* body_complex_relation) - : RelaxationOfAllDiffusionSpeciesRK2(body_complex_relation) {}; - virtual ~ElectroPhysiologyDiffusionRelaxationComplex() {}; - }; - /** - * @class ElectroPhysiologyReactionRelaxationForward - * @brief Solve the reaction ODE equation of trans-membrane potential - * using forward sweeping - */ - class ElectroPhysiologyReactionRelaxationForward - : public RelaxationOfAllReactionsForward - { - public: - ElectroPhysiologyReactionRelaxationForward(SolidBody* body) - : RelaxationOfAllReactionsForward(body) {}; - virtual ~ElectroPhysiologyReactionRelaxationForward() {}; - }; - /** - * @class ElectroPhysiologyReactionRelaxationForward - * @brief Solve the reaction ODE equation of trans-membrane potential - * using backward sweeping - */ - class ElectroPhysiologyReactionRelaxationBackward - : public RelaxationOfAllReactionsBackward - { - public: - ElectroPhysiologyReactionRelaxationBackward(SolidBody* body) - : RelaxationOfAllReactionsBackward(body) {}; - virtual ~ElectroPhysiologyReactionRelaxationBackward() {}; - }; - /** - * @class ApplyStimulusCurrents - * @brief Apply specific stimulus currents - * This is a abstract class to be override for case specific implementations. - */ - class ApplyStimulusCurrents : - public ParticleDynamicsSimple, - public ElectroPhysiologyDataDelegateSimple - { - public: - ApplyStimulusCurrents(SolidBody *body) : - ParticleDynamicsSimple(body), - ElectroPhysiologyDataDelegateSimple(body) {} - virtual ~ApplyStimulusCurrents() {}; - }; - } -} -#endif //ELECTRO_PHYSIOLOGY_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/external_force/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/external_force/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/external_force/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/external_force/external_force.cpp b/SPHINXsys/src/shared/particle_dynamics/external_force/external_force.cpp deleted file mode 100644 index 8a9a99bc37..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/external_force/external_force.cpp +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @file sexternal_froce.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ -#include "external_force.h" -//=================================================================================================// -namespace SPH { -//=================================================================================================// - ExternalForce::ExternalForce() {} -//=================================================================================================// - Gravity::Gravity(Vecd global_acceleration, Vecd reference_position) - : ExternalForce(), global_acceleration_(global_acceleration), - zero_potential_reference_(reference_position) {} - //=================================================================================================// - Vecd Gravity::InducedAcceleration(Vecd& position) - { - return global_acceleration_; - } - //=================================================================================================// - Real Gravity::getPotential(Vecd& position) - { - return dot(InducedAcceleration(position), zero_potential_reference_ - position); - } - //=================================================================================================// -} -//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/external_force/external_force.h b/SPHINXsys/src/shared/particle_dynamics/external_force/external_force.h deleted file mode 100644 index a493a66a2d..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/external_force/external_force.h +++ /dev/null @@ -1,69 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file external_force.h - * @brief Here, we define the base external force class. - * @details The simple derived classes, such as gravity will be defined in applications. - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#ifndef EXTERNAL_FORCE_H -#define EXTERNAL_FORCE_H - - - -#include "base_data_package.h" - -namespace SPH { - /** - * @class ExternalForce - * @brief This class define external forces. - */ - class ExternalForce - { - public: - ExternalForce(); - virtual ~ExternalForce() {}; - /** This function can be used for runtime control of external force. */ - virtual Vecd InducedAcceleration(Vecd& position) = 0; - }; - - /** - * @class Gravity - * @brief The gravity force, derived class of External force. - */ - class Gravity : public ExternalForce - { - protected: - Vecd global_acceleration_; - Vecd zero_potential_reference_; - public: - Gravity(Vecd gravity_vector, Vecd reference_position = Vecd(0)); - virtual ~Gravity() {}; - - /** This function can be used for runtime control of external force. */ - virtual Vecd InducedAcceleration(Vecd& position) override; - Real getPotential(Vecd& position); - }; -} -#endif //EXTERNAL_FORCE_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/all_fluid_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/all_fluid_dynamics.h deleted file mode 100644 index b415b2f315..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/all_fluid_dynamics.h +++ /dev/null @@ -1,42 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file all_fluid_dynamics.h -* @brief This is the header file that user code should include to pick up all -* fluid dynamics used in SPHinXsys. -* @details The fluid dynamics algorithms begin for fluid bulk without boundary condition, -* then algorithm interacting with wall is defined, further algorithms for multiphase flow interaction -* built upon these basic algorithms. -* @author Chi ZHang and Xiangyu Hu -*/ -#pragma once - -#include "fluid_dynamics_inner.h" -#include "fluid_dynamics_inner.hpp" -#include "fluid_dynamics_complex.h" -#include "fluid_dynamics_complex.hpp" -#include "fluid_dynamics_compound.h" -#include "fluid_dynamics_compound.hpp" -#include "fluid_dynamics_multi_phase.h" -#include "fluid_dynamics_multi_phase.hpp" -#include "all_eulerian_fluid_dynamics.h" diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/all_eulerian_fluid_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/all_eulerian_fluid_dynamics.h deleted file mode 100644 index 67eee894c3..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/all_eulerian_fluid_dynamics.h +++ /dev/null @@ -1,34 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file all_eulerian_fluid_dynamics.h -* @brief This is the header file that user code should include to pick up all eulerian -* fluid dynamics used in SPHinXsys. -* @details The eulerian fluid dynamics algorithms begin for fluid bulk without boundary condition, -* then algorithm interacting with wall is defined. -* @author Zhentong Wang -*/ -#pragma once - -#include "eulerian_fluid_dynamics_inner.h" -#include "eulerian_fluid_dynamics_inner.hpp" \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.cpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.cpp deleted file mode 100644 index 1f1b8c13c6..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @file fluid_dynamics_complex.cpp - * @author Chi ZHang and Xiangyu Hu - */ - -#include "eulerian_fluid_dynamics_complex.h" -#include "in_output.h" -#include "geometry_level_set.h" -//=================================================================================================// -using namespace std; -//=================================================================================================// -namespace SPH -{ -//=================================================================================================// - namespace eulerian_fluid_dynamics - { - } -//=================================================================================================// -} -//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.h b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.h deleted file mode 100644 index a64e5ea844..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.h +++ /dev/null @@ -1,184 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file eulerian_fluid_dynamics_complex.h -* @brief Here, we define the algorithm classes for complex compressible fluid dynamics, -* which is involving with either solid walls (with suffix WithWall) -* or/and other bodies treated as wall for the fluid (with suffix Complex). -* @author Chi ZHang, Xiangyu Hu and Zhentong Wang -*/ - -#pragma once - -#include "eulerian_fluid_dynamics_inner.h" - -namespace SPH -{ - namespace eulerian_fluid_dynamics - { - typedef DataDelegateContact CompressibleFluidWallData; - typedef DataDelegateContact CompressibleFluidContactData; - typedef DataDelegateContact CFSIContactData; //CFSI= Compressible Fluid_Structure Interation - - typedef DataDelegateContact FluidWallData; - typedef DataDelegateContact FluidContactData; - typedef DataDelegateContact FSIContactData; - /** - * @class RelaxationWithWall - * @brief Abstract base class for general relaxation algorithms with wall - */ - template - class RelaxationWithWall : public BaseRelaxationType, public CompressibleFluidWallData - { - public: - template - RelaxationWithWall(BaseBodyRelationType* base_body_relation, - BaseBodyRelationContact* wall_contact_relation); - virtual ~RelaxationWithWall() {}; - protected: - StdVec wall_inv_rho_0_; - StdVec*> wall_mass_, wall_Vol_; - StdVec*> wall_vel_ave_, wall_dvel_dt_ave_, wall_n_; - }; - - /** - * @class ViscousWithWall - * @brief template class viscous acceleration with wall boundary - */ - template - class ViscousWithWall : public RelaxationWithWall - { - public: - // template for different combination of constructing body relations - template - ViscousWithWall(BaseBodyRelationType* base_body_relation, - BaseBodyRelationContact* wall_contact_relation); - virtual ~ViscousWithWall() {}; - protected: - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** template interface class for different pressure relaxation with wall schemes */ - template - class BaseViscousAccelerationWithWall : public BaseViscousAccelerationType - { - public: - BaseViscousAccelerationWithWall(ComplexBodyRelation* fluid_wall_relation); - BaseViscousAccelerationWithWall(BaseBodyRelationInner* fluid_inner_relation, - BaseBodyRelationContact* wall_contact_relation); - BaseViscousAccelerationWithWall(ComplexBodyRelation* fluid_complex_relation, - BaseBodyRelationContact* wall_contact_relation); - }; - using ViscousAccelerationWithWall - = BaseViscousAccelerationWithWall>; - - /** - * @class PressureRelaxation - * @brief template class pressure relaxation scheme with wall boundary - */ - template - class PressureRelaxation : public RelaxationWithWall - { - public: - // template for different combination of constructing body relations - template - PressureRelaxation(BaseBodyRelationType* base_body_relation, - BaseBodyRelationContact* wall_contact_relation); - virtual ~PressureRelaxation() {}; - protected: - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** template interface class for different pressure relaxation with wall schemes */ - template - class BasePressureRelaxationWithWall : public BasePressureRelaxationType - { - public: - BasePressureRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation); - BasePressureRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, - BaseBodyRelationContact* wall_contact_relation); - BasePressureRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, - BaseBodyRelationContact* wall_contact_relation); - }; - using PressureRelaxationHLLCRiemannWithWall - = BasePressureRelaxationWithWall>; - using PressureRelaxationHLLCRiemannAndLimiterWithWall - = BasePressureRelaxationWithWall>; - /** - * @class DensityRelaxation - * @brief template density relaxation scheme without using different Riemann solvers. - * The difference from the free surface version is that no Riemann problem is applied - */ - template - class DensityAndEnergyRelaxation : public RelaxationWithWall - { - public: - // template for different combination of constructing body relations - template - DensityAndEnergyRelaxation(BaseBodyRelationType* base_body_relation, - BaseBodyRelationContact* wall_contact_relation); - virtual ~DensityAndEnergyRelaxation() {}; - protected: - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** template interface class for different density relaxation schemes */ - template - class BaseDensityAndEnergyRelaxationWithWall : public DensityAndEnergyRelaxation - { - public: - BaseDensityAndEnergyRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation); - BaseDensityAndEnergyRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, - BaseBodyRelationContact* wall_contact_relation); - BaseDensityAndEnergyRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, - BaseBodyRelationContact* wall_contact_relation); - }; - using DensityAndEnergyRelaxationHLLCRiemannWithWall = BaseDensityAndEnergyRelaxationWithWall; - using DensityAndEnergyRelaxationHLLCRiemannAndLimiterWithWall = BaseDensityAndEnergyRelaxationWithWall; - - /** - * @class SurfaceNormWithWall - * @brief Modify surface norm when contact with wall - */ - class SurfaceNormWithWall : public InteractionDynamics, public FSIContactData - { - public: - SurfaceNormWithWall(BaseBodyRelationContact* contact_relation, Real contact_angle); - virtual ~SurfaceNormWithWall() {}; - protected: - Real contact_angle_; - Real smoothing_length_; - Real particle_spacing_; - StdVec*> wall_n_; - StdLargeVec* surface_norm_; - StdLargeVec& is_free_surface_; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - } -} diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.hpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.hpp deleted file mode 100644 index 753c694dcc..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.hpp +++ /dev/null @@ -1,232 +0,0 @@ -/** - * @file eulerian_fluid_dynamics_complex.hpp - * @author Chi ZHang and Xiangyu Hu - */ -#include "eulerian_fluid_dynamics_complex.h" - -//=================================================================================================// -namespace SPH -{ -//=================================================================================================// - namespace eulerian_fluid_dynamics - { - //=================================================================================================// - template - template - RelaxationWithWall:: - RelaxationWithWall(BaseBodyRelationType* base_body_relation, - BaseBodyRelationContact* wall_contact_relation) : - BaseRelaxationType(base_body_relation), CompressibleFluidWallData(wall_contact_relation) - { - if (base_body_relation->sph_body_ != wall_contact_relation->sph_body_) - { - std::cout << "\n Error: the two body_realtions do not have the same source body!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - - for (size_t k = 0; k != CompressibleFluidWallData::contact_particles_.size(); ++k) - { - Real rho_0_k = CompressibleFluidWallData::contact_particles_[k]->rho0_; - wall_inv_rho_0_.push_back(1.0 / rho_0_k); - wall_mass_.push_back(&(CompressibleFluidWallData::contact_particles_[k]->mass_)); - wall_Vol_.push_back(&(CompressibleFluidWallData::contact_particles_[k]->Vol_)); - wall_vel_ave_.push_back(&(CompressibleFluidWallData::contact_particles_[k]->vel_ave_)); - wall_dvel_dt_ave_.push_back(&(CompressibleFluidWallData::contact_particles_[k]->dvel_dt_ave_)); - wall_n_.push_back(&(CompressibleFluidWallData::contact_particles_[k]->n_)); - } - } - //=================================================================================================// - template - template - ViscousWithWall:: - ViscousWithWall(BaseBodyRelationType* base_body_relation, - BaseBodyRelationContact* wall_contact_relation) - : RelaxationWithWall(base_body_relation, wall_contact_relation) {} - //=================================================================================================// - template - void ViscousWithWall::Interaction(size_t index_i, Real dt) - { - BaseViscousAccelerationType::Interaction(index_i, dt); - - Real rho_i = this->rho_n_[index_i]; - Vecd& vel_i = this->vel_n_[index_i]; - - Vecd acceleration(0), vel_derivative(0); - for (size_t k = 0; k < CompressibleFluidWallData::contact_configuration_.size(); ++k) - { - StdLargeVec& Vol_k = *(this->wall_Vol_[k]); - StdLargeVec& vel_ave_k = *(this->wall_vel_ave_[k]); - Neighborhood& contact_neighborhood = (*CompressibleFluidWallData::contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - Real r_ij = contact_neighborhood.r_ij_[n]; - - vel_derivative = 2.0*(vel_i - vel_ave_k[index_j]) / (r_ij + 0.01 * this->smoothing_length_); - acceleration += 2.0 * this->mu_ * vel_derivative - * contact_neighborhood.dW_ij_[n] * Vol_k[index_j] / rho_i; - } - } - - this->dvel_dt_prior_[index_i] += acceleration; - this->dE_dt_prior_[index_i] += rho_i * dot(acceleration, vel_n_[index_i]); - } - //=================================================================================================// - template - BaseViscousAccelerationWithWall:: - BaseViscousAccelerationWithWall(ComplexBodyRelation* fluid_wall_relation) : - BaseViscousAccelerationType(fluid_wall_relation->inner_relation_, - fluid_wall_relation->contact_relation_) {} - //=================================================================================================// - template - BaseViscousAccelerationWithWall:: - BaseViscousAccelerationWithWall(BaseBodyRelationInner* fluid_inner_relation, - BaseBodyRelationContact* wall_contact_relation) : - BaseViscousAccelerationType(fluid_inner_relation, - wall_contact_relation) {} - //=================================================================================================// - template - BaseViscousAccelerationWithWall:: - BaseViscousAccelerationWithWall(ComplexBodyRelation* fluid_complex_relation, - BaseBodyRelationContact* wall_contact_relation) : - BaseViscousAccelerationType(fluid_complex_relation, - wall_contact_relation) {} - //=================================================================================================// - template - template - PressureRelaxation:: - PressureRelaxation(BaseBodyRelationType* base_body_relation, - BaseBodyRelationContact* wall_contact_relation) : - RelaxationWithWall(base_body_relation, - wall_contact_relation) {} - //=================================================================================================// - template - void PressureRelaxation::Interaction(size_t index_i, Real dt) - { - BasePressureRelaxationType::Interaction(index_i, dt); - - CompressibleFluidState state_i(this->rho_n_[index_i], this->vel_n_[index_i], this->p_[index_i], this->E_[index_i]); - Vecd acceleration(0.0); - for (size_t k = 0; k < CompressibleFluidWallData::contact_configuration_.size(); ++k) - { - StdLargeVec& Vol_k = *(this->wall_Vol_[k]); - StdLargeVec& vel_ave_k = *(this->wall_vel_ave_[k]); - StdLargeVec& dvel_dt_ave_k = *(this->wall_dvel_dt_ave_[k]); - StdLargeVec& n_k = *(this->wall_n_[k]); - Neighborhood& wall_neighborhood = (*CompressibleFluidWallData::contact_configuration_[k])[index_i]; - for (size_t n = 0; n != wall_neighborhood.current_size_; ++n) - { - size_t index_j = wall_neighborhood.j_[n]; - Vecd& e_ij = wall_neighborhood.e_ij_[n]; - Real dW_ij = wall_neighborhood.dW_ij_[n]; - Real r_ij = wall_neighborhood.r_ij_[n]; - - Vecd vel_in_wall = 2.0 * vel_ave_k[index_j] - state_i.vel_; - Real p_in_wall = state_i.p_; - Real rho_in_wall = state_i.rho_; - Real E_in_wall = state_i.E_; - CompressibleFluidState state_j(rho_in_wall, vel_in_wall, p_in_wall, E_in_wall); - Real p_star = this->riemann_solver_.getPStar(state_i, state_j, n_k[index_j]); - Vecd vel_star = this->riemann_solver_.getVStar(state_i, state_j, n_k[index_j]); - Real rho_star = this->riemann_solver_.getRhoStar(state_i, state_j, n_k[index_j]); - acceleration -= 2.0 * Vol_k[index_j] * - (SimTK::outer(rho_star * vel_star, vel_star) + p_star * Matd(1.0)) * e_ij * dW_ij / state_i.rho_; - } - } - this->dvel_dt_[index_i] += acceleration; - } - //=================================================================================================// - template - BasePressureRelaxationWithWall:: - BasePressureRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation) : - BasePressureRelaxationType(fluid_wall_relation->inner_relation_, - fluid_wall_relation->contact_relation_) {} - //=================================================================================================// - template - BasePressureRelaxationWithWall:: - BasePressureRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, - BaseBodyRelationContact* wall_contact_relation) : - BasePressureRelaxationType(fluid_inner_relation, - wall_contact_relation) {} - //=================================================================================================// - template - BasePressureRelaxationWithWall:: - BasePressureRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, - BaseBodyRelationContact* wall_contact_relation) : - BasePressureRelaxationType(fluid_complex_relation, - wall_contact_relation) {} - //=================================================================================================// - template - template - DensityAndEnergyRelaxation:: - DensityAndEnergyRelaxation(BaseBodyRelationType* base_body_relation, - BaseBodyRelationContact* wall_contact_relation) : - RelaxationWithWall(base_body_relation, wall_contact_relation) {} - //=================================================================================================// - template - void DensityAndEnergyRelaxation::Interaction(size_t index_i, Real dt) - { - BaseDensityAndenergyRelaxationType::Interaction(index_i, dt); - - CompressibleFluidState state_i(this->rho_n_[index_i], this->vel_n_[index_i], this->p_[index_i], this->E_[index_i]); - Real density_change_rate = 0.0; - Real energy_change_rate = 0.0; - for (size_t k = 0; k < CompressibleFluidWallData::contact_configuration_.size(); ++k) - { - //Vecd& dvel_dt_others_i = this->dvel_dt_others_[index_i]; - - StdLargeVec& Vol_k = *(this->wall_Vol_[k]); - StdLargeVec& vel_ave_k = *(this->wall_vel_ave_[k]); - StdLargeVec& dvel_dt_ave_k = *(this->wall_dvel_dt_ave_[k]); - StdLargeVec& n_k = *(this->wall_n_[k]); - Neighborhood& wall_neighborhood = (*CompressibleFluidWallData::contact_configuration_[k])[index_i]; - for (size_t n = 0; n != wall_neighborhood.current_size_; ++n) - { - size_t index_j = wall_neighborhood.j_[n]; - Vecd& e_ij = wall_neighborhood.e_ij_[n]; - Real r_ij = wall_neighborhood.r_ij_[n]; - Real dW_ij = wall_neighborhood.dW_ij_[n]; - - /*Real face_wall_external_acceleration - = dot((dvel_dt_others_i - dvel_dt_ave_k[index_j]), -e_ij);*/ - Vecd vel_in_wall = 2.0 * vel_ave_k[index_j] - state_i.vel_; - Real p_in_wall = state_i.p_; - Real rho_in_wall = state_i.rho_; - Real E_in_wall = state_i.E_; - CompressibleFluidState state_j(rho_in_wall, vel_in_wall, p_in_wall, E_in_wall); - Real p_star = this->riemann_solver_.getPStar(state_i, state_j, n_k[index_j]); - Vecd vel_star = this->riemann_solver_.getVStar(state_i, state_j, n_k[index_j]); - Real rho_star = this->riemann_solver_.getRhoStar(state_i, state_j, n_k[index_j]); - Real E_star = this->riemann_solver_.getEStar(state_i, state_j, n_k[index_j]); - density_change_rate -= 2.0 * Vol_k[index_j] * dot(rho_star * vel_star, e_ij) * dW_ij; - energy_change_rate -= 2.0 * Vol_k[index_j] * dot(E_star * vel_star + p_star * vel_star, e_ij) * dW_ij; - } - } - this->drho_dt_[index_i] += density_change_rate; - this->dE_dt_[index_i] += energy_change_rate; - } - //=================================================================================================// - template - BaseDensityAndEnergyRelaxationWithWall:: - BaseDensityAndEnergyRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation) : - DensityAndEnergyRelaxation(fluid_wall_relation->inner_relation_, - fluid_wall_relation->contact_relation_) {} - //=================================================================================================// - template - BaseDensityAndEnergyRelaxationWithWall:: - BaseDensityAndEnergyRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, - BaseBodyRelationContact* wall_contact_relation) : - DensityAndEnergyRelaxation(fluid_inner_relation, - wall_contact_relation) {} - //=================================================================================================// - template - BaseDensityAndEnergyRelaxationWithWall:: - BaseDensityAndEnergyRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, - BaseBodyRelationContact* wall_contact_relation) : - DensityAndEnergyRelaxation(fluid_complex_relation, wall_contact_relation) {} - //=================================================================================================// - } -//=================================================================================================// -} -//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.cpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.cpp deleted file mode 100644 index 2a652eaee7..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/** - * @file eulerian_fluid_dynamics.cpp - * @author Chi ZHang, Xiangyu Hu and Zhentong Wang - */ - -#include "eulerian_fluid_dynamics_inner.h" - - //=================================================================================================// -using namespace std; -//=================================================================================================// -namespace SPH -{ -//=================================================================================================// - namespace eulerian_fluid_dynamics - { - //=================================================================================================// - CompressibleFlowTimeStepInitialization - ::CompressibleFlowTimeStepInitialization(SPHBody* body, Gravity* gravity) - : ParticleDynamicsSimple(body), CompressibleFluidDataSimple(body), - rho_n_(particles_->rho_n_), dE_dt_prior_(particles_->dE_dt_prior_), pos_n_(particles_->pos_n_), - vel_n_(particles_->vel_n_), dmom_dt_prior_(particles_->dmom_dt_prior_), - gravity_(gravity) - { - } - //=================================================================================================// - void CompressibleFlowTimeStepInitialization::setupDynamics(Real dt) - { - particles_->total_ghost_particles_ = 0; - } - //=================================================================================================// - void CompressibleFlowTimeStepInitialization::Update(size_t index_i, Real dt) - { - dmom_dt_prior_[index_i] = rho_n_[index_i] * gravity_->InducedAcceleration(pos_n_[index_i]); - dE_dt_prior_[index_i] = rho_n_[index_i] * SimTK::dot(gravity_->InducedAcceleration(pos_n_[index_i]), vel_n_[index_i]); - } - //=================================================================================================// - CompressibleFluidInitialCondition:: - CompressibleFluidInitialCondition(EulerianFluidBody* body) - : ParticleDynamicsSimple(body), CompressibleFluidDataSimple(body), - pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_), mom_(particles_->mom_), - rho_n_(particles_->rho_n_), E_(particles_->E_), p_(particles_->p_) - { - gamma_ = material_->HeatCapacityRatio(); - c0_ = material_->ReferenceSoundSpeed(); - rho0_ = material_->ReferenceDensity(); - } - //=================================================================================================// - ViscousAccelerationInner::ViscousAccelerationInner(BaseBodyRelationInner* inner_relation) : - InteractionDynamics(inner_relation->sph_body_), - CompressibleFluidDataInner(inner_relation), - Vol_(particles_->Vol_), rho_n_(particles_->rho_n_), p_(particles_->p_), - mass_(particles_->mass_), dE_dt_prior_(particles_->dE_dt_prior_), - vel_n_(particles_->vel_n_), dmom_dt_prior_(particles_->dmom_dt_prior_) - { - smoothing_length_ = particle_adaptation_->ReferenceSmoothingLength(); - mu_ = material_->ReferenceViscosity(); - } - //=================================================================================================// - void ViscousAccelerationInner::Interaction(size_t index_i, Real dt) - { - Real rho_i = rho_n_[index_i]; - Vecd& vel_i = vel_n_[index_i]; - - Vecd acceleration(0), vel_derivative(0); - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - - //viscous force - vel_derivative = (vel_i - vel_n_[index_j]) - / (inner_neighborhood.r_ij_[n] + 0.01 * smoothing_length_); - acceleration += 2.0 * mu_ * vel_derivative - * Vol_[index_j] * inner_neighborhood.dW_ij_[n] / rho_i; - } - dmom_dt_prior_[index_i] += rho_n_[index_i] * acceleration; - dE_dt_prior_[index_i] += rho_n_[index_i] * dot(acceleration, vel_n_[index_i]); - } - //=================================================================================================// - AcousticTimeStepSize::AcousticTimeStepSize(EulerianFluidBody* body) - : ParticleDynamicsReduce(body), - CompressibleFluidDataSimple(body), rho_n_(particles_->rho_n_), - p_(particles_->p_), vel_n_(particles_->vel_n_) - { - smoothing_length_ = particle_adaptation_->ReferenceSmoothingLength(); - initial_reference_ = 0.0; - } - //=================================================================================================// - Real AcousticTimeStepSize::ReduceFunction(size_t index_i, Real dt) - { - return material_->getSoundSpeed(p_[index_i], rho_n_[index_i]) + vel_n_[index_i].norm(); - } - //=================================================================================================// - Real AcousticTimeStepSize::OutputResult(Real reduced_value) - { - particles_->signal_speed_max_ = reduced_value; - //since the particle does not change its configuration in pressure relaxation step - //I chose a time-step size according to Eulerian method - return 0.6 * smoothing_length_ / (reduced_value + TinyReal); - } - //=================================================================================================// - BaseRelaxation::BaseRelaxation(BaseBodyRelationInner* inner_relation) : - ParticleDynamics1Level(inner_relation->sph_body_), - CompressibleFluidDataInner(inner_relation), - Vol_(particles_->Vol_), rho_n_(particles_->rho_n_),p_(particles_->p_), - drho_dt_(particles_->drho_dt_), E_(particles_->E_), dE_dt_(particles_->dE_dt_), - dE_dt_prior_(particles_->dE_dt_prior_), - vel_n_(particles_->vel_n_), mom_(particles_->mom_), - dmom_dt_(particles_->dmom_dt_), dmom_dt_prior_(particles_->dmom_dt_prior_) {} - //=================================================================================================// - BasePressureRelaxation:: - BasePressureRelaxation(BaseBodyRelationInner* inner_relation) : - BaseRelaxation(inner_relation) {} - //=================================================================================================// - void BasePressureRelaxation::Initialization(size_t index_i, Real dt) - { - E_[index_i] += dE_dt_[index_i] * dt * 0.5; - rho_n_[index_i] += drho_dt_[index_i] * dt * 0.5; - Real rho_e = E_[index_i] - 0.5 * mom_[index_i].normSqr() / rho_n_[index_i]; - p_[index_i] = material_->getPressure(rho_n_[index_i], rho_e); - } - //=================================================================================================// - void BasePressureRelaxation::Update(size_t index_i, Real dt) - { - mom_[index_i] += dmom_dt_[index_i] * dt; - vel_n_[index_i] = mom_[index_i] / rho_n_[index_i]; - } - //=================================================================================================// - BaseDensityAndEnergyRelaxation:: - BaseDensityAndEnergyRelaxation(BaseBodyRelationInner* inner_relation) : - BaseRelaxation(inner_relation) {} - //=================================================================================================// - void BaseDensityAndEnergyRelaxation::Update(size_t index_i, Real dt) - { - E_[index_i] += dE_dt_[index_i] * dt * 0.5; - rho_n_[index_i] += drho_dt_[index_i] * dt * 0.5; - } - //=================================================================================================// - } -//=================================================================================================// -} -//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.h b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.h deleted file mode 100644 index 0f7578b0b4..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.h +++ /dev/null @@ -1,188 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file eulerian_fluid_dynamics_inner.h -* @brief Here, we define the algorithm classes for fluid dynamics within the body. -* @details We consider here compressible fluids. -* @author Chi ZHang, Xiangyu Hu and Zhentong Wang -*/ - -#pragma once - -#include "all_particle_dynamics.h" -#include "base_kernel.h" -#include "riemann_solver.h" - -namespace SPH -{ - namespace eulerian_fluid_dynamics - { - typedef DataDelegateSimple CompressibleFluidDataSimple; - typedef DataDelegateInner CompressibleFluidDataInner; - - class CompressibleFlowTimeStepInitialization - : public ParticleDynamicsSimple, public CompressibleFluidDataSimple - { - public: - CompressibleFlowTimeStepInitialization(SPHBody* body, Gravity* gravity = new Gravity(Vecd(0))); - virtual ~CompressibleFlowTimeStepInitialization() {}; - protected: - StdLargeVec& rho_n_, & dE_dt_prior_; - StdLargeVec& pos_n_, & vel_n_, & dmom_dt_prior_; - Gravity* gravity_; - virtual void setupDynamics(Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class CompressibleFluidInitialCondition - * @brief Set initial condition for a fluid body. - * This is a abstract class to be override for case specific initial conditions - */ - class CompressibleFluidInitialCondition - : public ParticleDynamicsSimple, public CompressibleFluidDataSimple - { - public: - CompressibleFluidInitialCondition(EulerianFluidBody* body); - virtual ~CompressibleFluidInitialCondition() {}; - protected: - StdLargeVec& pos_n_, & vel_n_, & mom_; - StdLargeVec& rho_n_, & E_, & p_; - Real gamma_, c0_, rho0_; - }; - - /** - * @class ViscousAccelerationInner - * @brief the viscosity force induced acceleration - */ - class ViscousAccelerationInner - : public InteractionDynamics, public CompressibleFluidDataInner - { - public: - ViscousAccelerationInner(BaseBodyRelationInner* inner_relation); - virtual ~ViscousAccelerationInner() {}; - protected: - Real mu_; - Real smoothing_length_; - StdLargeVec& Vol_, & rho_n_, & p_, & mass_, & dE_dt_prior_; - StdLargeVec& vel_n_, & dmom_dt_prior_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class AcousticTimeStepSize - * @brief Computing the acoustic time step size - */ - class AcousticTimeStepSize : - public ParticleDynamicsReduce, public CompressibleFluidDataSimple - { - public: - explicit AcousticTimeStepSize(EulerianFluidBody* body); - virtual ~AcousticTimeStepSize() {}; - protected: - StdLargeVec& rho_n_, & p_; - StdLargeVec& vel_n_; - Real smoothing_length_; - Real ReduceFunction(size_t index_i, Real dt = 0.0) override; - Real OutputResult(Real reduced_value) override; - }; - - /** - * @class BaseRelaxation - * @brief Pure abstract base class for all fluid relaxation schemes - */ - class BaseRelaxation : public ParticleDynamics1Level, public CompressibleFluidDataInner - { - public: - BaseRelaxation(BaseBodyRelationInner* inner_relation); - virtual ~BaseRelaxation() {}; - protected: - StdLargeVec& Vol_, & rho_n_, & p_, & drho_dt_, & E_, & dE_dt_, & dE_dt_prior_; - StdLargeVec& vel_n_, & mom_, & dmom_dt_, & dmom_dt_prior_; - }; - - /** - * @class BasePressureRelaxation - * @brief Abstract base class for all pressure relaxation schemes - */ - class BasePressureRelaxation : public BaseRelaxation - { - public: - BasePressureRelaxation(BaseBodyRelationInner* inner_relation); - virtual ~BasePressureRelaxation() {}; - protected: - virtual void Initialization(size_t index_i, Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class BasePressureRelaxationInner - * @brief Template class for pressure relaxation scheme with the Riemann solver - * as template variable - */ - template - class BasePressureRelaxationInner : public BasePressureRelaxation - { - public: - BasePressureRelaxationInner(BaseBodyRelationInner* inner_relation); - virtual ~BasePressureRelaxationInner() {}; - RiemannSolverType riemann_solver_; - protected: - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - using PressureRelaxationHLLCRiemannInner = BasePressureRelaxationInner; - using PressureRelaxationHLLCWithLimiterRiemannInner = BasePressureRelaxationInner; - - /** - * @class BaseDensityAndEnergyRelaxation - * @brief Abstract base class for all density relaxation schemes - */ - class BaseDensityAndEnergyRelaxation : public BaseRelaxation - { - public: - BaseDensityAndEnergyRelaxation(BaseBodyRelationInner* inner_relation); - virtual ~BaseDensityAndEnergyRelaxation() {}; - protected: - virtual void Initialization(size_t index_i, Real dt = 0.0) override {}; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class BaseDensityAndEnergyRelaxationInner - * @brief Template density relaxation scheme in HLLC Riemann solver with and without limiter - */ - template - class BaseDensityAndEnergyRelaxationInner : public BaseDensityAndEnergyRelaxation - { - public: - BaseDensityAndEnergyRelaxationInner(BaseBodyRelationInner* inner_relation); - virtual ~BaseDensityAndEnergyRelaxationInner() {}; - RiemannSolverType riemann_solver_; - protected: - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - using DensityAndEnergyRelaxationHLLCRiemannInner = BaseDensityAndEnergyRelaxationInner; - using DensityAndEnergyRelaxationHLLCWithLimiterRiemannInner = BaseDensityAndEnergyRelaxationInner; - } -} diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.hpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.hpp deleted file mode 100644 index 173563421f..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.hpp +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @file eulerian_fluid_dynamics_inner.hpp - * @author Chi ZHang, Xiangyu Hu and Zhentong Wang - */ - -#include "eulerian_fluid_dynamics_inner.h" - -//=================================================================================================// -namespace SPH -{ -//=================================================================================================// - namespace eulerian_fluid_dynamics - { - //=================================================================================================// - template - BasePressureRelaxationInner:: - BasePressureRelaxationInner(BaseBodyRelationInner* inner_relation) : - BasePressureRelaxation(inner_relation), - riemann_solver_(*material_, *material_) {} - //=================================================================================================// - template - void BasePressureRelaxationInner::Interaction(size_t index_i, Real dt) - { - CompressibleFluidState state_i(rho_n_[index_i], vel_n_[index_i], p_[index_i], E_[index_i]); - Vecd momentum_change_rate = dmom_dt_prior_[index_i]; - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - Real dW_ij = inner_neighborhood.dW_ij_[n]; - Vecd& e_ij = inner_neighborhood.e_ij_[n]; - - CompressibleFluidState state_j(rho_n_[index_j], vel_n_[index_j], p_[index_j], E_[index_j]); - Real p_star = riemann_solver_.getPStar(state_i, state_j, e_ij); - Vecd vel_star = riemann_solver_.getVStar(state_i, state_j, e_ij); - Real rho_star = riemann_solver_.getRhoStar(state_i, state_j, e_ij); - - momentum_change_rate -= 2.0 * Vol_[index_j] * - (SimTK::outer(rho_star * vel_star, vel_star) + p_star * Matd(1.0)) * e_ij * dW_ij; - } - dmom_dt_[index_i] = momentum_change_rate; - } - //=================================================================================================// - template - BaseDensityAndEnergyRelaxationInner:: - BaseDensityAndEnergyRelaxationInner(BaseBodyRelationInner* inner_relation) : - BaseDensityAndEnergyRelaxation(inner_relation), - riemann_solver_(*material_, *material_) {} - //=================================================================================================// - template - void BaseDensityAndEnergyRelaxationInner::Interaction(size_t index_i, Real dt) - { - CompressibleFluidState state_i(rho_n_[index_i], vel_n_[index_i], p_[index_i], E_[index_i]); - Real density_change_rate = 0.0; - Real energy_change_rate = dE_dt_prior_[index_i]; - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - Vecd& e_ij = inner_neighborhood.e_ij_[n]; - Real dW_ij = inner_neighborhood.dW_ij_[n]; - - CompressibleFluidState state_j(rho_n_[index_j], vel_n_[index_j], p_[index_j], E_[index_j]); - Vecd vel_star = riemann_solver_.getVStar(state_i, state_j, e_ij); - Real p_star = riemann_solver_.getPStar(state_i, state_j, e_ij); - Real rho_star = riemann_solver_.getRhoStar(state_i, state_j, e_ij); - Real E_star = riemann_solver_.getEStar(state_i, state_j, e_ij); - - density_change_rate -= 2.0 * Vol_[index_j] * dot(rho_star * vel_star, e_ij) * dW_ij; - energy_change_rate -= 2.0 * Vol_[index_j] * dot(E_star * vel_star + p_star * vel_star, e_ij) * dW_ij; - } - drho_dt_[index_i] = density_change_rate; - dE_dt_[index_i] = energy_change_rate; - }; - //=================================================================================================// - } -//=================================================================================================// -} -//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.cpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.cpp deleted file mode 100644 index 46fd953986..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.cpp +++ /dev/null @@ -1,299 +0,0 @@ -/** - * @file fluid_dynamics_complex.cpp - * @author Chi ZHang and Xiangyu Hu - */ - -#include "fluid_dynamics_complex.h" -#include "in_output.h" -#include "geometry_level_set.h" - -namespace SPH -{ -//=================================================================================================// - namespace fluid_dynamics - { - //=================================================================================================// - FreeSurfaceIndicationComplex::FreeSurfaceIndicationComplex(BaseBodyRelationInner* inner_relation, - BaseBodyRelationContact* conatct_relation, Real thereshold) - : FreeSurfaceIndicationInner(inner_relation, thereshold), FluidContactData(conatct_relation) - { - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - Real rho0_k = contact_particles_[k]->rho0_; - contact_inv_rho0_.push_back(1.0 / rho0_k); - contact_mass_.push_back(&(contact_particles_[k]->mass_)); - } - } - //=================================================================================================// - FreeSurfaceIndicationComplex::FreeSurfaceIndicationComplex(ComplexBodyRelation* body_complex_relation, - Real thereshold) - : FreeSurfaceIndicationComplex(body_complex_relation->inner_relation_, - body_complex_relation->contact_relation_, thereshold){} - //=================================================================================================// - void FreeSurfaceIndicationComplex::Interaction(size_t index_i, Real dt) - { - FreeSurfaceIndicationInner::Interaction(index_i, dt); - - Real pos_div = 0.0; - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - StdLargeVec& contact_mass_k = *(contact_mass_[k]); - Real contact_inv_rho0_k = contact_inv_rho0_[k]; - Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - pos_div -= contact_neighborhood.dW_ij_[n] * contact_neighborhood.r_ij_[n] - * contact_inv_rho0_k * contact_mass_k[contact_neighborhood.j_[n]]; - } - } - pos_div_[index_i] += pos_div; - } - //=================================================================================================// - SpatialTemporalFreeSurfaceIdentificationComplex::SpatialTemporalFreeSurfaceIdentificationComplex - (BaseBodyRelationInner* inner_relation,BaseBodyRelationContact* conatct_relation, Real thereshold) - : FreeSurfaceIndicationComplex(inner_relation, conatct_relation, thereshold), - particle_spacing_(body_->particle_adaptation_->ReferenceSpacing()), - previous_surface_indicator_(*particles_->createAVariable("PreviousSurfaceIndicator")) - { - particles_->registerASortableVariable("PreviousSurfaceIndicator"); - for (size_t n = 0; n != particles_->total_real_particles_; ++n) - previous_surface_indicator_[n] = 1; - } - //=================================================================================================// - SpatialTemporalFreeSurfaceIdentificationComplex::SpatialTemporalFreeSurfaceIdentificationComplex - (ComplexBodyRelation* body_complex_relation,Real thereshold) - : SpatialTemporalFreeSurfaceIdentificationComplex(body_complex_relation->inner_relation_, - body_complex_relation->contact_relation_, thereshold) {} - //=================================================================================================// - void SpatialTemporalFreeSurfaceIdentificationComplex::Interaction(size_t index_i, Real dt) - { - FreeSurfaceIndicationInner::Interaction(index_i, dt); - - Real pos_div = 0.0; - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - StdLargeVec& contact_mass_k = *(contact_mass_[k]); - Real contact_inv_rho_0_k = contact_inv_rho0_[k]; - Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - pos_div -= contact_neighborhood.dW_ij_[n] * contact_neighborhood.r_ij_[n] - * contact_inv_rho_0_k * contact_mass_k[contact_neighborhood.j_[n]]; - } - } - pos_div_[index_i] += pos_div; - - surface_indicator_[index_i] = 0; - if (pos_div_[index_i] < thereshold_by_dimensions_) - { - if (previous_surface_indicator_[index_i] == 1) surface_indicator_[index_i] = 1; - else - { - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - if (previous_surface_indicator_[inner_neighborhood.j_[n]] == 1 || - previous_surface_indicator_[inner_neighborhood.j_[n]] == 2 ) - surface_indicator_[index_i] = 1; - } - } - } - } - //=================================================================================================// - void SpatialTemporalFreeSurfaceIdentificationComplex::Update(size_t index_i, Real dt) - { - previous_surface_indicator_[index_i] = surface_indicator_[index_i]; - } - //=================================================================================================// - TransportVelocityCorrectionComplex:: - TransportVelocityCorrectionComplex(ComplexBodyRelation* body_complex_relation) : - TransportVelocityCorrectionComplex(body_complex_relation->inner_relation_, - body_complex_relation->contact_relation_) {} - //=================================================================================================// - TransportVelocityCorrectionComplex:: - TransportVelocityCorrectionComplex(ComplexBodyRelation* complex_relation, - BaseBodyRelationContact* extra_conatct_relation) : - ParticleDynamicsComplex( - complex_relation, extra_conatct_relation) - { - prepareContactData(); - } - //=================================================================================================// - void TransportVelocityCorrectionComplex::prepareContactData() - { - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); - } - } - //=================================================================================================// - void TransportVelocityCorrectionComplex::Interaction(size_t index_i, Real dt) - { - TransportVelocityCorrectionInner::Interaction(index_i, dt); - - Real rho_i = rho_n_[index_i]; - - Vecd acceleration_trans(0); - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - StdLargeVec& Vol_k = *(contact_Vol_[k]); - Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - Vecd nablaW_ij = contact_neighborhood.dW_ij_[n] * contact_neighborhood.e_ij_[n]; - - //acceleration for transport velocity - acceleration_trans -= 2.0 * p_background_ * Vol_k[index_j] * nablaW_ij / rho_i; - } - } - - /** correcting particle position */ - if (surface_indicator_[index_i] == 0)pos_n_[index_i] += acceleration_trans * dt * dt * 0.5; - } - //=================================================================================================// - void PressureRelaxationRiemannWithWallOldroyd_B::Interaction(size_t index_i, Real dt) - { - PressureRelaxation::Interaction(index_i, dt); - - Real rho_i = rho_n_[index_i]; - Matd tau_i = tau_[index_i]; - - Vecd acceleration(0); - for (size_t k = 0; k < FluidWallData::contact_configuration_.size(); ++k) - { - StdLargeVec& Vol_k = *(wall_Vol_[k]); - Neighborhood& wall_neighborhood = (*FluidWallData::contact_configuration_[k])[index_i]; - for (size_t n = 0; n != wall_neighborhood.current_size_; ++n) - { - size_t index_j = wall_neighborhood.j_[n]; - Vecd nablaW_ij = wall_neighborhood.dW_ij_[n] * wall_neighborhood.e_ij_[n]; - /** stress boundary condition. */ - acceleration += 2.0 * tau_i * nablaW_ij * Vol_k[index_j] / rho_i; - } - } - - dvel_dt_[index_i] += acceleration; - } - //=================================================================================================// - void DensityRelaxationRiemannWithWallOldroyd_B::Interaction(size_t index_i, Real dt) - { - DensityRelaxation::Interaction(index_i, dt); - - Vecd vel_i = vel_n_[index_i]; - Matd tau_i = tau_[index_i]; - - Matd stress_rate(0); - for (size_t k = 0; k < FluidWallData::contact_configuration_.size(); ++k) - { - StdLargeVec& Vol_k = *(wall_Vol_[k]); - StdLargeVec& vel_ave_k = *(wall_vel_ave_[k]); - Neighborhood& wall_neighborhood = (*FluidWallData::contact_configuration_[k])[index_i]; - for (size_t n = 0; n != wall_neighborhood.current_size_; ++n) - { - size_t index_j = wall_neighborhood.j_[n]; - Vecd nablaW_ij = wall_neighborhood.dW_ij_[n] * wall_neighborhood.e_ij_[n]; - - Matd velocity_gradient = -SimTK::outer((vel_i - vel_ave_k[index_j]), nablaW_ij) * Vol_k[index_j] * 2.0; - stress_rate += ~velocity_gradient * tau_i + tau_i * velocity_gradient - - tau_i / lambda_ + (~velocity_gradient + velocity_gradient) * mu_p_ / lambda_; - } - } - dtau_dt_[index_i] += stress_rate; - } - //=================================================================================================// - ColorFunctionGradientComplex::ColorFunctionGradientComplex(BaseBodyRelationInner* inner_relation, - BaseBodyRelationContact* conatct_relation) - : ColorFunctionGradientInner(inner_relation), FluidContactData(conatct_relation) - { - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); - } - } - //=================================================================================================// - ColorFunctionGradientComplex::ColorFunctionGradientComplex(ComplexBodyRelation* body_complex_relation) - : ColorFunctionGradientComplex(body_complex_relation->inner_relation_, - body_complex_relation->contact_relation_){} - //=================================================================================================// - void ColorFunctionGradientComplex::Interaction(size_t index_i, Real dt) - { - ColorFunctionGradientInner::Interaction(index_i, dt); - - Vecd gradient(0.0); - if (pos_div_[index_i] < thereshold_by_dimensions_) - { - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - StdLargeVec& contact_vol_k = *(contact_Vol_[k]); - Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - gradient -= contact_neighborhood.dW_ij_[n] * contact_neighborhood.e_ij_[n] - * contact_vol_k[contact_neighborhood.j_[n]]; - } - } - } - color_grad_[index_i] += gradient; - surface_norm_[index_i] = color_grad_[index_i] / (color_grad_[index_i].norm() + TinyReal); - } - //=================================================================================================// - SurfaceNormWithWall::SurfaceNormWithWall(BaseBodyRelationContact* contact_relation, Real contact_angle) - : InteractionDynamics(contact_relation->sph_body_), FSIContactData(contact_relation), - contact_angle_(contact_angle), - surface_indicator_(particles_->surface_indicator_), - surface_norm_(*particles_->getVariableByName("SurfaceNormal")), - pos_div_(*particles_->getVariableByName("PositionDivergence")) - { - particle_spacing_ = contact_relation->sph_body_->particle_adaptation_->ReferenceSpacing(); - smoothing_length_ = contact_relation->sph_body_->particle_adaptation_->ReferenceSmoothingLength(); - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - wall_n_.push_back(&(contact_particles_[k]->n_)); - } - } - //=================================================================================================// - void SurfaceNormWithWall::Interaction(size_t index_i, Real dt) - { - Real large_dist(1.0e6); - Vecd n_i = surface_norm_[index_i]; - Real smoothing_factor(1.0); - Vecd smooth_norm(0); - Vecd n_i_w(0); - /** Contact interaction. */ - if(surface_indicator_[index_i] == 1) - { - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - StdLargeVec& n_k = *(wall_n_[k]); - Neighborhood& wall_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != wall_neighborhood.current_size_; ++n) - { - size_t index_j = wall_neighborhood.j_[n]; - if(wall_neighborhood.r_ij_[n] < large_dist) - { - Vecd n_w_t = n_i - dot(n_i, n_k[index_j]) * n_k[index_j]; - Vecd n_t = n_w_t / (n_w_t.norm() + TinyReal); - n_i_w = n_t * sin(contact_angle_) + cos(contact_angle_) * n_k[index_j]; - /** No change for multi-resolution. */ - Real r_ij = wall_neighborhood.r_ij_[n] * dot(n_k[index_j], wall_neighborhood.e_ij_[n]); - if(r_ij <= smoothing_length_) - { - smoothing_factor = 0.0; - }else - { - smoothing_factor = (r_ij - smoothing_length_) / smoothing_length_; - } - large_dist = wall_neighborhood.r_ij_[n]; - smooth_norm = smoothing_factor * n_i + (1.0 - smoothing_factor) * n_i_w; - surface_norm_[index_i] = smooth_norm / (smooth_norm.norm() + TinyReal); - } - } - } - } - } - //=================================================================================================// - } -//=================================================================================================// -} -//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.h b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.h deleted file mode 100644 index 23ea05742a..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.h +++ /dev/null @@ -1,365 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file fluid_dynamics_complex.h -* @brief Here, we define the algorithm classes for complex fluid dynamics, -* which is involving with either solid walls (with suffix WithWall) -* or/and other bodies treated as wall for the fluid (with suffix Complex). -* @author Chi ZHang and Xiangyu Hu -*/ - - -#ifndef FLUID_DYNAMCIS_COMPLEX_H -#define FLUID_DYNAMCIS_COMPLEX_H - - - -#include "fluid_dynamics_inner.h" - -namespace SPH -{ - namespace fluid_dynamics - { - typedef DataDelegateContact FluidWallData; - typedef DataDelegateContact FluidContactData; - typedef DataDelegateContact FSIContactData; - /** - * @class RelaxationWithWall - * @brief Abstract base class for general relaxation algorithms with wall - */ - template - class RelaxationWithWall : public BaseRelaxationType, public FluidWallData - { - public: - template - RelaxationWithWall(BaseBodyRelationType* base_body_relation, - BaseBodyRelationContact* wall_contact_relation); - virtual ~RelaxationWithWall() {}; - protected: - StdVec wall_inv_rho0_; - StdVec*> wall_mass_, wall_Vol_; - StdVec*> wall_vel_ave_, wall_dvel_dt_ave_, wall_n_; - }; - - /** - * @class FreeSurfaceIndicationComplex - * @brief indicate the particles near the free fluid surface. - */ - class FreeSurfaceIndicationComplex : public FreeSurfaceIndicationInner, public FluidContactData - { - public: - FreeSurfaceIndicationComplex(BaseBodyRelationInner* inner_relation, - BaseBodyRelationContact* contact_relation, Real thereshold = 0.75); - FreeSurfaceIndicationComplex(ComplexBodyRelation* body_complex_relation, Real thereshold = 0.75); - virtual ~FreeSurfaceIndicationComplex() {}; - protected: - StdVec contact_inv_rho0_; - StdVec*> contact_mass_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class Spatial-temporalFreeSurfaceIdentificationComplex - * @brief using the spatial-temporal method to indicate the surface particles without misjudgement. - * @brief the indicator index of 1st layer surface particles is set to 1. - * @brief the indicator index of internal fluid particles is set to 0. - */ - class SpatialTemporalFreeSurfaceIdentificationComplex : public FreeSurfaceIndicationComplex - { - public: - SpatialTemporalFreeSurfaceIdentificationComplex(BaseBodyRelationInner* inner_relation, - BaseBodyRelationContact* contact_relation, Real thereshold = 0.75); - SpatialTemporalFreeSurfaceIdentificationComplex(ComplexBodyRelation* body_complex_relation, - Real thereshold = 0.75); - virtual ~SpatialTemporalFreeSurfaceIdentificationComplex() {}; - protected: - Real particle_spacing_; - StdLargeVec& previous_surface_indicator_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - - - /** - * @class DensitySummation - * @brief computing density by summation considering contribution from contact bodies - */ - template - class DensitySummation : public ParticleDynamicsComplex - { - public: - DensitySummation(BaseBodyRelationInner* inner_relation, BaseBodyRelationContact* contact_relation); - DensitySummation(ComplexBodyRelation* body_complex_relation); - DensitySummation(ComplexBodyRelation* complex_relation, BaseBodyRelationContact* extra_contact_relation); - virtual ~DensitySummation() {}; - protected: - StdVec contact_inv_rho0_; - StdVec*> contact_mass_; - - virtual void prepareContactData() override; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - /** the case without free surface */ - using DensitySummationComplex = DensitySummation; - /** the case with free surface */ - using DensitySummationFreeSurfaceComplex = DensitySummation; - /** the case with free stream */ - using DensitySummationFreeStreamComplex = DensitySummation; - - /** - * @class ViscousWithWall - * @brief template class viscous acceleration with wall boundary - */ - template - class ViscousWithWall : public RelaxationWithWall - { - public: - // template for different combination of constructing body relations - template - ViscousWithWall(BaseBodyRelationType* base_body_relation, - BaseBodyRelationContact* wall_contact_relation); - virtual ~ViscousWithWall() {}; - protected: - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** template interface class for different pressure relaxation with wall schemes */ - template - class BaseViscousAccelerationWithWall : public BaseViscousAccelerationType - { - public: - BaseViscousAccelerationWithWall(ComplexBodyRelation* fluid_wall_relation); - BaseViscousAccelerationWithWall(BaseBodyRelationInner* fluid_inner_relation, - BaseBodyRelationContact* wall_contact_relation); - BaseViscousAccelerationWithWall(ComplexBodyRelation* fluid_complex_relation, - BaseBodyRelationContact* wall_contact_relation); - }; - using ViscousAccelerationWithWall - = BaseViscousAccelerationWithWall>; - /** - * @class TransportVelocityCorrectionComplex - * @brief transport velocity correction consdiering the contribution from contact bodies - */ - class TransportVelocityCorrectionComplex - : public ParticleDynamicsComplex - { - public: - TransportVelocityCorrectionComplex(BaseBodyRelationInner* inner_relation, - BaseBodyRelationContact* conatct_relation); - - TransportVelocityCorrectionComplex(ComplexBodyRelation* body_complex_relation); - - TransportVelocityCorrectionComplex(ComplexBodyRelation* complex_relation, - BaseBodyRelationContact* extra_conatct_relation); - virtual ~TransportVelocityCorrectionComplex() {}; - protected: - StdVec*> contact_Vol_; - - virtual void prepareContactData() override; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class PressureRelaxation - * @brief template class pressure relaxation scheme with wall boundary - */ - template - class PressureRelaxation : public RelaxationWithWall - { - public: - // template for different combination of constructing body relations - template - PressureRelaxation(BaseBodyRelationType* base_body_relation, - BaseBodyRelationContact* wall_contact_relation); - virtual ~PressureRelaxation() {}; - protected: - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - virtual Vecd computeNonConservativeAcceleration(size_t index_i) override; - }; - - /** - * @class ExtendPressureRelaxation - * @brief template class for pressure relaxation scheme with wall boundary - * and considering non-conservative acceleration term and wall penalty to prevent - * particle penetration. - */ - template - class ExtendPressureRelaxation : public PressureRelaxation - { - public: - // template for different combination of constructing body relations - template - ExtendPressureRelaxation(BaseBodyRelationType* base_body_relation, - BaseBodyRelationContact* wall_contact_relation, Real penalty_strength = 1.0); - - virtual ~ExtendPressureRelaxation() {}; - protected: - Real penalty_strength_; - StdLargeVec& non_cnsrv_dvel_dt_; - - virtual void Initialization(size_t index_i, Real dt = 0.0) override; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - virtual Vecd computeNonConservativeAcceleration(size_t index_i) override; - }; - - /** template interface class for different pressure relaxation with wall schemes */ - template - class BasePressureRelaxationWithWall : public BasePressureRelaxationType - { - public: - BasePressureRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation); - BasePressureRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, - BaseBodyRelationContact* wall_contact_relation); - BasePressureRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, - BaseBodyRelationContact* wall_contact_relation); - }; - using PressureRelaxationWithWall - = BasePressureRelaxationWithWall>; - using PressureRelaxationRiemannWithWall - = BasePressureRelaxationWithWall>; - - /** template interface class for the extended pressure relaxation with wall schemes */ - template - class ExtendPressureRelaxationWithWall : public BasePressureRelaxationType - { - public: - ExtendPressureRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation, Real penalty_strength = 1.0); - ExtendPressureRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, - BaseBodyRelationContact* wall_contact_relation, Real penalty_strength = 1.0); - ExtendPressureRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, - BaseBodyRelationContact* wall_contact_relation, Real penalty_strength = 1.0); - }; - using ExtendPressureRelaxationRiemannWithWall - = ExtendPressureRelaxationWithWall>; - - /** - * @class DensityRelaxation - * @brief template density relaxation scheme without using different Riemann solvers. - * The difference from the free surface version is that no Riemann problem is applied - */ - template - class DensityRelaxation : public RelaxationWithWall - { - public: - // template for different combination of constructing body relations - template - DensityRelaxation(BaseBodyRelationType* base_body_relation, - BaseBodyRelationContact* wall_contact_relation); - virtual ~DensityRelaxation() {}; - protected: - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** template interface class for different density relaxation schemes */ - template - class BaseDensityRelaxationWithWall : public DensityRelaxation - { - public: - BaseDensityRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation); - BaseDensityRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, - BaseBodyRelationContact* wall_contact_relation); - BaseDensityRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, - BaseBodyRelationContact* wall_contact_relation); - }; - using DensityRelaxationWithWall = BaseDensityRelaxationWithWall; - using DensityRelaxationRiemannWithWall = BaseDensityRelaxationWithWall; - - /** - * @class PressureRelaxationRiemannWithWallOldroyd_B - * @brief first half of the pressure relaxation scheme using Riemann solver. - */ - class PressureRelaxationRiemannWithWallOldroyd_B : - public PressureRelaxation - { - public: - PressureRelaxationRiemannWithWallOldroyd_B(ComplexBodyRelation* fluid_wall_relation) : - PressureRelaxation(fluid_wall_relation->inner_relation_, - fluid_wall_relation->contact_relation_) {}; - - virtual ~PressureRelaxationRiemannWithWallOldroyd_B() {}; - protected: - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class DensityRelaxationRiemannWithWallOldroyd_B - * @brief second half of the pressure relaxation scheme using Riemann solver. - */ - class DensityRelaxationRiemannWithWallOldroyd_B : - public DensityRelaxation - { - public: - DensityRelaxationRiemannWithWallOldroyd_B(ComplexBodyRelation* fluid_wall_relation) : - DensityRelaxation(fluid_wall_relation->inner_relation_, - fluid_wall_relation->contact_relation_) {}; - - virtual ~DensityRelaxationRiemannWithWallOldroyd_B() {}; - protected: - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class ColorFunctionGradientComplex - * @brief indicate the particles near the free fluid surface. - */ - class ColorFunctionGradientComplex : public ColorFunctionGradientInner, public FluidContactData - { - public: - ColorFunctionGradientComplex(BaseBodyRelationInner* inner_relation, BaseBodyRelationContact* contact_relation); - ColorFunctionGradientComplex(ComplexBodyRelation* body_complex_relation); - virtual ~ColorFunctionGradientComplex() {}; - protected: - StdVec*> contact_Vol_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class SurfaceNormWithWall - * @brief Modify surface norm when contact with wall - */ - class SurfaceNormWithWall : public InteractionDynamics, public FSIContactData - { - public: - SurfaceNormWithWall(BaseBodyRelationContact* contact_relation, Real contact_angle); - virtual ~SurfaceNormWithWall() {}; - protected: - Real contact_angle_; - Real smoothing_length_; - Real particle_spacing_; - StdLargeVec& surface_indicator_; - StdLargeVec& surface_norm_; - StdLargeVec& pos_div_; - StdVec*> wall_n_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - } -} -#endif //FLUID_DYNAMCIS_COMPLEX_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.hpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.hpp deleted file mode 100644 index a07c6e742f..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.hpp +++ /dev/null @@ -1,368 +0,0 @@ -/** - * @file fluid_dynamics_complex.hpp - * @author Chi ZHang and Xiangyu Hu - */ - -#pragma once - -#include "fluid_dynamics_complex.h" - -//=================================================================================================// -namespace SPH -{ -//=================================================================================================// - namespace fluid_dynamics - { - //=================================================================================================// - template - template - RelaxationWithWall:: - RelaxationWithWall(BaseBodyRelationType* base_body_relation, - BaseBodyRelationContact* wall_contact_relation) : - BaseRelaxationType(base_body_relation), FluidWallData(wall_contact_relation) - { - if (base_body_relation->sph_body_ != wall_contact_relation->sph_body_) - { - std::cout << "\n Error: the two body_realtions do not have the same source body!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - - for (size_t k = 0; k != FluidWallData::contact_particles_.size(); ++k) - { - Real rho0_k = FluidWallData::contact_particles_[k]->rho0_; - wall_inv_rho0_.push_back(1.0 / rho0_k); - wall_mass_.push_back(&(FluidWallData::contact_particles_[k]->mass_)); - wall_Vol_.push_back(&(FluidWallData::contact_particles_[k]->Vol_)); - wall_vel_ave_.push_back(&(FluidWallData::contact_particles_[k]->vel_ave_)); - wall_dvel_dt_ave_.push_back(&(FluidWallData::contact_particles_[k]->dvel_dt_ave_)); - wall_n_.push_back(&(FluidWallData::contact_particles_[k]->n_)); - } - } - //=================================================================================================// - template - DensitySummation:: - DensitySummation(BaseBodyRelationInner* inner_relation, BaseBodyRelationContact* contact_relation) : - ParticleDynamicsComplex(inner_relation, contact_relation) - { - prepareContactData(); - } - //=================================================================================================// - template - DensitySummation:: - DensitySummation(ComplexBodyRelation* body_complex_relation) : - DensitySummation(body_complex_relation->inner_relation_, - body_complex_relation->contact_relation_) {} - //=================================================================================================// - template - DensitySummation:: - DensitySummation(ComplexBodyRelation* complex_relation, BaseBodyRelationContact* extra_contact_relation) : - ParticleDynamicsComplex(complex_relation, extra_contact_relation) - { - prepareContactData(); - } - //=================================================================================================// - template - void DensitySummation::prepareContactData() - { - for (size_t k = 0; k != this->contact_particles_.size(); ++k) { - Real rho0_k = this->contact_particles_[k]->rho0_; - contact_inv_rho0_.push_back(1.0 / rho0_k); - contact_mass_.push_back(&(this->contact_particles_[k]->mass_)); - } - } - //=================================================================================================// - template - void DensitySummation::Interaction(size_t index_i, Real dt) - { - DensitySummationInnerType::Interaction(index_i, dt); - - /** Contact interaction. */ - Real sigma(0.0); - Real inv_Vol_0_i = this->rho0_ / this->mass_[index_i]; - for (size_t k = 0; k < this->contact_configuration_.size(); ++k) - { - StdLargeVec& contact_mass_k = *(this->contact_mass_[k]); - Real contact_inv_rho0_k = contact_inv_rho0_[k]; - Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - sigma += contact_neighborhood.W_ij_[n] * inv_Vol_0_i - * contact_inv_rho0_k * contact_mass_k[contact_neighborhood.j_[n]]; - } - } - this->rho_sum_[index_i] += sigma * this->rho0_ * this->inv_sigma0_; - } - //=================================================================================================// - template - template - ViscousWithWall:: - ViscousWithWall(BaseBodyRelationType* base_body_relation, - BaseBodyRelationContact* wall_contact_relation) - : RelaxationWithWall(base_body_relation, wall_contact_relation) {} - //=================================================================================================// - template - void ViscousWithWall::Interaction(size_t index_i, Real dt) - { - BaseViscousAccelerationType::Interaction(index_i, dt); - - Real rho_i = this->rho_n_[index_i]; - Vecd& vel_i = this->vel_n_[index_i]; - - Vecd acceleration(0), vel_derivative(0); - for (size_t k = 0; k < FluidWallData::contact_configuration_.size(); ++k) - { - StdLargeVec& Vol_k = *(this->wall_Vol_[k]); - StdLargeVec& vel_ave_k = *(this->wall_vel_ave_[k]); - Neighborhood& contact_neighborhood = (*FluidWallData::contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - Real r_ij = contact_neighborhood.r_ij_[n]; - - vel_derivative = 2.0*(vel_i - vel_ave_k[index_j]) / (r_ij + 0.01 * this->smoothing_length_); - acceleration += 2.0 * this->mu_ * vel_derivative - * contact_neighborhood.dW_ij_[n] * Vol_k[index_j] / rho_i; - } - } - - this->dvel_dt_prior_[index_i] += acceleration; - } - //=================================================================================================// - template - BaseViscousAccelerationWithWall:: - BaseViscousAccelerationWithWall(ComplexBodyRelation* fluid_wall_relation) : - BaseViscousAccelerationType(fluid_wall_relation->inner_relation_, - fluid_wall_relation->contact_relation_) {} - //=================================================================================================// - template - BaseViscousAccelerationWithWall:: - BaseViscousAccelerationWithWall(BaseBodyRelationInner* fluid_inner_relation, - BaseBodyRelationContact* wall_contact_relation) : - BaseViscousAccelerationType(fluid_inner_relation, - wall_contact_relation) {} - //=================================================================================================// - template - BaseViscousAccelerationWithWall:: - BaseViscousAccelerationWithWall(ComplexBodyRelation* fluid_complex_relation, - BaseBodyRelationContact* wall_contact_relation) : - BaseViscousAccelerationType(fluid_complex_relation, - wall_contact_relation) {} - //=================================================================================================// - template - template - PressureRelaxation:: - PressureRelaxation(BaseBodyRelationType* base_body_relation, - BaseBodyRelationContact* wall_contact_relation) : - RelaxationWithWall(base_body_relation, - wall_contact_relation) {} - //=================================================================================================// - template - void PressureRelaxation::Interaction(size_t index_i, Real dt) - { - BasePressureRelaxationType::Interaction(index_i, dt); - - FluidState state_i(this->rho_n_[index_i], this->vel_n_[index_i], this->p_[index_i]); - Vecd dvel_dt_prior_i = computeNonConservativeAcceleration(index_i); - - Vecd acceleration(0.0); - for (size_t k = 0; k < FluidWallData::contact_configuration_.size(); ++k) - { - StdLargeVec& Vol_k = *(this->wall_Vol_[k]); - StdLargeVec& vel_ave_k = *(this->wall_vel_ave_[k]); - StdLargeVec& dvel_dt_ave_k = *(this->wall_dvel_dt_ave_[k]); - StdLargeVec& n_k = *(this->wall_n_[k]); - Neighborhood& wall_neighborhood = (*FluidWallData::contact_configuration_[k])[index_i]; - for (size_t n = 0; n != wall_neighborhood.current_size_; ++n) - { - size_t index_j = wall_neighborhood.j_[n]; - Vecd& e_ij = wall_neighborhood.e_ij_[n]; - Real dW_ij = wall_neighborhood.dW_ij_[n]; - Real r_ij = wall_neighborhood.r_ij_[n]; - - Real face_wall_external_acceleration = dot((dvel_dt_prior_i - dvel_dt_ave_k[index_j]), -e_ij); - Vecd vel_in_wall = 2.0 * vel_ave_k[index_j] - state_i.vel_; - Real p_in_wall = state_i.p_ + state_i.rho_ * r_ij * SMAX(0.0, face_wall_external_acceleration); - Real rho_in_wall = this->material_->DensityFromPressure(p_in_wall); - FluidState state_j(rho_in_wall, vel_in_wall, p_in_wall); - Real p_star = this->riemann_solver_.getPStar(state_i, state_j, n_k[index_j]); - acceleration -= 2.0 * p_star * e_ij * Vol_k[index_j] * dW_ij / state_i.rho_; - } - } - this->dvel_dt_[index_i] += acceleration; - } - //=================================================================================================// - template - Vecd PressureRelaxation::computeNonConservativeAcceleration(size_t index_i) - { - return this->dvel_dt_prior_[index_i]; - } - //=================================================================================================// - template - template - ExtendPressureRelaxation:: - ExtendPressureRelaxation(BaseBodyRelationType* base_body_relation, - BaseBodyRelationContact* wall_contact_relation, Real penalty_strength) : - PressureRelaxation(base_body_relation, wall_contact_relation), - penalty_strength_(penalty_strength), - non_cnsrv_dvel_dt_(*(this->particles_->template createAVariable("NonConservativeAcceleration"))) {} - //=================================================================================================// - template - void ExtendPressureRelaxation::Initialization(size_t index_i, Real dt) - { - BasePressureRelaxationType::Initialization(index_i, dt); - non_cnsrv_dvel_dt_[index_i] = Vecd(0); - } - //=================================================================================================// - template - void ExtendPressureRelaxation::Interaction(size_t index_i, Real dt) - { - PressureRelaxation::Interaction(index_i, dt); - - Real rho_i = this->rho_n_[index_i]; - Real penalty_pressure = fabs(this->p_[index_i]); - Vecd acceleration(0); - for (size_t k = 0; k < FluidWallData::contact_configuration_.size(); ++k) - { - Real particle_spacing_j1 = 1.0 / FluidWallData::contact_bodies_[k]->particle_adaptation_->ReferenceSpacing(); - Real particle_spacing_ratio2 = 1.0 / (this->body_->particle_adaptation_->ReferenceSpacing() * particle_spacing_j1); - particle_spacing_ratio2 *= 0.1 * particle_spacing_ratio2; - - StdLargeVec& Vol_k = *(this->wall_Vol_[k]); - StdLargeVec& n_k = *(this->wall_n_[k]); - Neighborhood& wall_neighborhood = (*FluidWallData::contact_configuration_[k])[index_i]; - for (size_t n = 0; n != wall_neighborhood.current_size_; ++n) - { - size_t index_j = wall_neighborhood.j_[n]; - Vecd& e_ij = wall_neighborhood.e_ij_[n]; - Real dW_ij = wall_neighborhood.dW_ij_[n]; - Real r_ij = wall_neighborhood.r_ij_[n]; - Vecd& n_j = n_k[index_j]; - - /** penalty method to prevent particle running into boundary */ - Real projection = dot(e_ij, n_j); - Real delta = 2.0 * projection * r_ij * particle_spacing_j1; - Real beta = delta < 1.0 ? (1.0 - delta) * (1.0 - delta) * particle_spacing_ratio2 : 0.0; - Real penalty = penalty_strength_ * beta * projection * penalty_pressure; - - //pressure force - acceleration -= 2.0 * penalty * n_j * Vol_k[index_j] * dW_ij / rho_i; - } - } - this->dvel_dt_[index_i] += acceleration; - } - //=================================================================================================// - template - Vecd ExtendPressureRelaxation::computeNonConservativeAcceleration(size_t index_i) - { - Vecd acceleration = BasePressureRelaxationType::computeNonConservativeAcceleration(index_i); - non_cnsrv_dvel_dt_[index_i] = acceleration; - return acceleration; - } - //=================================================================================================// - template - BasePressureRelaxationWithWall:: - BasePressureRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation) : - BasePressureRelaxationType(fluid_wall_relation->inner_relation_, - fluid_wall_relation->contact_relation_) {} - //=================================================================================================// - template - BasePressureRelaxationWithWall:: - BasePressureRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, - BaseBodyRelationContact* wall_contact_relation) : - BasePressureRelaxationType(fluid_inner_relation, - wall_contact_relation) {} - //=================================================================================================// - template - BasePressureRelaxationWithWall:: - BasePressureRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, - BaseBodyRelationContact* wall_contact_relation) : - BasePressureRelaxationType(fluid_complex_relation, - wall_contact_relation) {} - //=================================================================================================// - template - ExtendPressureRelaxationWithWall:: - ExtendPressureRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation, Real penalty_strength) : - BasePressureRelaxationType(fluid_wall_relation->inner_relation_, - fluid_wall_relation->contact_relation_, penalty_strength) {} - //=================================================================================================// - template - ExtendPressureRelaxationWithWall:: - ExtendPressureRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, - BaseBodyRelationContact* wall_contact_relation, Real penalty_strength) : - BasePressureRelaxationType(fluid_inner_relation, - wall_contact_relation, penalty_strength) {} - //=================================================================================================// - template - ExtendPressureRelaxationWithWall:: - ExtendPressureRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, - BaseBodyRelationContact* wall_contact_relation, Real penalty_strength) : - BasePressureRelaxationType(fluid_complex_relation, - wall_contact_relation, penalty_strength) {} - //=================================================================================================// - template - template - DensityRelaxation:: - DensityRelaxation(BaseBodyRelationType* base_body_relation, - BaseBodyRelationContact* wall_contact_relation) : - RelaxationWithWall(base_body_relation, wall_contact_relation) {} - //=================================================================================================// - template - void DensityRelaxation::Interaction(size_t index_i, Real dt) - { - BaseDensityRelaxationType::Interaction(index_i, dt); - - FluidState state_i(this->rho_n_[index_i], this->vel_n_[index_i], this->p_[index_i]); - Real density_change_rate = 0.0; - for (size_t k = 0; k < FluidWallData::contact_configuration_.size(); ++k) - { - Vecd& dvel_dt_prior_i = this->dvel_dt_prior_[index_i]; - - StdLargeVec& Vol_k = *(this->wall_Vol_[k]); - StdLargeVec& vel_ave_k = *(this->wall_vel_ave_[k]); - StdLargeVec& dvel_dt_ave_k = *(this->wall_dvel_dt_ave_[k]); - StdLargeVec& n_k = *(this->wall_n_[k]); - Neighborhood& wall_neighborhood = (*FluidWallData::contact_configuration_[k])[index_i]; - for (size_t n = 0; n != wall_neighborhood.current_size_; ++n) - { - size_t index_j = wall_neighborhood.j_[n]; - Vecd& e_ij = wall_neighborhood.e_ij_[n]; - Real r_ij = wall_neighborhood.r_ij_[n]; - Real dW_ij = wall_neighborhood.dW_ij_[n]; - - Real face_wall_external_acceleration - = dot((dvel_dt_prior_i - dvel_dt_ave_k[index_j]), -e_ij); - Vecd vel_in_wall = 2.0 * vel_ave_k[index_j] - state_i.vel_; - Real p_in_wall = state_i.p_ + state_i.rho_ * r_ij * SMAX(0.0, face_wall_external_acceleration); - Real rho_in_wall = this->material_->DensityFromPressure(p_in_wall); - FluidState state_j(rho_in_wall, vel_in_wall, p_in_wall); - Vecd vel_star = this->riemann_solver_.getVStar(state_i, state_j, n_k[index_j]); - density_change_rate += 2.0 * state_i.rho_ * Vol_k[index_j] * dot(state_i.vel_ - vel_star, e_ij) * dW_ij; - } - } - this->drho_dt_[index_i] += density_change_rate; - } - //=================================================================================================// - template - BaseDensityRelaxationWithWall:: - BaseDensityRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation) : - DensityRelaxation(fluid_wall_relation->inner_relation_, - fluid_wall_relation->contact_relation_) {} - //=================================================================================================// - template - BaseDensityRelaxationWithWall:: - BaseDensityRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, - BaseBodyRelationContact* wall_contact_relation) : - DensityRelaxation(fluid_inner_relation, - wall_contact_relation) {} - //=================================================================================================// - template - BaseDensityRelaxationWithWall:: - BaseDensityRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, - BaseBodyRelationContact* wall_contact_relation) : - DensityRelaxation(fluid_complex_relation, wall_contact_relation) {} - //=================================================================================================// - } -//=================================================================================================// -} -//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.cpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.cpp deleted file mode 100644 index 1e5a52b18f..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.cpp +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @file fluid_dynamics_compound.cpp - * @author Chi ZHang and Xiangyu Hu - */ - -#include "fluid_dynamics_compound.h" -#include "in_output.h" -#include "geometry_level_set.h" - -namespace SPH -{ -//=================================================================================================// - namespace fluid_dynamics - { - - } -} -//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.h b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.h deleted file mode 100644 index c195698f52..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.h +++ /dev/null @@ -1,103 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file fluid_dynamics_compound.h -* @brief Here, we define the algorithm classes for compound fluid dynamics, -* which is involving with fluid dynamcis inner and fluid dynamics complex. -* @author Chi ZHang and Xiangyu Hu -*/ - - -#ifndef FLUID_DYNAMCIS_COMPOUND_H -#define FLUID_DYNAMCIS_COMPOUND_H - -#include "fluid_dynamics_complex.h" - -namespace SPH -{ - namespace fluid_dynamics - { - /** - * @class SurfaceParticlesIndicator - * @brief this compound class contains SpatialTemporalFreeSurfaceIdentificationComplex method, - * @brief MultilayeredSurfaceParticlesIdentification method and FreeStreamInletOutletSurfaceParticleIdentification method. - * @brief SpatialTemporalFreeSurfaceIdentificationComplex method detect first layer suface particles. - * @brief MultilayeredSurfaceParticlesIdentification method detect 2nd and 3rd layer surface particles. - * @brief FreeStreamInletOutletSurfaceParticleIdentification method can detect inlet and outlet surface particles seperately. - * @brief you can also combine these three subclasses according to your detailed case. - */ - class SurfaceParticlesIndicator - { - protected: - /** - * @class FirstyLayerSurfaceParticleIndicator - * @brief detect first layer suface particles. - */ - class FirstyLayerSurfaceParticleIndicator : public SpatialTemporalFreeSurfaceIdentificationComplex - { - public: - FirstyLayerSurfaceParticleIndicator(BaseBodyRelationInner* inner_relation, - BaseBodyRelationContact* contact_relation, Real thereshold = 0.75) : - SpatialTemporalFreeSurfaceIdentificationComplex(inner_relation, contact_relation, thereshold) {}; - FirstyLayerSurfaceParticleIndicator(ComplexBodyRelation* body_complex_relation, Real thereshold = 0.75) : - FirstyLayerSurfaceParticleIndicator(body_complex_relation->inner_relation_, - body_complex_relation->contact_relation_, thereshold) {}; - virtual ~FirstyLayerSurfaceParticleIndicator() {}; - }; - - /** - * @class SecondAndThirdLayersSurfaceParticlesIndicator - * @brief detect 2nd and 3rd layer surface particles at inlet and outlet. - */ - class SecondAndThirdLayersSurfaceParticlesIndicator : public MultilayeredSurfaceParticlesIdentification - { - public: - SecondAndThirdLayersSurfaceParticlesIndicator(BaseBodyRelationInner* inner_relation) : - MultilayeredSurfaceParticlesIdentification(inner_relation) {}; - virtual ~SecondAndThirdLayersSurfaceParticlesIndicator() {}; - }; - - /** - * @class InletOutletSurfaceParticleIndicator - * @brief detect inlet and outlet surface particles independantly. - */ - class InletOutletSurfaceParticleIndicator : public FreeStreamInletOutletSurfaceParticleIdentification - { - public: - InletOutletSurfaceParticleIndicator(BaseBodyRelationInner* inner_relation, int axis_direction) : - FreeStreamInletOutletSurfaceParticleIdentification(inner_relation, axis_direction) {}; - virtual ~InletOutletSurfaceParticleIndicator() {}; - }; - - public: - SurfaceParticlesIndicator(ComplexBodyRelation* body_complex_relation, BaseBodyRelationInner* inner_relation, int axis_direction = 0) : - first_layer(body_complex_relation), second_and_third_layers(inner_relation), inlet_outlet_layers(inner_relation, axis_direction) {}; - virtual ~SurfaceParticlesIndicator() {}; - - FirstyLayerSurfaceParticleIndicator first_layer; - SecondAndThirdLayersSurfaceParticlesIndicator second_and_third_layers; - InletOutletSurfaceParticleIndicator inlet_outlet_layers; - }; - } -} -#endif //FLUID_DYNAMCIS_COMPOUND_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.hpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.hpp deleted file mode 100644 index d1c26f997e..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.hpp +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @file fluid_dynamics_compound.hpp - * @author Chi ZHang and Xiangyu Hu - */ - -#pragma once - -#include "fluid_dynamics_compound.h" - -//=================================================================================================// -namespace SPH -{ -//=================================================================================================// - namespace fluid_dynamics - { - - //=================================================================================================// - } -//=================================================================================================// -} -//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.cpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.cpp deleted file mode 100644 index 13aaa8c2fa..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.cpp +++ /dev/null @@ -1,771 +0,0 @@ -/** - * @file fluid_dynamics.cpp - * @author Chi ZHang and Xiangyu Hu - */ - -#include "fluid_dynamics_inner.h" -#include "in_output.h" -#include "geometry_level_set.h" - -namespace SPH -{ -//=================================================================================================// - namespace fluid_dynamics - { - //=================================================================================================// - FluidInitialCondition:: - FluidInitialCondition(FluidBody* body) - : ParticleDynamicsSimple(body), FluidDataSimple(body), - pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_) - { - } - //=================================================================================================// - FreeSurfaceIndicationInner:: - FreeSurfaceIndicationInner(BaseBodyRelationInner* inner_relation, Real thereshold) : - InteractionDynamicsWithUpdate(inner_relation->sph_body_), - FluidDataInner(inner_relation), - thereshold_by_dimensions_(thereshold*(Real)Dimensions), - Vol_(particles_->Vol_), - pos_div_(*particles_->createAVariable("PositionDivergence")), - surface_indicator_(particles_->surface_indicator_) - { - smoothing_length_ = inner_relation->sph_body_->particle_adaptation_->ReferenceSmoothingLength(); - } - //=================================================================================================// - void FreeSurfaceIndicationInner::Interaction(size_t index_i, Real dt) - { - Real pos_div = 0.0; - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - pos_div -= inner_neighborhood.dW_ij_[n] - * inner_neighborhood.r_ij_[n] * Vol_[inner_neighborhood.j_[n]]; - } - pos_div_[index_i] = pos_div; - } - //=================================================================================================// - void FreeSurfaceIndicationInner::Update(size_t index_i, Real dt) - { - bool is_free_surface = pos_div_[index_i] < thereshold_by_dimensions_ ? true : false; - - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - /** Two layer particles.*/ - if (pos_div_[inner_neighborhood.j_[n]] < thereshold_by_dimensions_ && inner_neighborhood.r_ij_[n] < smoothing_length_) - { - is_free_surface = true; - break; - } - } - surface_indicator_[index_i] = is_free_surface ? 1 : 0; - } - //=================================================================================================// - MultilayeredSurfaceParticlesIdentification:: - MultilayeredSurfaceParticlesIdentification(BaseBodyRelationInner* inner_relation) : - InteractionDynamicsWithUpdate(inner_relation->sph_body_), - FluidDataInner(inner_relation),pos_n_(particles_->pos_n_), - surface_indicator_(particles_->surface_indicator_), - previous_surface_indicator_(*particles_->createAVariable("PreviousSurfaceIndicator")) {} - //=================================================================================================// - void MultilayeredSurfaceParticlesIdentification::Interaction(size_t index_i, Real dt) - { - if (surface_indicator_[index_i] != 1) - { - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - if (surface_indicator_[inner_neighborhood.j_[n]] == 1) - { - surface_indicator_[index_i] = 2; - break; - } - } - } - } - //=================================================================================================// - void MultilayeredSurfaceParticlesIdentification::Update(size_t index_i, Real dt) - { - if (surface_indicator_[index_i] == 2)surface_indicator_[index_i] = 1; - previous_surface_indicator_[index_i] = surface_indicator_[index_i]; - } - //=================================================================================================// - FreeStreamInletOutletSurfaceParticleIdentification :: - FreeStreamInletOutletSurfaceParticleIdentification(BaseBodyRelationInner* inner_relation, - int axis_direction) :ParticleDynamicsSimple(inner_relation->sph_body_), - FluidDataInner(inner_relation), pos_n_(particles_->pos_n_), - fluid_body_domain_bounds_(body_->getBodyDomainBounds()), axis_(axis_direction), - particle_spacing_(body_->particle_adaptation_->ReferenceSpacing()), - surface_indicator_(particles_->surface_indicator_), - previous_surface_indicator_(*particles_->createAVariable("PreviousSurfaceIndicator")) {} - //=================================================================================================// - void FreeStreamInletOutletSurfaceParticleIdentification::Update(size_t index_i, Real dt) - { - if (surface_indicator_[index_i] == 1) - { - Real distance_to_inlet_bound = pos_n_[index_i][axis_] - fluid_body_domain_bounds_.first[axis_]; - Real distance_to_outlet_bound = fluid_body_domain_bounds_.second[axis_] - pos_n_[index_i][axis_]; - - if (distance_to_inlet_bound < 3.5 * particle_spacing_ || distance_to_outlet_bound < 3.5 * particle_spacing_) - surface_indicator_[index_i] = 2; - } - previous_surface_indicator_[index_i] = surface_indicator_[index_i]; - } - //=================================================================================================// - TransportVelocityCorrectionComplex:: - TransportVelocityCorrectionComplex(BaseBodyRelationInner* inner_relation, - BaseBodyRelationContact* conatct_relation) : - ParticleDynamicsComplex( - inner_relation, conatct_relation) - { - prepareContactData(); - } - //=================================================================================================// - DensitySummationInner::DensitySummationInner(BaseBodyRelationInner* inner_relation) : - InteractionDynamicsWithUpdate(inner_relation->sph_body_), - FluidDataInner(inner_relation), - Vol_(particles_->Vol_), rho_n_(particles_->rho_n_), mass_(particles_->mass_), - rho_sum_(particles_->rho_sum_) - { - W0_ = particle_adaptation_->getKernel()->W0(Vecd(0)); - rho0_ = particles_->rho0_; - inv_sigma0_ = 1.0 / particles_->sigma0_; - } - //=================================================================================================// - void DensitySummationInner::Interaction(size_t index_i, Real dt) - { - /** Inner interaction. */ - Real sigma = W0_; - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - sigma += inner_neighborhood.W_ij_[n]; - - rho_sum_[index_i] = sigma * rho0_ * inv_sigma0_; - } - //=================================================================================================// - void DensitySummationInner::Update(size_t index_i, Real dt) - { - rho_n_[index_i] = ReinitializedDensity(rho_sum_[index_i], rho0_, rho_n_[index_i]); - Vol_[index_i] = mass_[index_i] / rho_n_[index_i]; - } - //=================================================================================================// - void DensitySummationFreeStreamInner::Update(size_t index_i, Real dt) - { - if (surface_indicator_[index_i] == 1 || surface_indicator_[index_i] == 2) - rho_n_[index_i] = ReinitializedDensity(rho_sum_[index_i], rho0_, rho_n_[index_i]); - else - rho_n_[index_i] = rho_sum_[index_i]; - - Vol_[index_i] = mass_[index_i] / rho_n_[index_i]; - } - //=================================================================================================// - ViscousAccelerationInner::ViscousAccelerationInner(BaseBodyRelationInner* inner_relation) : - InteractionDynamics(inner_relation->sph_body_), - FluidDataInner(inner_relation), - Vol_(particles_->Vol_), rho_n_(particles_->rho_n_), p_(particles_->p_), - vel_n_(particles_->vel_n_), dvel_dt_prior_(particles_->dvel_dt_prior_) - { - mu_ = material_->ReferenceViscosity(); - smoothing_length_ = particle_adaptation_->ReferenceSmoothingLength(); - } - //=================================================================================================// - void ViscousAccelerationInner::Interaction(size_t index_i, Real dt) - { - Real rho_i = rho_n_[index_i]; - Vecd& vel_i = vel_n_[index_i]; - - Vecd acceleration(0), vel_derivative(0); - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - - //viscous force - vel_derivative = (vel_i - vel_n_[index_j]) - / (inner_neighborhood.r_ij_[n] + 0.01 * smoothing_length_); - acceleration += 2.0 * mu_ * vel_derivative - * Vol_[index_j] * inner_neighborhood.dW_ij_[n] / rho_i; - } - - dvel_dt_prior_[index_i] += acceleration; - } - //=================================================================================================// - void AngularConservativeViscousAccelerationInner::Interaction(size_t index_i, Real dt) - { - Real rho_i = rho_n_[index_i]; - Vecd& vel_i = vel_n_[index_i]; - - Vecd acceleration(0); - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - Vecd& e_ij = inner_neighborhood.e_ij_[n]; - Real r_ij = inner_neighborhood.r_ij_[n]; - - /** The following viscous force is given in Monaghan 2005 (Rep. Prog. Phys.), it seems that - * this formulation is more accurate than the previous one for Taylor-Green-Vortex flow. */ - Real v_r_ij = dot(vel_i - vel_n_[index_j], r_ij * e_ij); - Real eta_ij = 8.0 * mu_ * v_r_ij / (r_ij * r_ij + 0.01 * smoothing_length_); - acceleration += eta_ij * Vol_[index_j] / rho_i - * inner_neighborhood.dW_ij_[n] * e_ij; - } - - dvel_dt_prior_[index_i] += acceleration; - } - //=================================================================================================// - TransportVelocityCorrectionInner:: - TransportVelocityCorrectionInner(BaseBodyRelationInner* inner_relation) : - InteractionDynamics(inner_relation->sph_body_), - FluidDataInner(inner_relation), - Vol_(particles_->Vol_), rho_n_(particles_->rho_n_), - pos_n_(particles_->pos_n_), - surface_indicator_(particles_->surface_indicator_), p_background_(0){} - //=================================================================================================// - void TransportVelocityCorrectionInner::setupDynamics(Real dt) - { - Real speed_max = particles_->speed_max_; - Real density = material_->ReferenceDensity(); - p_background_ = 7.0 * density * speed_max * speed_max; - } - //=================================================================================================// - void TransportVelocityCorrectionInner::Interaction(size_t index_i, Real dt) - { - Real rho_i = rho_n_[index_i]; - - Vecd acceleration_trans(0); - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - Vecd nablaW_ij = inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]; - - //acceleration for transport velocity - acceleration_trans -= 2.0 * p_background_*Vol_[index_j] * nablaW_ij / rho_i; - } - - if (surface_indicator_[index_i] == 0) pos_n_[index_i] += acceleration_trans * dt * dt * 0.5; - } - //=================================================================================================// - AcousticTimeStepSize::AcousticTimeStepSize(FluidBody* body) - : ParticleDynamicsReduce(body), - FluidDataSimple(body), rho_n_(particles_->rho_n_), - p_(particles_->p_), vel_n_(particles_->vel_n_) - { - smoothing_length_ = particle_adaptation_->ReferenceSmoothingLength(); - initial_reference_ = 0.0; - } - //=================================================================================================// - Real AcousticTimeStepSize::ReduceFunction(size_t index_i, Real dt) - { - return material_->getSoundSpeed(p_[index_i], rho_n_[index_i]) + vel_n_[index_i].norm(); - } - //=================================================================================================// - Real AcousticTimeStepSize::OutputResult(Real reduced_value) - { - particles_->signal_speed_max_ = reduced_value; - //since the particle does not change its configuration in pressure relaxation step - //I chose a time-step size according to Eulerian method - return 0.6 * smoothing_length_ / (reduced_value + TinyReal); - } - //=================================================================================================// - AdvectionTimeStepSize::AdvectionTimeStepSize(FluidBody* body, Real U_max) - : ParticleDynamicsReduce(body), - FluidDataSimple(body), vel_n_(particles_->vel_n_) - { - smoothing_length_ = particle_adaptation_->ReferenceSmoothingLength(); - Real rho_0 = material_->ReferenceDensity(); - Real mu = material_->ReferenceViscosity(); - Real viscous_speed = mu / rho_0 / smoothing_length_; - Real u_max = SMAX(viscous_speed, U_max); - initial_reference_ = u_max * u_max; - } - //=================================================================================================// - Real AdvectionTimeStepSize::ReduceFunction(size_t index_i, Real dt) - { - return vel_n_[index_i].normSqr(); - } - //=================================================================================================// - Real AdvectionTimeStepSize::OutputResult(Real reduced_value) - { - Real speed_max = sqrt(reduced_value); - particles_->speed_max_ = speed_max; - return 0.25 * smoothing_length_ / (speed_max + TinyReal); - } - //=================================================================================================// - AdvectionTimeStepSizeForImplicitViscosity:: - AdvectionTimeStepSizeForImplicitViscosity(FluidBody* body, Real U_max) - : AdvectionTimeStepSize(body, U_max) - { - initial_reference_ = U_max * U_max; - } - //=================================================================================================// - VorticityInner:: - VorticityInner(BaseBodyRelationInner* body_inner_relation) : - InteractionDynamics(body_inner_relation->sph_body_), - FluidDataInner(body_inner_relation), - Vol_(particles_->Vol_), vel_n_(particles_->vel_n_), - vorticity_(*particles_->createAVariable("VorticityInner")) - { - particles_->addAVariableToWrite("VorticityInner"); - } - //=================================================================================================// - void VorticityInner::Interaction(size_t index_i, Real dt) - { - Vecd& vel_i = vel_n_[index_i]; - - AngularVecd vorticity(0); - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - Vecd r_ij = inner_neighborhood.r_ij_[n] * inner_neighborhood.e_ij_[n]; - - Vecd vel_diff = vel_i - vel_n_[index_j]; - vorticity += SimTK::cross(vel_diff, r_ij) * Vol_[index_j] * inner_neighborhood.dW_ij_[n]; - } - - vorticity_[index_i] = vorticity; - } - //=================================================================================================// - BaseRelaxation::BaseRelaxation(BaseBodyRelationInner* inner_relation) : - ParticleDynamics1Level(inner_relation->sph_body_), - FluidDataInner(inner_relation), - Vol_(particles_->Vol_), mass_(particles_->mass_), rho_n_(particles_->rho_n_), - p_(particles_->p_), drho_dt_(particles_->drho_dt_), - pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_), - dvel_dt_(particles_->dvel_dt_), dvel_dt_prior_(particles_->dvel_dt_prior_) {} - //=================================================================================================// - BasePressureRelaxation:: - BasePressureRelaxation(BaseBodyRelationInner* inner_relation) : - BaseRelaxation(inner_relation) {} - //=================================================================================================// - void BasePressureRelaxation::Initialization(size_t index_i, Real dt) - { - rho_n_[index_i] += drho_dt_[index_i] * dt * 0.5; - Vol_[index_i] = mass_[index_i] / rho_n_[index_i]; - p_[index_i] = material_->getPressure(rho_n_[index_i]); - pos_n_[index_i] += vel_n_[index_i] * dt * 0.5; - } - //=================================================================================================// - void BasePressureRelaxation::Update(size_t index_i, Real dt) - { - vel_n_[index_i] += dvel_dt_[index_i] * dt; - } - //=================================================================================================// - Vecd BasePressureRelaxation::computeNonConservativeAcceleration(size_t index_i) - { - Real rho_i = rho_n_[index_i]; - Real p_i = p_[index_i]; - Vecd acceleration = dvel_dt_prior_[index_i]; - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - Real dW_ij = inner_neighborhood.dW_ij_[n]; - Vecd& e_ij = inner_neighborhood.e_ij_[n]; - - Real rho_j = rho_n_[index_j]; - Real p_j = p_[index_j]; - - Real p_star = (rho_i * p_j + rho_j * p_i) / (rho_i + rho_j); - acceleration += (p_i - p_star) * Vol_[index_j] * dW_ij * e_ij / rho_i; - } - return acceleration; - } - //=================================================================================================// - BaseDensityRelaxation:: - BaseDensityRelaxation(BaseBodyRelationInner* inner_relation) : - BaseRelaxation(inner_relation) {} - //=================================================================================================// - void BaseDensityRelaxation::Initialization(size_t index_i, Real dt) - { - pos_n_[index_i] += vel_n_[index_i] * dt * 0.5; - } - //=================================================================================================// - void BaseDensityRelaxation::Update(size_t index_i, Real dt) - { - rho_n_[index_i] += drho_dt_[index_i] * dt * 0.5; - } - //=================================================================================================// - void FreeStreamBoundaryVelocityCorrection::Update(size_t index_i, Real dt) - { - vel_n_[index_i] += dvel_dt_[index_i] * dt; - dvel_dt_[index_i] = Vecd(0.0, 0.0); - - if (surface_indicator_[index_i] == 1) - { - Real run_time_ = GlobalStaticVariables::physical_time_; - Real u_ave_ = run_time_ < t_ref_ ? 0.5 * u_ref_ * (1.0 - cos(Pi * run_time_ / t_ref_)) : u_ref_; - vel_n_[index_i][0] = u_ave_ + SMIN(rho_sum[index_i], rho_ref_) * (vel_n_[index_i][0] - u_ave_) / rho_ref_; - } - } - //=================================================================================================// - PressureRelaxationRiemannInnerOldroyd_B :: - PressureRelaxationRiemannInnerOldroyd_B(BaseBodyRelationInner* inner_relation) : - PressureRelaxationRiemannInner(inner_relation), - tau_(dynamic_cast(body_->base_particles_)->tau_), - dtau_dt_(dynamic_cast(body_->base_particles_)->dtau_dt_) {} - //=================================================================================================// - void PressureRelaxationRiemannInnerOldroyd_B::Initialization(size_t index_i, Real dt) - { - PressureRelaxationRiemannInner::Initialization(index_i, dt); - - tau_[index_i] += dtau_dt_[index_i] * dt * 0.5; - } - //=================================================================================================// - void PressureRelaxationRiemannInnerOldroyd_B::Interaction(size_t index_i, Real dt) - { - PressureRelaxationRiemannInner::Interaction(index_i, dt); - - Real rho_i = rho_n_[index_i]; - Matd tau_i = tau_[index_i]; - - Vecd acceleration(0); - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - Vecd nablaW_ij = inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]; - - //elastic force - acceleration += (tau_i + tau_[index_j]) * nablaW_ij * Vol_[index_j] / rho_i; - } - - dvel_dt_[index_i] += acceleration; - } - //=================================================================================================// - DensityRelaxationRiemannInnerOldroyd_B:: - DensityRelaxationRiemannInnerOldroyd_B(BaseBodyRelationInner* inner_relation) : - DensityRelaxationRiemannInner(inner_relation), - tau_(dynamic_cast(body_->base_particles_)->tau_), - dtau_dt_(dynamic_cast(body_->base_particles_)->dtau_dt_) - { - Oldroyd_B_Fluid *oldroy_b_fluid - = dynamic_cast(body_->base_particles_->base_material_); - mu_p_ = oldroy_b_fluid->ReferencePolymericViscosity(); - lambda_ = oldroy_b_fluid->getReferenceRelaxationTime(); - } - //=================================================================================================// - void DensityRelaxationRiemannInnerOldroyd_B::Interaction(size_t index_i, Real dt) - { - DensityRelaxationRiemannInner::Interaction(index_i, dt); - - Vecd vel_i = vel_n_[index_i]; - Matd tau_i = tau_[index_i]; - - Matd stress_rate(0); - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - Vecd nablaW_ij = inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]; - - Matd velocity_gradient = - SimTK::outer((vel_i - vel_n_[index_j]), nablaW_ij) * Vol_[index_j]; - stress_rate += ~velocity_gradient * tau_i + tau_i * velocity_gradient - - tau_i / lambda_ + (~velocity_gradient + velocity_gradient) * mu_p_ / lambda_; - } - - dtau_dt_[index_i] = stress_rate; - } - //=================================================================================================// - void DensityRelaxationRiemannInnerOldroyd_B::Update(size_t index_i, Real dt) - { - DensityRelaxationRiemannInner::Update(index_i, dt); - - tau_[index_i] += dtau_dt_[index_i] * dt * 0.5; - } - //=================================================================================================// - FlowRelaxationBuffer:: - FlowRelaxationBuffer(FluidBody* body, BodyPartByCell* body_part) : - PartDynamicsByCell(body, body_part), FluidDataSimple(body), - pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_), relaxation_rate_(0.3) - { - }; - //=================================================================================================// - void FlowRelaxationBuffer - ::Update(size_t index_i, Real dt) - { - vel_n_[index_i] += - relaxation_rate_ * ( getTargetVelocity(pos_n_[index_i], vel_n_[index_i]) - vel_n_[index_i]); - } - //=================================================================================================// - DampingBoundaryCondition:: - DampingBoundaryCondition(FluidBody* body, BodyPartByCell* body_part) : - PartDynamicsByCell(body, body_part), FluidDataSimple(body), - pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_), strength_(5.0) - { - damping_zone_bounds_ = body_part->BodyPartBounds(); - }; - //=================================================================================================// - void DampingBoundaryCondition::Update(size_t index_i, Real dt) - { - Real damping_factor = (pos_n_[index_i][0] - damping_zone_bounds_.first[0]) / - (damping_zone_bounds_.second[0]- damping_zone_bounds_.first[0]); - vel_n_[index_i] *= (1.0 - dt * strength_ * damping_factor * damping_factor); - } - //=================================================================================================// - StaticConfinementDensity:: - StaticConfinementDensity(FluidBody* body, NearShapeSurface* near_surface) : - PartDynamicsByCell(body, near_surface), FluidDataSimple(body), - rho0_(particles_->rho0_), inv_sigma0_(1.0 / particles_->sigma0_), - mass_(particles_->mass_), rho_sum_(particles_->rho_sum_), pos_n_(particles_->pos_n_), - level_set_complex_shape_(near_surface->getLevelSetComplexShape()) {} - //=================================================================================================// - void StaticConfinementDensity::Update(size_t index_i, Real dt) - { - Real inv_Vol_0_i = rho0_ / mass_[index_i]; - rho_sum_[index_i] += - level_set_complex_shape_->computeKernelIntegral(pos_n_[index_i]) * inv_Vol_0_i * rho0_ * inv_sigma0_ ; - } - //=================================================================================================// - StaticConfinementPressureRelaxation:: - StaticConfinementPressureRelaxation(FluidBody* body, NearShapeSurface* near_surface) : - PartDynamicsByCell(body, near_surface), FluidDataSimple(body), - rho_n_(particles_->rho_n_), p_(particles_->p_), - pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_), - dvel_dt_(particles_->dvel_dt_), - level_set_complex_shape_(near_surface->getLevelSetComplexShape()), - riemann_solver_(*material_, *material_) {} - //=================================================================================================// - void StaticConfinementPressureRelaxation::Update(size_t index_i, Real dt) - { - Vecd kernel_gradient = level_set_complex_shape_->computeKernelGradientIntegral(pos_n_[index_i]); - Vecd normal_to_fluid = -kernel_gradient / (kernel_gradient.norm() + TinyReal); - - FluidState state(rho_n_[index_i], vel_n_[index_i], p_[index_i]); - Vecd vel_in_wall = -state.vel_; - FluidState state_in_wall(rho_n_[index_i], vel_in_wall, p_[index_i]); - - //always solving one-side Riemann problem for wall boundaries - Real p_star = riemann_solver_.getPStar(state, state_in_wall, normal_to_fluid); - dvel_dt_[index_i] -= 2.0 * p_star * kernel_gradient / state.rho_; - } - //=================================================================================================// - StaticConfinementDensityRelaxation:: - StaticConfinementDensityRelaxation(FluidBody* body, NearShapeSurface* near_surface) : - PartDynamicsByCell(body, near_surface), FluidDataSimple(body), - rho_n_(particles_->rho_n_), p_(particles_->p_), drho_dt_(particles_->drho_dt_), - pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_), - level_set_complex_shape_(near_surface->getLevelSetComplexShape()), - riemann_solver_(*material_, *material_) {} - //=================================================================================================// - void StaticConfinementDensityRelaxation::Update(size_t index_i, Real dt) - { - Vecd kernel_gradient = level_set_complex_shape_->computeKernelGradientIntegral(pos_n_[index_i]); - Vecd normal_to_fluid = -kernel_gradient / (kernel_gradient.norm() + TinyReal); - - FluidState state(rho_n_[index_i], vel_n_[index_i], p_[index_i]); - Vecd vel_in_wall = -state.vel_; - FluidState state_in_wall(rho_n_[index_i], vel_in_wall, p_[index_i]); - - //always solving one-side Riemann problem for wall boundaries - Vecd vel_star = riemann_solver_.getVStar(state, state_in_wall, normal_to_fluid); - drho_dt_[index_i] += 2.0 * state.rho_ * dot(state.vel_ - vel_star, kernel_gradient); - } - //=================================================================================================// - StaticConfinement::StaticConfinement(FluidBody* body, NearShapeSurface* near_surface) : - density_summation_(body, near_surface), pressure_relaxation_(body, near_surface), - density_relaxation_(body, near_surface) {} - //=================================================================================================// - EmitterInflowCondition:: - EmitterInflowCondition(FluidBody* body, BodyPartByParticle* body_part) : - PartSimpleDynamicsByParticle(body, body_part), FluidDataSimple(body), - rho_n_(particles_->rho_n_), p_(particles_->p_), - pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_), inflow_pressure_(0) - { - rho0_ = material_->ReferenceDensity(); - } - //=================================================================================================// - void EmitterInflowCondition - ::Update(size_t unsorted_index_i, Real dt) - { - size_t sorted_index_i = sorted_id_[unsorted_index_i]; - vel_n_[sorted_index_i] = getTargetVelocity(pos_n_[sorted_index_i], vel_n_[sorted_index_i]); - rho_n_[sorted_index_i] = rho0_; - p_[sorted_index_i] = material_->getPressure(rho_n_[sorted_index_i]); - } - //=================================================================================================// - InletOutletInflowCondition:: - InletOutletInflowCondition(FluidBody* body, BodyPartByParticle* body_part) : - EmitterInflowCondition(body, body_part){} - //=================================================================================================// - void InletOutletInflowCondition - ::Update(size_t unsorted_index_i, Real dt) - { - size_t sorted_index_i = sorted_id_[unsorted_index_i]; - vel_n_[sorted_index_i] = getTargetVelocity(pos_n_[sorted_index_i], vel_n_[sorted_index_i]); - } - //=================================================================================================// - EmitterInflowInjecting - ::EmitterInflowInjecting(FluidBody* body, BodyPartByParticle* body_part, - size_t body_buffer_width, int axis_direction, bool positive) - : PartSimpleDynamicsByParticle(body, body_part), FluidDataSimple(body), - pos_n_(particles_->pos_n_),rho_n_(particles_->rho_n_), p_(particles_->p_), - axis_(axis_direction), periodic_translation_(0), body_buffer_width_(body_buffer_width) - { - body_part_bounds_ = body_part->getBodyPartShape()->findBounds(); - periodic_translation_[axis_] = body_part_bounds_.second[axis_] - body_part_bounds_.first[axis_]; - size_t total_body_buffer_particles = body_part_particles_.size() * body_buffer_width_; - for (size_t i = 0; i < total_body_buffer_particles; ++i) - { - particles_->addABufferParticle(); - } - particles_->real_particles_bound_ += total_body_buffer_particles; - body_->allocateConfigurationMemoriesForBufferParticles(); - - checking_bound_ = positive ? - std::bind(&EmitterInflowInjecting::checkUpperBound, this, _1, _2) - : std::bind(&EmitterInflowInjecting::checkLowerBound, this, _1, _2); - } - //=================================================================================================// - void EmitterInflowInjecting::checkUpperBound(size_t unsorted_index_i, Real dt) - { - size_t sorted_index_i = sorted_id_[unsorted_index_i]; - if (pos_n_[sorted_index_i][axis_] > body_part_bounds_.second[axis_]) { - if (particles_->total_real_particles_ >= particles_->real_particles_bound_) - { - std::cout << "EmitterInflowBoundaryCondition::ConstraintAParticle: \n" - << "Not enough body buffer particles! Exit the code." << "\n"; - exit(0); - } - /** Buffer Particle state copied from real particle. */ - particles_->copyFromAnotherParticle(particles_->total_real_particles_, sorted_index_i); - /** Realize the buffer particle by increas�ng the number of real particle in the body. */ - particles_->total_real_particles_ += 1; - /** Periodic bounding. */ - pos_n_[sorted_index_i][axis_] -= periodic_translation_[axis_]; - rho_n_[sorted_index_i] = material_->ReferenceDensity(); - p_[sorted_index_i] = material_->getPressure(rho_n_[sorted_index_i]); - } - } - //=================================================================================================// - void EmitterInflowInjecting::checkLowerBound(size_t unsorted_index_i, Real dt) - { - size_t sorted_index_i = sorted_id_[unsorted_index_i]; - if (pos_n_[sorted_index_i][axis_] < body_part_bounds_.first[axis_]) { - if (particles_->total_real_particles_ >= particles_->real_particles_bound_) - { - std::cout << "EmitterInflowBoundaryCondition::ConstraintAParticle: \n" - << "Not enough body buffer particles! Exit the code." << "\n"; - exit(0); - } - /** Buffer Particle state copied from real particle. */ - particles_->copyFromAnotherParticle(particles_->total_real_particles_, sorted_index_i); - /** Realize the buffer particle by increasing the number of real particle in the body. */ - particles_->total_real_particles_ += 1; - pos_n_[sorted_index_i][axis_] += periodic_translation_[axis_]; - } - } - //=================================================================================================// - ColorFunctionGradientInner::ColorFunctionGradientInner(BaseBodyRelationInner* inner_relation) - : InteractionDynamics(inner_relation->sph_body_), FluidDataInner(inner_relation), - Vol_(particles_->Vol_), - surface_indicator_(particles_->surface_indicator_), - color_grad_(*particles_->createAVariable("ColorGradient")), - surface_norm_(*particles_->createAVariable("SurfaceNormal")), - pos_div_(*particles_->getVariableByName("PositionDivergence")) - { - //register particle variable defined in this class - thereshold_by_dimensions_ = (0.75 * (Real)Dimensions); - } - //=================================================================================================// - void ColorFunctionGradientInner::Interaction(size_t index_i, Real dt) - { - Vecd gradient(0); - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - if(pos_div_[index_i] < thereshold_by_dimensions_) - { - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - gradient -= inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n] * Vol_[index_j]; - } - - } - color_grad_[index_i] = gradient; - surface_norm_[index_i] = gradient / (gradient.norm() + TinyReal); - } - //=================================================================================================// - ColorFunctionGradientInterplationInner::ColorFunctionGradientInterplationInner(BaseBodyRelationInner* inner_relation) - : InteractionDynamics(inner_relation->sph_body_), FluidDataInner(inner_relation), Vol_(particles_->Vol_), - surface_indicator_(particles_->surface_indicator_), - color_grad_(*particles_->getVariableByName("ColorGradient")), - surface_norm_(*particles_->getVariableByName("SurfaceNormal")), - pos_div_(*particles_->getVariableByName("PositionDivergence")) - { - thereshold_by_dimensions_ = (0.75 * (Real)Dimensions); - particles_->addAVariableToWrite("SurfaceNormal"); - particles_->addAVariableToWrite("ColorGradient"); - } - //=================================================================================================// - void ColorFunctionGradientInterplationInner::Interaction(size_t index_i, Real dt) - { - Vecd grad(0); - Real weight(0); - Real total_weight(0); - if (surface_indicator_[index_i] == 1 && pos_div_[index_i] > thereshold_by_dimensions_) - { - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - if (surface_indicator_[index_j] == 1 && pos_div_[index_j] < thereshold_by_dimensions_) - { - weight = inner_neighborhood.W_ij_[n] * Vol_[index_j]; - grad += weight * color_grad_[index_j]; - total_weight += weight; - } - } - Vecd grad_norm = grad / (total_weight + TinyReal); - color_grad_[index_i] = grad_norm; - surface_norm_[index_i] = grad_norm / (grad_norm.norm() + TinyReal); - } - } - //=================================================================================================// - SurfaceTensionAccelerationInner::SurfaceTensionAccelerationInner(BaseBodyRelationInner* inner_relation, Real gamma) - : InteractionDynamics(inner_relation->sph_body_), FluidDataInner(inner_relation), - gamma_(gamma), Vol_(particles_->Vol_), - mass_(particles_->mass_), dvel_dt_prior_(particles_->dvel_dt_prior_), surface_indicator_(particles_->surface_indicator_), - color_grad_(*particles_->getVariableByName("ColorGradient")), - surface_norm_(*particles_->getVariableByName("SurfaceNormal")){} - //=================================================================================================// - SurfaceTensionAccelerationInner::SurfaceTensionAccelerationInner(BaseBodyRelationInner* inner_relation) - : SurfaceTensionAccelerationInner(inner_relation, 1.0) {} - //=================================================================================================// - void SurfaceTensionAccelerationInner::Interaction(size_t index_i, Real dt) - { - Vecd n_i = surface_norm_[index_i]; - Real curvature(0.0); - Real renormal_curvature(0); - Real pos_div(0); - if(surface_indicator_[index_i] == 1) - { - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - if(surface_indicator_[index_j] == 1) - { - Vecd n_j = surface_norm_[index_j]; - Vecd n_ij = n_i - n_j; - curvature -= inner_neighborhood.dW_ij_[n] * Vol_[index_j] * dot(n_ij, inner_neighborhood.e_ij_[n]); - pos_div -= inner_neighborhood.dW_ij_[n] * inner_neighborhood.r_ij_[n] * Vol_[index_j]; - } - } - } - /** - Adami et al. 2010 is wrong in equation. - (dv / dt)_s = (1.0 / rho) (-sigma * k * n * delta) - = (1/rho) * curvature * color_grad - = (1/m) * curvature * color_grad * vol - */ - renormal_curvature = (Real)Dimensions * curvature / ABS(pos_div + TinyReal); - Vecd acceleration = gamma_ * renormal_curvature* color_grad_[index_i] * Vol_[index_i]; - dvel_dt_prior_[index_i] -= acceleration / mass_[index_i]; - } - //=================================================================================================// - } -//=================================================================================================// -} -//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.h b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.h deleted file mode 100644 index 5e06c576eb..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.h +++ /dev/null @@ -1,747 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file fluid_dynamics_inner.h -* @brief Here, we define the algorithm classes for fluid dynamics within the body. -* @details We consider here weakly compressible fluids. The algorithms may be -* different for free surface flow and the one without free surface. -* @author Chi ZHang and Xiangyu Hu -*/ - - -#ifndef FLUID_DYNAMCIS_INNER_H -#define FLUID_DYNAMCIS_INNER_H - - - -#include "all_particle_dynamics.h" -#include "base_kernel.h" -#include "riemann_solver.h" - -namespace SPH -{ - namespace fluid_dynamics - { - typedef DataDelegateSimple FluidDataSimple; - typedef DataDelegateInner FluidDataInner; - - /** - * @class FluidInitialCondition - * @brief Set initial condition for a fluid body. - * This is a abstract class to be override for case specific initial conditions - */ - class FluidInitialCondition - : public ParticleDynamicsSimple, public FluidDataSimple - { - public: - FluidInitialCondition(FluidBody* body); - virtual ~FluidInitialCondition() {}; - protected: - StdLargeVec& pos_n_, & vel_n_; - }; - - /** - * @class FreeSurfaceIndicationInner - * @brief indicate the particles near the free surface of a fluid body. - * Note that, SPHinXsys does not require this function for simulating general free surface flow problems. - * However, some other applications may use this function, such as transport velocity formulation, - * for masking some function which is only applicable for the bulk of the fluid body. - */ - class FreeSurfaceIndicationInner - : public InteractionDynamicsWithUpdate, public FluidDataInner - { - public: - FreeSurfaceIndicationInner(BaseBodyRelationInner* inner_relation, Real thereshold = 0.75); - virtual ~FreeSurfaceIndicationInner() {}; - - protected: - Real smoothing_length_; - Real thereshold_by_dimensions_; - StdLargeVec& Vol_, & pos_div_; - StdLargeVec& surface_indicator_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class MultilayeredSurfaceParticlesIdentification - * @brief extendedly indicate the 2nd and 3rd layers of surface particles. - * @brief In total, all indicator indexs of 3 layers of surface paritlces are set to 1. - * @brief in detail, index of second and third layer particles should be changed to 2 firstly as a temporary value. - * @brief then index 2 will be changed to 1 in the end. - * @brief applied in inlet-outlet case with wall boundary. - */ - class MultilayeredSurfaceParticlesIdentification - : public InteractionDynamicsWithUpdate, public FluidDataInner - { - public: - MultilayeredSurfaceParticlesIdentification(BaseBodyRelationInner* inner_relation); - virtual ~MultilayeredSurfaceParticlesIdentification() {}; - - protected: - StdLargeVec& pos_n_; - StdLargeVec& surface_indicator_, &previous_surface_indicator_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class FreeStreamIndicateInletOutletSurfaceParticle - * @brief further indicate the surface particles sepearately at inlet and outlet. - * @brief In total, index of internal fluid particles is 0. that of 3-layered surface particles is 1 or 2. - * @brief In detail, based on the result of MultilayeredSurfaceParticlesIdentification, - * @brief surface particles close to inlet and outler have the index 2,while other surface particles own index 1. - * @brief applied in the free-stream case without wall boundary. - */ - class FreeStreamInletOutletSurfaceParticleIdentification - : public ParticleDynamicsSimple, public FluidDataInner - { - public: - FreeStreamInletOutletSurfaceParticleIdentification(BaseBodyRelationInner* inner_relation, int axis_direction); - virtual ~FreeStreamInletOutletSurfaceParticleIdentification() {}; - - protected: - StdLargeVec& pos_n_; - /**< lower and upper body domain bound for checking. */ - BoundingBox fluid_body_domain_bounds_; - /** the axis direction for bounding*/ - const int axis_; - Real particle_spacing_; - StdLargeVec& surface_indicator_, &previous_surface_indicator_; - - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class DensitySummationInner - * @brief computing density by summation - */ - class DensitySummationInner - : public InteractionDynamicsWithUpdate, public FluidDataInner - { - public: - DensitySummationInner(BaseBodyRelationInner* inner_relation); - virtual ~DensitySummationInner() {}; - protected: - Real W0_, rho0_, inv_sigma0_; - StdLargeVec& Vol_, & rho_n_, & mass_, & rho_sum_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - virtual Real ReinitializedDensity(Real rho_sum, Real rho_0, Real rho_n) { return rho_sum; }; - }; - - /** - * @class DensitySummationFreeSurfaceInner - * @brief computing density by summation with a re-normalization for free surface flows - */ - class DensitySummationFreeSurfaceInner : public DensitySummationInner - { - public: - DensitySummationFreeSurfaceInner(BaseBodyRelationInner* inner_relation) : - DensitySummationInner(inner_relation) {}; - virtual ~DensitySummationFreeSurfaceInner() {}; - protected: - virtual Real ReinitializedDensity(Real rho_sum, Real rho_0, Real rho_n) override - { - return rho_sum + SMAX(0.0, (rho_n - rho_sum)) * rho_0 / rho_n; - }; - }; - /** - * @class DensitySummationFreeStreamInner - * @brief the density of three-layer surface particles is calculated by DensitySummationFreeSurface, - * @brief and the density of other internal particles is obtained by DensitySummation. - * @brief applied in free stream flow without wall boundary. - */ - class DensitySummationFreeStreamInner : public DensitySummationFreeSurfaceInner - { - public: - DensitySummationFreeStreamInner(BaseBodyRelationInner* inner_relation) : - DensitySummationFreeSurfaceInner(inner_relation), - surface_indicator_(*particles_->getVariableByName("SurfaceIndicator")) {}; - virtual ~DensitySummationFreeStreamInner() {}; - protected: - StdLargeVec& surface_indicator_; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class ViscousAccelerationInner - * @brief the viscosity force induced acceleration - */ - class ViscousAccelerationInner - : public InteractionDynamics, public FluidDataInner - { - public: - ViscousAccelerationInner(BaseBodyRelationInner* inner_relation); - virtual ~ViscousAccelerationInner() {}; - protected: - Real mu_; - Real smoothing_length_; - StdLargeVec &Vol_, &rho_n_, &p_; - StdLargeVec &vel_n_, &dvel_dt_prior_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class AngularConservativeViscousAccelerationInner - * @brief the viscosity force induced acceleration, a formulation for conserving - * angular momentum, to be tested for its practical applications. - */ - class AngularConservativeViscousAccelerationInner : public ViscousAccelerationInner - { - public: - AngularConservativeViscousAccelerationInner(BaseBodyRelationInner* inner_relation) : - ViscousAccelerationInner(inner_relation) {}; - virtual ~AngularConservativeViscousAccelerationInner() {}; - protected: - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class TransportVelocityCorrectionInner - * @brief transport velocity correction - */ - class TransportVelocityCorrectionInner - : public InteractionDynamics, public FluidDataInner - { - public: - TransportVelocityCorrectionInner(BaseBodyRelationInner* inner_relation); - virtual ~TransportVelocityCorrectionInner() {}; - protected: - StdLargeVec& Vol_, & rho_n_; - StdLargeVec& pos_n_; - StdLargeVec& surface_indicator_; - Real p_background_; - - virtual void setupDynamics(Real dt = 0.0) override; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class AcousticTimeStepSize - * @brief Computing the acoustic time step size - */ - class AcousticTimeStepSize : - public ParticleDynamicsReduce, public FluidDataSimple - { - public: - explicit AcousticTimeStepSize(FluidBody* body); - virtual ~AcousticTimeStepSize() {}; - protected: - StdLargeVec& rho_n_, & p_; - StdLargeVec& vel_n_; - Real smoothing_length_; - Real ReduceFunction(size_t index_i, Real dt = 0.0) override; - Real OutputResult(Real reduced_value) override; - }; - - /** - * @class AdvectionTimeStepSize - * @brief Computing the advection time step size - */ - class AdvectionTimeStepSize : - public ParticleDynamicsReduce, public FluidDataSimple - { - public: - explicit AdvectionTimeStepSize(FluidBody* body, Real U_max); - virtual ~AdvectionTimeStepSize() {}; - protected: - Real smoothing_length_; - StdLargeVec& vel_n_; - Real ReduceFunction(size_t index_i, Real dt = 0.0) override; - Real OutputResult(Real reduced_value) override; - }; - - /** - * @class AdvectionTimeStepSizeForImplicitViscosity - * @brief Computing the advection time step size when viscosity is handled implicitly - */ - class AdvectionTimeStepSizeForImplicitViscosity : public AdvectionTimeStepSize - { - public: - explicit AdvectionTimeStepSizeForImplicitViscosity(FluidBody* body, Real U_max); - virtual ~AdvectionTimeStepSizeForImplicitViscosity() {}; - }; - - /** - * @class VorticityInner - * @brief compute vorticity in the fluid field - */ - class VorticityInner - : public InteractionDynamics, public FluidDataInner - { - public: - VorticityInner(BaseBodyRelationInner* body_inner_relation); - virtual ~VorticityInner() {}; - protected: - StdLargeVec& Vol_; - StdLargeVec& vel_n_; - StdLargeVec & vorticity_; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class BaseRelaxation - * @brief Pure abstract base class for all fluid relaxation schemes - */ - class BaseRelaxation : public ParticleDynamics1Level, public FluidDataInner - { - public: - BaseRelaxation(BaseBodyRelationInner* inner_relation); - virtual ~BaseRelaxation() {}; - protected: - StdLargeVec& Vol_, & mass_, & rho_n_, & p_, & drho_dt_; - StdLargeVec& pos_n_, & vel_n_, & dvel_dt_, & dvel_dt_prior_; - }; - - /** - * @class BasePressureRelaxation - * @brief Abstract base class for all pressure relaxation schemes - */ - class BasePressureRelaxation : public BaseRelaxation - { - public: - BasePressureRelaxation(BaseBodyRelationInner* inner_relation); - virtual ~BasePressureRelaxation() {}; - protected: - virtual void Initialization(size_t index_i, Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - virtual Vecd computeNonConservativeAcceleration(size_t index_i); - }; - - /** - * @class BasePressureRelaxationInner - * @brief Template class for pressure relaxation scheme with the Riemann solver - * as template variable - */ - template - class BasePressureRelaxationInner : public BasePressureRelaxation - { - public: - BasePressureRelaxationInner(BaseBodyRelationInner* inner_relation); - virtual ~BasePressureRelaxationInner() {}; - RiemannSolverType riemann_solver_; - protected: - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - using PressureRelaxationInner = BasePressureRelaxationInner; - /** define the mostly used pressure relaxation scheme using Riemann solver */ - using PressureRelaxationRiemannInner = BasePressureRelaxationInner; - using PressureRelaxationDissipativeRiemannInner = BasePressureRelaxationInner; - - /** - * @class BaseDensityRelaxation - * @brief Abstract base class for all density relaxation schemes - */ - class BaseDensityRelaxation : public BaseRelaxation - { - public: - BaseDensityRelaxation(BaseBodyRelationInner* inner_relation); - virtual ~BaseDensityRelaxation() {}; - protected: - virtual void Initialization(size_t index_i, Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class FreeStreamBoundaryVelocityCorrection - * @brief this function is applied to freetream flows - * @brief modify the velocity of free surface particles with far-field velocity - */ - class FreeStreamBoundaryVelocityCorrection: public ParticleDynamicsSimple, public FluidDataInner - { - public: - FreeStreamBoundaryVelocityCorrection(BaseBodyRelationInner* inner_relation) - : ParticleDynamicsSimple(inner_relation->sph_body_), - FluidDataInner(inner_relation), u_ref_(1.0), t_ref_(2.0), - rho_ref_(material_->ReferenceDensity()), rho_sum(particles_->rho_sum_), - vel_n_(particles_->vel_n_),dvel_dt_(particles_->dvel_dt_), - surface_indicator_(*particles_->getVariableByName("SurfaceIndicator")) {}; - virtual ~FreeStreamBoundaryVelocityCorrection() {}; - protected: - Real u_ref_, t_ref_, rho_ref_; - StdLargeVec& rho_sum; - StdLargeVec& vel_n_, &dvel_dt_; - StdLargeVec& surface_indicator_; - - virtual void Update(size_t index_i, Real dt = 0.0) override ; - }; - - /** - * @class DensityRelaxationInner - * @brief Template density relaxation scheme with different Riemann solver - */ - template - class BaseDensityRelaxationInner : public BaseDensityRelaxation - { - public: - BaseDensityRelaxationInner(BaseBodyRelationInner* inner_relation); - virtual ~BaseDensityRelaxationInner() {}; - RiemannSolverType riemann_solver_; - protected: - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - using DensityRelaxationInner = BaseDensityRelaxationInner; - /** define the mostly used density relaxation scheme using Riemann solver */ - using DensityRelaxationRiemannInner = BaseDensityRelaxationInner; - - /** - * @class Oldroyd_B_FluidInitialCondition - * @brief set initial condition for Oldroyd_B_Fluid dynamics - * This is a abstract class to be override for case specific initial conditions - */ - class Oldroyd_B_FluidInitialCondition - : public ParticleDynamicsSimple, public FluidDataSimple - { - public: - Oldroyd_B_FluidInitialCondition(FluidBody* body) - : ParticleDynamicsSimple(body), FluidDataSimple(body) {}; - virtual ~Oldroyd_B_FluidInitialCondition() {}; - }; - - /** - * @class PressureRelaxationRiemannInnerOldroyd_B - * @brief Pressure relaxation scheme with the mostly used Riemann solver. - */ - class PressureRelaxationRiemannInnerOldroyd_B : public PressureRelaxationRiemannInner - { - public: - PressureRelaxationRiemannInnerOldroyd_B(BaseBodyRelationInner* inner_relation); - virtual ~PressureRelaxationRiemannInnerOldroyd_B() {}; - protected: - StdLargeVec& tau_, & dtau_dt_; - virtual void Initialization(size_t index_i, Real dt = 0.0) override; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class DensityRelaxationRiemannInnerOldroyd_B - * @brief Density relaxation scheme with the mostly used Riemann solver. - */ - class DensityRelaxationRiemannInnerOldroyd_B : public DensityRelaxationRiemannInner - { - public: - DensityRelaxationRiemannInnerOldroyd_B(BaseBodyRelationInner* inner_relation); - virtual ~DensityRelaxationRiemannInnerOldroyd_B() {}; - protected: - StdLargeVec& tau_, & dtau_dt_; - Real mu_p_, lambda_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class FlowRelaxationBuffer - * @brief Flow buffer in which the particles relaxes to a given target velocity profile. - * This technique will be used for applying several boundary conditions, - * such as freestream, inflow, damping boundary conditions. - */ - class FlowRelaxationBuffer : public PartDynamicsByCell, public FluidDataSimple - { - public: - FlowRelaxationBuffer(FluidBody* body, BodyPartByCell* body_part); - virtual ~FlowRelaxationBuffer() {}; - protected: - StdLargeVec& pos_n_, & vel_n_; - /** default value is 0.1 suggests reaching target inflow velocity in about 10 time steps */ - Real relaxation_rate_; - - /** inflow profile to be defined in applications */ - virtual Vecd getTargetVelocity(Vecd& position, Vecd& velocity) = 0; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class InflowBoundaryCondition - * @brief inflow boundary condition which relaxes - * the particles to a given velocity profile. - */ - class InflowBoundaryCondition : public FlowRelaxationBuffer - { - public: - InflowBoundaryCondition(FluidBody* body, BodyPartByCell* body_part) : - FlowRelaxationBuffer(body, body_part) {};; - virtual ~InflowBoundaryCondition() {}; - }; - - /** - * @class DampingBoundaryCondition - * @brief damping boundary condition which relaxes - * the particles to zero velocity profile. - */ - class DampingBoundaryCondition - : public PartDynamicsByCell, public FluidDataSimple - { - public: - DampingBoundaryCondition(FluidBody* body, BodyPartByCell* body_part); - virtual ~DampingBoundaryCondition() {}; - protected: - StdLargeVec& pos_n_, & vel_n_; - /** default value is 0.1 suggests reaching target inflow velocity in about 10 time steps */ - Real strength_; - BoundingBox damping_zone_bounds_; - virtual void Update(size_t index_particle_i, Real dt = 0.0) override; - }; - - - /** - * @class StaticConfinementDensity - * @brief static confinement condition for density summation - */ - class StaticConfinementDensity - : public PartDynamicsByCell, public FluidDataSimple - { - public: - StaticConfinementDensity(FluidBody* body, NearShapeSurface* near_surface); - virtual ~StaticConfinementDensity() {}; - protected: - Real rho0_, inv_sigma0_; - StdLargeVec& mass_, & rho_sum_; - StdLargeVec& pos_n_; - LevelSetComplexShape* level_set_complex_shape_; - - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class StaticConfinementPressureRelaxation - * @brief static confinement condition for pressure relaxation - */ - class StaticConfinementPressureRelaxation - : public PartDynamicsByCell, public FluidDataSimple - { - public: - StaticConfinementPressureRelaxation(FluidBody* body, NearShapeSurface* near_surface); - virtual ~StaticConfinementPressureRelaxation() {}; - protected: - StdLargeVec& rho_n_, & p_; - StdLargeVec& pos_n_, & vel_n_, & dvel_dt_; - LevelSetComplexShape* level_set_complex_shape_; - AcousticRiemannSolver riemann_solver_; - - - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class StaticConfinementDensityRelaxation - * @brief static confinement condition for density relaxation - */ - class StaticConfinementDensityRelaxation - : public PartDynamicsByCell, public FluidDataSimple - { - public: - StaticConfinementDensityRelaxation(FluidBody* body, NearShapeSurface* near_surface); - virtual ~StaticConfinementDensityRelaxation() {}; - protected: - StdLargeVec& rho_n_, & p_, & drho_dt_; - StdLargeVec& pos_n_, & vel_n_; - LevelSetComplexShape* level_set_complex_shape_; - AcousticRiemannSolver riemann_solver_; - - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class StaticConfinement - * @brief Static confined boundary condition for complex structures. - */ - class StaticConfinement - { - public: - StaticConfinementDensity density_summation_; - StaticConfinementPressureRelaxation pressure_relaxation_; - StaticConfinementDensityRelaxation density_relaxation_; - - StaticConfinement(FluidBody* body, NearShapeSurface* near_surface); - virtual ~StaticConfinement() {}; - }; - - /** - * @class EmitterInflowCondition - * @brief Inflow boundary condition. - * The body part region is required to - * have parallel lower- and upper-bound surfaces. - */ - class EmitterInflowCondition - : public PartSimpleDynamicsByParticle, public FluidDataSimple - { - public: - explicit EmitterInflowCondition(FluidBody* body, BodyPartByParticle* body_part); - virtual ~EmitterInflowCondition() {}; - protected: - StdLargeVec& rho_n_, & p_; - StdLargeVec& pos_n_, & vel_n_; - /** inflow pressure condition */ - Real inflow_pressure_; - Real rho0_; - - /** inflow velocity profile to be defined in applications */ - virtual Vecd getTargetVelocity(Vecd& position, Vecd& velocity) = 0; - /** inflow parameters to be defined in applications */ - virtual void SetInflowParameters() = 0; - - virtual void Update(size_t unsorted_index_i, Real dt = 0.0) override; - }; - - /** - * @class InletOutletInflowCondition - * @brief this function is for inlet-outlet flow - */ - class InletOutletInflowCondition - : public EmitterInflowCondition - { - public: - explicit InletOutletInflowCondition(FluidBody* body, BodyPartByParticle* body_part); - virtual ~InletOutletInflowCondition() {}; - protected: - /** inflow velocity profile to be defined in applications */ - virtual Vecd getTargetVelocity(Vecd& position, Vecd& velocity) = 0; - /** inflow parameters to be defined in applications */ - virtual void SetInflowParameters() = 0; - - virtual void Update(size_t unsorted_index_i, Real dt = 0.0) override; - }; - - /** - * @class EmitterInflowInjecting - * @brief Inject particles into the computational domain. - */ - class EmitterInflowInjecting - : public PartSimpleDynamicsByParticle, public FluidDataSimple - { - public: - explicit EmitterInflowInjecting(FluidBody* body, BodyPartByParticle* body_part, - size_t body_buffer_width, int axis_direction, bool positive); - virtual ~EmitterInflowInjecting() {}; - - /** This class is only implemented in sequential due to memory conflicts. */ - virtual void parallel_exec(Real dt = 0.0) override { exec(); }; - protected: - StdLargeVec& pos_n_; - StdLargeVec& rho_n_, &p_; - const int axis_; /**< the axis direction for bounding*/ - Vecd periodic_translation_; - size_t body_buffer_width_; - BoundingBox body_part_bounds_; - - virtual void checkLowerBound(size_t unsorted_index_i, Real dt = 0.0); - virtual void checkUpperBound(size_t unsorted_index_i, Real dt = 0.0); - ParticleFunctor checking_bound_; - - virtual void Update(size_t unsorted_index_i, Real dt = 0.0) override { - checking_bound_(unsorted_index_i, dt); - }; - }; - - /** - * @class FreeSurfaceProbeOnFluidBody - * @brief Probe the free surface profile for a fluid body part by reduced operation. - */ - class FreeSurfaceProbeOnFluidBody : public PartDynamicsByCellReduce, - public FluidDataSimple - { - public: - FreeSurfaceProbeOnFluidBody(FluidBody* body, BodyPartByCell* body_part) - : PartDynamicsByCellReduce(body, body_part), FluidDataSimple(body), - pos_n_(particles_->pos_n_) - { - quantity_name_ = "FreeSurfaceProbeOnFluidBody"; - initial_reference_ = 0.0; - } - virtual ~FreeSurfaceProbeOnFluidBody() {}; - protected: - StdLargeVec& pos_n_; - virtual void SetupReduce() override {}; - virtual Real ReduceFunction(size_t index_i, Real dt = 0.0) override { return pos_n_[index_i][1]; }; - }; - /** - * @class ColorFunctionGradientInner - * @brief indicate the particles near the interface of a fluid-fluid interaction and computing norm - */ - class ColorFunctionGradientInner : public InteractionDynamics, public FluidDataInner - { - public: - ColorFunctionGradientInner(BaseBodyRelationInner* inner_relation); - virtual ~ColorFunctionGradientInner() {}; - protected: - Real thereshold_by_dimensions_; - StdLargeVec &Vol_; - StdLargeVec& surface_indicator_; - StdLargeVec& color_grad_; - StdLargeVec& surface_norm_; - StdLargeVec &pos_div_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class ColorFunctionGradientInterplationInner - * @brief the viscous force induced acceleration - */ - class ColorFunctionGradientInterplationInner - : public InteractionDynamics, public FluidDataInner - { - public: - ColorFunctionGradientInterplationInner(BaseBodyRelationInner* inner_relation); - virtual ~ColorFunctionGradientInterplationInner() {}; - protected: - Real thereshold_by_dimensions_; - StdLargeVec &Vol_; - StdLargeVec& surface_indicator_; - StdLargeVec& color_grad_; - StdLargeVec& surface_norm_; - StdLargeVec& pos_div_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class SurfaceTensionAccelerationInner - * @brief the viscous force induced acceleration - */ - class SurfaceTensionAccelerationInner - : public InteractionDynamics, public FluidDataInner - { - public: - SurfaceTensionAccelerationInner(BaseBodyRelationInner* inner_relation, Real gamma); - SurfaceTensionAccelerationInner(BaseBodyRelationInner* inner_relation); - virtual ~SurfaceTensionAccelerationInner() {}; - protected: - Real gamma_; - StdLargeVec &Vol_, &mass_; - StdLargeVec &dvel_dt_prior_; - StdLargeVec& surface_indicator_; - StdLargeVec& color_grad_; - StdLargeVec& surface_norm_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - } -} -#endif //FLUID_DYNAMCIS_INNER_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.hpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.hpp deleted file mode 100644 index 2428f35c24..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.hpp +++ /dev/null @@ -1,70 +0,0 @@ -/** - * @file fluid_dynamics_inner.hpp - * @author Chi ZHang and Xiangyu Hu - */ - -#pragma once - -#include "fluid_dynamics_inner.h" - -//=================================================================================================// -namespace SPH -{ -//=================================================================================================// - namespace fluid_dynamics - { - //=================================================================================================// - template - BasePressureRelaxationInner:: - BasePressureRelaxationInner(BaseBodyRelationInner* inner_relation) : - BasePressureRelaxation(inner_relation), - riemann_solver_(*material_, *material_) {} - //=================================================================================================// - template - void BasePressureRelaxationInner::Interaction(size_t index_i, Real dt) - { - FluidState state_i(rho_n_[index_i], vel_n_[index_i], p_[index_i]); - Vecd acceleration = dvel_dt_prior_[index_i]; - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - Real dW_ij = inner_neighborhood.dW_ij_[n]; - Vecd& e_ij = inner_neighborhood.e_ij_[n]; - - FluidState state_j(rho_n_[index_j], vel_n_[index_j], p_[index_j]); - Real p_star = riemann_solver_.getPStar(state_i, state_j, e_ij); - acceleration -= 2.0 * p_star * Vol_[index_j] * dW_ij * e_ij / state_i.rho_; - } - dvel_dt_[index_i] = acceleration; - } - //=================================================================================================// - template - BaseDensityRelaxationInner:: - BaseDensityRelaxationInner(BaseBodyRelationInner* inner_relation) : - BaseDensityRelaxation(inner_relation), - riemann_solver_(*material_, *material_) {} - //=================================================================================================// - template - void BaseDensityRelaxationInner::Interaction(size_t index_i, Real dt) - { - FluidState state_i(rho_n_[index_i], vel_n_[index_i], p_[index_i]); - Real density_change_rate = 0.0; - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - Vecd& e_ij = inner_neighborhood.e_ij_[n]; - Real dW_ij = inner_neighborhood.dW_ij_[n]; - - FluidState state_j(rho_n_[index_j], vel_n_[index_j], p_[index_j]); - Vecd vel_star = riemann_solver_.getVStar(state_i, state_j, e_ij); - density_change_rate += 2.0 * state_i.rho_ * Vol_[index_j] * dot(state_i.vel_ - vel_star, e_ij) * dW_ij; - } - drho_dt_[index_i] = density_change_rate; - }; - //=================================================================================================// - } -//=================================================================================================// -} -//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.cpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.cpp deleted file mode 100644 index 6550e89fba..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/** - * @file fluid_dynamics_multi_phase.cpp - * @author Chi ZHang and Xiangyu Hu - */ - -#include "fluid_dynamics_multi_phase.h" - -//=================================================================================================// -namespace SPH -{ -//=================================================================================================// - namespace fluid_dynamics - { - //=================================================================================================// - ViscousAccelerationMultiPhase::ViscousAccelerationMultiPhase(BaseBodyRelationInner* inner_relation, - BaseBodyRelationContact* contact_relation) - : ViscousAccelerationInner(inner_relation), MultiPhaseContactData(contact_relation) - { - if (inner_relation->sph_body_ != contact_relation->sph_body_) - { - std::cout << "\n Error: the two body_realtions do not have the same source body!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); - contact_vel_n_.push_back(&(contact_particles_[k]->vel_n_)); - } - } - //=================================================================================================// - ViscousAccelerationMultiPhase::ViscousAccelerationMultiPhase(ComplexBodyRelation* complex_relation) - : ViscousAccelerationMultiPhase(complex_relation->inner_relation_, complex_relation->contact_relation_) {} - //=================================================================================================// - void ViscousAccelerationMultiPhase::Interaction(size_t index_i, Real dt) - { - ViscousAccelerationInner::Interaction(index_i, dt); - - Real rho_i = this->rho_n_[index_i]; - Vecd& vel_i = this->vel_n_[index_i]; - - Vecd acceleration(0), vel_derivative(0); - for (size_t k = 0; k < this->contact_configuration_.size(); ++k) - { - Real mu_j = this->contact_material_[k]->ReferenceViscosity(); - StdLargeVec& Vol_k = *(this->contact_Vol_[k]); - StdLargeVec& vel_k = *(this->contact_vel_n_[k]); - Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - Real r_ij = contact_neighborhood.r_ij_[n]; - - vel_derivative = 2.0*(vel_i - vel_k[index_j]) / (r_ij + 0.01 * this->smoothing_length_); - Real mu_ij = 2.0 * this->mu_ * mu_j / (this->mu_ + mu_j); - acceleration += 2.0 * mu_ij * vel_derivative - * contact_neighborhood.dW_ij_[n] * Vol_k[index_j] / rho_i; - } - } - - dvel_dt_prior_[index_i] += acceleration; - } - //=================================================================================================// - MultiPhaseColorFunctionGradient:: - MultiPhaseColorFunctionGradient(BaseBodyRelationContact* contact_relation) : - InteractionDynamics(contact_relation->sph_body_), MultiPhaseData(contact_relation), - rho0_(particles_->rho0_), Vol_(particles_->Vol_), - pos_div_(*particles_->getVariableByName("PositionDivergence")), - surface_indicator_(particles_->surface_indicator_), - color_grad_(*particles_->createAVariable("ColorGradient")), - surface_norm_(*particles_->createAVariable("SurfaceNormal")) - { - for (size_t k = 0; k != contact_particles_.size(); ++k) - { Real rho0_k = contact_particles_[k]->rho0_; - contact_rho0_.push_back(rho0_k); - contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); - } - } - //=================================================================================================// - void MultiPhaseColorFunctionGradient::Interaction(size_t index_i, Real dt) - { - Real vol_i = Vol_[index_i]; - Real pos_div = 0.0; - Vecd gradient(0.0); - if(surface_indicator_[index_i]) - { - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - Real rho0_k = contact_rho0_[k]; - StdLargeVec& contact_vol_k = *(contact_Vol_[k]); - Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - pos_div -= contact_neighborhood.dW_ij_[n] * contact_neighborhood.r_ij_[n] * contact_vol_k[index_j]; - /** Norm of interface.*/ - Real rho_ij = rho0_ / (rho0_ + rho0_k); - Real area_ij = (vol_i * vol_i + contact_vol_k[index_j] * contact_vol_k[index_j]) * contact_neighborhood.dW_ij_[n]; - gradient += rho_ij * area_ij * contact_neighborhood.e_ij_[n] / vol_i; - } - } - } - color_grad_[index_i] = gradient; - surface_norm_[index_i] = gradient / (gradient.norm() + TinyReal); - } - //=================================================================================================// - } -//=================================================================================================// -} -//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.h b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.h deleted file mode 100644 index 71b18edb2c..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.h +++ /dev/null @@ -1,153 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file fluid_dynamics_multi_phase.h -* @brief Here, we define the algorithm classes for the dynamics involving multiple fluids. -* @author Chi ZHang and Xiangyu Hu -*/ - - -#ifndef FLUID_DYNAMICS_MULTI_PHASE_H -#define FLUID_DYNAMICS_MULTI_PHASE_H - - - -#include "fluid_dynamics_complex.h" -namespace SPH -{ - namespace fluid_dynamics - { - typedef DataDelegateContact MultiPhaseContactData; - typedef DataDelegateContact MultiPhaseData; - /** - * @class ViscousAccelerationMultiPhase - * @brief the viscosity force induced acceleration - */ - class ViscousAccelerationMultiPhase : public ViscousAccelerationInner, public MultiPhaseContactData - { - public: - ViscousAccelerationMultiPhase(BaseBodyRelationInner* inner_relation, - BaseBodyRelationContact* contact_relation); - ViscousAccelerationMultiPhase(ComplexBodyRelation* complex_relation); - virtual ~ViscousAccelerationMultiPhase() {}; - protected: - StdVec*> contact_Vol_; - StdVec*> contact_vel_n_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - using ViscousAccelerationMultiPhaseWithWall = - BaseViscousAccelerationWithWall>; - - /** - * @class ViscousAccelerationMultiPhase - * @brief Abstract base class for general multiphase fluid dynamics - */ - template - class RelaxationMultiPhase : public RelaxationInnerType, public MultiPhaseContactData - { - public: - RelaxationMultiPhase(BaseBodyRelationInner* inner_relation, - BaseBodyRelationContact* contact_relation); - virtual ~RelaxationMultiPhase() {}; - protected: - StdVec*> contact_Vol_, contact_p_, contact_rho_n_; - StdVec*> contact_vel_n_; - }; - - /** - * @class BasePressureRelaxationMultiPhase - * @brief template class for multiphase pressure relaxation scheme - */ - template - class BasePressureRelaxationMultiPhase : public RelaxationMultiPhase - { - public: - BasePressureRelaxationMultiPhase(BaseBodyRelationInner* inner_relation, - BaseBodyRelationContact* contact_relation); - BasePressureRelaxationMultiPhase(ComplexBodyRelation* complex_relation); - virtual ~BasePressureRelaxationMultiPhase() {}; - protected: - using CurrentRiemannSolver = decltype(PressureRelaxationInnerType::riemann_solver_); - StdVec riemann_solvers_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - virtual Vecd computeNonConservativeAcceleration(size_t index_i) override; - }; - using MultiPhasePressureRelaxation = BasePressureRelaxationMultiPhase; - using MultiPhasePressureRelaxationRiemann = BasePressureRelaxationMultiPhase; - - using MultiPhasePressureRelaxationWithWall = - BasePressureRelaxationWithWall>; - using MultiPhasePressureRelaxationRiemannWithWall = - BasePressureRelaxationWithWall>; - using ExtendMultiPhasePressureRelaxationRiemannWithWall = - ExtendPressureRelaxationWithWall>; - - - /** - * @class BaseDensityRelaxationMultiPhase - * @brief template class pressure relaxation scheme with wall boundary - */ - template - class BaseDensityRelaxationMultiPhase : public RelaxationMultiPhase - { - public: - BaseDensityRelaxationMultiPhase(BaseBodyRelationInner* inner_relation, - BaseBodyRelationContact* contact_relation); - BaseDensityRelaxationMultiPhase(ComplexBodyRelation* complex_relation); - virtual ~BaseDensityRelaxationMultiPhase() {}; - protected: - using CurrentRiemannSolver = decltype(DensityRelaxationInnerType::riemann_solver_); - StdVec riemann_solvers_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - using MultiPhaseDensityRelaxation = BaseDensityRelaxationMultiPhase; - using MultiPhaseDensityRelaxationRiemann = BaseDensityRelaxationMultiPhase; - using MultiPhaseDensityRelaxationWithWall = BaseDensityRelaxationMultiPhase; - using MultiPhaseDensityRelaxationRiemannWithWall = BaseDensityRelaxationWithWall; - - /** - * @class MultiPhaseColorFunctionGradient - * @brief indicate the particles near the interface of a fluid-fluid interaction and computing norm - */ - class MultiPhaseColorFunctionGradient : public InteractionDynamics, public MultiPhaseData - { - public: - MultiPhaseColorFunctionGradient(BaseBodyRelationContact* contact_relation); - virtual ~MultiPhaseColorFunctionGradient() {}; - protected: - Real rho0_; - StdVec contact_rho0_; - StdLargeVec& Vol_, & pos_div_; - StdLargeVec& surface_indicator_; - StdLargeVec& color_grad_, & surface_norm_; - StdVec*> contact_Vol_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - } -} -#endif //FLUID_DYNAMICS_MULTI_PHASE_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.hpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.hpp deleted file mode 100644 index 3b43803310..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.hpp +++ /dev/null @@ -1,167 +0,0 @@ -/** - * @file fluid_dynamics_multi_phase.hpp - * @author Chi ZHang and Xiangyu Hu - */ - -#pragma once - -#include "fluid_dynamics_multi_phase.h" - -//=================================================================================================// -namespace SPH -{ -//=================================================================================================// - namespace fluid_dynamics - { - //=================================================================================================// - template - RelaxationMultiPhase:: - RelaxationMultiPhase(BaseBodyRelationInner* inner_relation, - BaseBodyRelationContact* contact_relation) : - RelaxationInnerType(inner_relation), MultiPhaseContactData(contact_relation) - { - if (inner_relation->sph_body_ != contact_relation->sph_body_) - { - std::cout << "\n Error: the two body_realtions do not have the same source body!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); - contact_p_.push_back(&(contact_particles_[k]->p_)); - contact_rho_n_.push_back(&(contact_particles_[k]->rho_n_)); - contact_vel_n_.push_back(&(contact_particles_[k]->vel_n_)); - } - } - //=================================================================================================// - template - BasePressureRelaxationMultiPhase:: - BasePressureRelaxationMultiPhase(BaseBodyRelationInner* inner_relation, - BaseBodyRelationContact* contact_relation) : - RelaxationMultiPhase(inner_relation, - contact_relation) - { - for (size_t k = 0; k != this->contact_particles_.size(); ++k) - { - riemann_solvers_.push_back(new CurrentRiemannSolver(*this->material_, *this->contact_material_[k])); - } - } - //=================================================================================================// - template - BasePressureRelaxationMultiPhase:: - BasePressureRelaxationMultiPhase(ComplexBodyRelation* complex_relation) : - BasePressureRelaxationMultiPhase(complex_relation->inner_relation_, - complex_relation->contact_relation_) {} - //=================================================================================================// - template - void BasePressureRelaxationMultiPhase::Interaction(size_t index_i, Real dt) - { - PressureRelaxationInnerType::Interaction(index_i, dt); - - FluidState state_i(this->rho_n_[index_i], this->vel_n_[index_i], this->p_[index_i]); - Vecd acceleration(0.0); - for (size_t k = 0; k < this->contact_configuration_.size(); ++k) - { - StdLargeVec& Vol_k = *(this->contact_Vol_[k]); - StdLargeVec& rho_k = *(this->contact_rho_n_[k]); - StdLargeVec& p_k = *(this->contact_p_[k]); - StdLargeVec& vel_k = *(this->contact_vel_n_[k]); - CurrentRiemannSolver* riemann_solver_k = riemann_solvers_[k]; - Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - Vecd& e_ij = contact_neighborhood.e_ij_[n]; - Real dW_ij = contact_neighborhood.dW_ij_[n]; - - FluidState state_j(rho_k[index_j], vel_k[index_j], p_k[index_j]); - Real p_star = riemann_solver_k->getPStar(state_i, state_j, e_ij); - acceleration -= 2.0 * p_star * e_ij * Vol_k[index_j] * dW_ij / state_i.rho_; - } - } - this->dvel_dt_[index_i] += acceleration; - } - //=================================================================================================// - template - Vecd BasePressureRelaxationMultiPhase:: - computeNonConservativeAcceleration(size_t index_i) - { - Vecd acceleration = PressureRelaxationInnerType::computeNonConservativeAcceleration(index_i); - - Real rho_i = this->rho_n_[index_i]; - Real p_i = this->p_[index_i]; - for (size_t k = 0; k < this->contact_configuration_.size(); ++k) - { - StdLargeVec& rho_k = *(this->contact_rho_n_[k]); - StdLargeVec& p_k = *(this->contact_p_[k]); - Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - Vecd& e_ij = contact_neighborhood.e_ij_[n]; - Real dW_ij = contact_neighborhood.dW_ij_[n]; - - Real rho_j = rho_k[index_j]; - Real p_j = p_k[index_j]; - - Real p_star = (rho_i * p_j + rho_j * p_i) / (rho_i + rho_j); - acceleration += (p_i - p_star) * this->Vol_[index_j] * dW_ij * e_ij / rho_i; - } - } - return acceleration; - } - //=================================================================================================// - template - BaseDensityRelaxationMultiPhase:: - BaseDensityRelaxationMultiPhase(BaseBodyRelationInner* inner_relation, - BaseBodyRelationContact* contact_relation) : - RelaxationMultiPhase(inner_relation, - contact_relation) - { - for (size_t k = 0; k != this->contact_particles_.size(); ++k) - { - riemann_solvers_.push_back(new CurrentRiemannSolver(*this->material_, *this->contact_material_[k])); - } - } - //=================================================================================================// - template - BaseDensityRelaxationMultiPhase:: - BaseDensityRelaxationMultiPhase(ComplexBodyRelation* complex_relation) : - BaseDensityRelaxationMultiPhase(complex_relation->inner_relation_, - complex_relation->contact_relation_) {} - //=================================================================================================// - template - void BaseDensityRelaxationMultiPhase::Interaction(size_t index_i, Real dt) - { - DensityRelaxationInnerType::Interaction(index_i, dt); - - FluidState state_i(this->rho_n_[index_i], this->vel_n_[index_i], this->p_[index_i]); - Real density_change_rate = 0.0; - for (size_t k = 0; k < this->contact_configuration_.size(); ++k) - { - StdLargeVec& Vol_k = *(this->contact_Vol_[k]); - StdLargeVec& rho_k = *(this->contact_rho_n_[k]); - StdLargeVec& p_k = *(this->contact_p_[k]); - StdLargeVec& vel_k = *(this->contact_vel_n_[k]); - CurrentRiemannSolver* riemann_solver_k = riemann_solvers_[k]; - Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - Vecd& e_ij = contact_neighborhood.e_ij_[n]; - Real dW_ij = contact_neighborhood.dW_ij_[n]; - - FluidState state_j(rho_k[index_j], vel_k[index_j], p_k[index_j]); - Vecd vel_star = riemann_solver_k->getVStar(state_i, state_j, e_ij); - density_change_rate += 2.0 * state_i.rho_ * Vol_k[index_j] * dot(state_i.vel_ - vel_star, e_ij) * dW_ij; - } - } - this->drho_dt_[index_i] += density_change_rate; - } - //=================================================================================================// - } -//=================================================================================================// -} -//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/general_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/general_dynamics/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/general_dynamics/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/general_dynamics/general_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/general_dynamics/general_dynamics.cpp deleted file mode 100644 index b26803f7f3..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/general_dynamics/general_dynamics.cpp +++ /dev/null @@ -1,535 +0,0 @@ -/** - * @file general_dynamics.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#include "general_dynamics.h" - -namespace SPH { - //=================================================================================================// - TimeStepInitialization - ::TimeStepInitialization(SPHBody* body, Gravity* gravity) - : ParticleDynamicsSimple(body), GeneralDataDelegateSimple(body), - pos_n_(particles_->pos_n_), dvel_dt_prior_(particles_->dvel_dt_prior_), - gravity_(gravity) - { - } - //=================================================================================================// - void TimeStepInitialization::setupDynamics(Real dt) - { - particles_->total_ghost_particles_ = 0; - } - //=================================================================================================// - void TimeStepInitialization::Update(size_t index_i, Real dt) - { - dvel_dt_prior_[index_i] = gravity_->InducedAcceleration(pos_n_[index_i]); - } - //=================================================================================================// - RandomizePartilePosition::RandomizePartilePosition(SPHBody* body) - : ParticleDynamicsSimple(body), DataDelegateSimple(body), - pos_n_(particles_->pos_n_) - { - randomize_scale_ = body->particle_adaptation_->MinimumSpacing(); - } - //=================================================================================================// - void RandomizePartilePosition::Update(size_t index_i, Real dt) - { - Vecd& pos_n_i = pos_n_[index_i]; - for (int k = 0; k < pos_n_i.size(); ++k) - { - pos_n_i[k] += dt * (((double)rand() / (RAND_MAX)) - 0.5) * 2.0 * randomize_scale_; - } - } - //=================================================================================================// - BoundingInAxisDirection::BoundingInAxisDirection(RealBody* real_body, int axis_direction) : - ParticleDynamics(real_body), DataDelegateSimple(real_body), - axis_(axis_direction), body_domain_bounds_(real_body->getBodyDomainBounds()), - pos_n_(particles_->pos_n_), - mesh_cell_linked_list_(real_body->mesh_cell_linked_list_) - { - Real h_ratio_min = 1.0 / particle_adaptation_->MaximumSpacingRatio(); - cut_off_radius_max_ = particle_adaptation_->getKernel()->CutOffRadius(h_ratio_min); - } - //=================================================================================================// - void PeriodicConditionInAxisDirection:: - setPeriodicTranslation(BoundingBox& body_domain_bounds, int axis_direction) - { - periodic_translation_[axis_direction] = - body_domain_bounds.second[axis_direction] - body_domain_bounds.first[axis_direction]; - } - //=================================================================================================// - PeriodicConditionInAxisDirection:: - PeriodicConditionInAxisDirection(RealBody* real_body, int axis_direction) : - periodic_translation_(0.0) - { - BoundingBox body_domain_bounds = real_body->getBodyDomainBounds(); - setPeriodicTranslation(body_domain_bounds, axis_direction); - bound_cells_.resize(2); - BaseMeshCellLinkedList* mesh_cell_linked_list = real_body->mesh_cell_linked_list_; - mesh_cell_linked_list->tagBodyDomainBoundingCells(bound_cells_, body_domain_bounds, axis_direction); - if (periodic_translation_.norm() < real_body->particle_adaptation_->ReferenceSpacing()) - { - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - std::cout << "\n Periodic bounding failure: bounds not defined!" << std::endl; - exit(1); - } - } - //=================================================================================================// - void PeriodicConditionInAxisDirection::PeriodicBounding::checkLowerBound(size_t index_i, Real dt) - { - if (pos_n_[index_i][axis_] < body_domain_bounds_.first[axis_]) - pos_n_[index_i][axis_] += periodic_translation_[axis_]; - } - //=================================================================================================// - void PeriodicConditionInAxisDirection::PeriodicBounding::checkUpperBound(size_t index_i, Real dt) - { - if (pos_n_[index_i][axis_] > body_domain_bounds_.second[axis_]) - pos_n_[index_i][axis_] -= periodic_translation_[axis_]; - } - //=================================================================================================// - void PeriodicConditionInAxisDirection::PeriodicBounding::exec(Real dt) - { - setupDynamics(dt); - - //check lower bound - CellLists& lower_bound_cells = bound_cells_[0]; - for (size_t i = 0; i != lower_bound_cells.size(); ++i) - { - IndexVector& particle_indexes = lower_bound_cells[i]->real_particle_indexes_; - for (size_t num = 0; num < particle_indexes.size(); ++num) checkLowerBound(particle_indexes[num], dt); - } - - //check upper bound - CellLists& upper_bound_cells = bound_cells_[1]; - for (size_t i = 0; i != upper_bound_cells.size(); ++i) - { - IndexVector& particle_indexes = upper_bound_cells[i]->real_particle_indexes_; - for (size_t num = 0; num < particle_indexes.size(); ++num) checkUpperBound(particle_indexes[num], dt); - } - } - //=================================================================================================// - void PeriodicConditionInAxisDirection::PeriodicBounding::parallel_exec(Real dt) - { - setupDynamics(dt); - - //check lower bound - CellLists& lower_bound_cells = bound_cells_[0]; - parallel_for(blocked_range(0, lower_bound_cells.size()), - [&](const blocked_range& r) { - for (size_t i = r.begin(); i < r.end(); ++i) { - IndexVector& particle_indexes = lower_bound_cells[i]->real_particle_indexes_; - for (size_t num = 0; num < particle_indexes.size(); ++num) checkLowerBound(particle_indexes[num], dt); - } - }, ap); - - //check upper bound - CellLists& upper_bound_cells = bound_cells_[1]; - parallel_for(blocked_range(0, upper_bound_cells.size()), - [&](const blocked_range& r) { - for (size_t i = r.begin(); i < r.end(); ++i) { - IndexVector& particle_indexes = upper_bound_cells[i]->real_particle_indexes_; - for (size_t num = 0; num < particle_indexes.size(); ++num) checkUpperBound(particle_indexes[num], dt); - } - }, ap); - } - //=================================================================================================// - void PeriodicConditionInAxisDirection::PeriodicCondition::exec(Real dt) - { - setupDynamics(dt); - - //check lower bound - CellLists& lower_bound_cells = bound_cells_[0]; - for (size_t i = 0; i != lower_bound_cells.size(); ++i) { - ListDataVector& cell_list_data = lower_bound_cells[i]->cell_list_data_; - for (size_t num = 0; num < cell_list_data.size(); ++num) checkLowerBound(cell_list_data[num], dt); - } - - //check upper bound - CellLists& upper_bound_cells = bound_cells_[1]; - for (size_t i = 0; i != upper_bound_cells.size(); ++i) { - ListDataVector& cell_list_data = upper_bound_cells[i]->cell_list_data_; - for (size_t num = 0; num < cell_list_data.size(); ++num) checkUpperBound(cell_list_data[num], dt); - } - } - //=================================================================================================// - void PeriodicConditionInAxisDirectionUsingCellLinkedList:: - PeriodicCellLinkedList::checkUpperBound(ListData& list_data, Real dt) - { - Vecd particle_position = list_data.second; - if (particle_position[axis_] < body_domain_bounds_.second[axis_] - && particle_position[axis_] > (body_domain_bounds_.second[axis_] - cut_off_radius_max_)) - { - Vecd translated_position = particle_position - periodic_translation_; - /** insert ghost particle to cell linked list */ - mesh_cell_linked_list_->InsertACellLinkedListDataEntry(list_data.first, translated_position); - } - } - //=================================================================================================// - void PeriodicConditionInAxisDirectionUsingCellLinkedList:: - PeriodicCellLinkedList::checkLowerBound(ListData& list_data, Real dt) - { - Vecd particle_position = list_data.second; - if (particle_position[axis_] > body_domain_bounds_.first[axis_] - && particle_position[axis_] < (body_domain_bounds_.first[axis_] + cut_off_radius_max_)) - { - Vecd translated_position = particle_position + periodic_translation_; - /** insert ghost particle to cell linked list */ - mesh_cell_linked_list_->InsertACellLinkedListDataEntry(list_data.first, translated_position); - } - } - //=================================================================================================// - OpenBoundaryConditionInAxisDirection:: - OpenBoundaryConditionInAxisDirection(RealBody* real_body, int axis_direction, bool positive): - particle_type_transfer(this->bound_cells_, real_body, axis_direction, positive) - { - BoundingBox body_domain_bounds = real_body->getBodyDomainBounds(); - bound_cells_.resize(2); - BaseMeshCellLinkedList* mesh_cell_linked_list = real_body->mesh_cell_linked_list_; - mesh_cell_linked_list->tagBodyDomainBoundingCells(bound_cells_, body_domain_bounds, axis_direction); - } - //=================================================================================================// - void OpenBoundaryConditionInAxisDirection :: - ParticleTypeTransfer::checkLowerBound(size_t index_i, Real dt) - { - Vecd particle_position = pos_n_[index_i]; - if (particle_position[axis_] < body_domain_bounds_.first[axis_]) - particles_->switchToBufferParticle(index_i); - } - //=================================================================================================// - void OpenBoundaryConditionInAxisDirection :: - ParticleTypeTransfer::checkUpperBound(size_t index_i, Real dt) - { - Vecd particle_position = pos_n_[index_i]; - if (particle_position[axis_] > body_domain_bounds_.second[axis_]) - particles_->switchToBufferParticle(index_i); - } - //=================================================================================================// - void OpenBoundaryConditionInAxisDirection::ParticleTypeTransfer::exec(Real dt) - { - setupDynamics(dt); - - //check lower bound - CellLists& lower_bound_cells = bound_cells_[0]; - for (size_t i = 0; i != lower_bound_cells.size(); ++i) - { - IndexVector& particle_indexes = lower_bound_cells[i]->real_particle_indexes_; - for (size_t num = 0; num < particle_indexes.size(); ++num) checking_bound_(particle_indexes[num], dt); - } - - //check upper bound - CellLists& upper_bound_cells = bound_cells_[1]; - for (size_t i = 0; i != upper_bound_cells.size(); ++i) - { - IndexVector& particle_indexes = upper_bound_cells[i]->real_particle_indexes_; - for (size_t num = 0; num < particle_indexes.size(); ++num) checking_bound_(particle_indexes[num], dt); - } - } - //=================================================================================================// - void PeriodicConditionInAxisDirectionUsingGhostParticles:: - CreatPeriodicGhostParticles::setupDynamics(Real dt) - { - for (size_t i = 0; i != ghost_particles_.size(); ++i) ghost_particles_[i].clear(); - } - //=================================================================================================// - void PeriodicConditionInAxisDirectionUsingGhostParticles:: - CreatPeriodicGhostParticles::checkLowerBound(size_t index_i, Real dt) - { - Vecd particle_position = pos_n_[index_i]; - if (particle_position[axis_] > body_domain_bounds_.first[axis_] - && particle_position[axis_] < (body_domain_bounds_.first[axis_] + cut_off_radius_max_)) - { - size_t expected_particle_index = particles_->insertAGhostParticle(index_i); - ghost_particles_[0].push_back(expected_particle_index); - Vecd translated_position = particle_position + periodic_translation_; - /** insert ghost particle to cell linked list */ - mesh_cell_linked_list_->InsertACellLinkedListDataEntry(expected_particle_index, translated_position); - } - } - //=================================================================================================// - void PeriodicConditionInAxisDirectionUsingGhostParticles:: - CreatPeriodicGhostParticles::checkUpperBound(size_t index_i, Real dt) - { - Vecd particle_position = pos_n_[index_i]; - if (particle_position[axis_] < body_domain_bounds_.second[axis_] - && particle_position[axis_] > (body_domain_bounds_.second[axis_] - cut_off_radius_max_)) - { - size_t expected_particle_index = particles_->insertAGhostParticle(index_i); - ghost_particles_[1].push_back(expected_particle_index); - Vecd translated_position = particle_position - periodic_translation_; - /** insert ghost particle to cell linked list */ - mesh_cell_linked_list_->InsertACellLinkedListDataEntry(expected_particle_index, translated_position); - } - } - //=================================================================================================// - void PeriodicConditionInAxisDirectionUsingGhostParticles:: - UpdatePeriodicGhostParticles::checkLowerBound(size_t index_i, Real dt) - { - particles_->updateFromAnotherParticle(index_i, sorted_id_[index_i]); - pos_n_[index_i] += periodic_translation_; - } - //=================================================================================================// - void PeriodicConditionInAxisDirectionUsingGhostParticles:: - UpdatePeriodicGhostParticles::checkUpperBound(size_t index_i, Real dt) - { - particles_->updateFromAnotherParticle(index_i, sorted_id_[index_i]); - pos_n_[index_i] -= periodic_translation_; - } - //=================================================================================================// - void PeriodicConditionInAxisDirectionUsingGhostParticles:: - UpdatePeriodicGhostParticles::exec(Real dt) - { - for (size_t i = 0; i != ghost_particles_[0].size(); ++i) - { - checkLowerBound(ghost_particles_[0][i], dt); - } - for (size_t i = 0; i != ghost_particles_[1].size(); ++i) - { - checkUpperBound(ghost_particles_[1][i], dt); - } - } - //=================================================================================================// - void PeriodicConditionInAxisDirectionUsingGhostParticles:: - UpdatePeriodicGhostParticles::parallel_exec(Real dt) - { - parallel_for(blocked_range(0, ghost_particles_[0].size()), - [&](const blocked_range& r) { - for (size_t i = r.begin(); i < r.end(); ++i) { - checkLowerBound(ghost_particles_[0][i], dt); - } - }, ap); - - parallel_for(blocked_range(0, ghost_particles_[1].size()), - [&](const blocked_range& r) { - for (size_t i = r.begin(); i < r.end(); ++i) { - checkUpperBound(ghost_particles_[1][i], dt); - } - }, ap); - } - //=================================================================================================// - MirrorBoundaryConditionInAxisDirection::MirrorBounding - ::MirrorBounding(CellLists& bound_cells, RealBody* real_body, int axis_direction, bool positive) - : BoundingInAxisDirection(real_body, axis_direction), - bound_cells_(bound_cells), vel_n_(particles_->vel_n_) - { - checking_bound_ = positive ? - std::bind(&MirrorBoundaryConditionInAxisDirection::MirrorBounding::checkUpperBound, this, _1, _2) - : std::bind(&MirrorBoundaryConditionInAxisDirection::MirrorBounding::checkLowerBound, this, _1, _2); - } - //=================================================================================================// - MirrorBoundaryConditionInAxisDirection - ::CreatingGhostParticles::CreatingGhostParticles(IndexVector& ghost_particles, - CellLists& bound_cells, RealBody* real_body, int axis_direction, bool positive) - : MirrorBounding(bound_cells, real_body, axis_direction, positive), ghost_particles_(ghost_particles) {} - //=================================================================================================// - MirrorBoundaryConditionInAxisDirection::UpdatingGhostStates - ::UpdatingGhostStates(IndexVector& ghost_particles, CellLists& bound_cells, - RealBody* real_body, int axis_direction, bool positive) - : MirrorBounding(bound_cells, real_body, axis_direction, positive), ghost_particles_(ghost_particles) - { - checking_bound_update_ = positive ? - std::bind(&MirrorBoundaryConditionInAxisDirection::UpdatingGhostStates::checkUpperBound, this, _1, _2) - : std::bind(&MirrorBoundaryConditionInAxisDirection::UpdatingGhostStates::checkLowerBound, this, _1, _2); - } - //=================================================================================================// - void MirrorBoundaryConditionInAxisDirection - ::MirrorBounding::checkLowerBound(size_t index_i, Real dt) - { - if (pos_n_[index_i][axis_] < body_domain_bounds_.first[axis_]) { - mirrorInAxisDirection(index_i, body_domain_bounds_.first, axis_); - } - } - //=================================================================================================// - void MirrorBoundaryConditionInAxisDirection::MirrorBounding - ::checkUpperBound(size_t index_i, Real dt) - { - if (pos_n_[index_i][axis_] > body_domain_bounds_.second[axis_]) { - mirrorInAxisDirection(index_i, body_domain_bounds_.second, axis_); - } - } - //=================================================================================================// - void MirrorBoundaryConditionInAxisDirection::MirrorBounding:: - mirrorInAxisDirection(size_t particle_index_i, Vecd body_bound, int axis_direction) - { - pos_n_[particle_index_i][axis_direction] - = 2.0 * body_bound[axis_direction] - pos_n_[particle_index_i][axis_direction]; - vel_n_[particle_index_i][axis_direction] *= -1.0; - } - //=================================================================================================// - void MirrorBoundaryConditionInAxisDirection::MirrorBounding::exec(Real dt) - { - setupDynamics(dt); - for (size_t i = 0; i != bound_cells_.size(); ++i) { - ListDataVector& list_data = bound_cells_[i]->cell_list_data_; - for (size_t num = 0; num < list_data.size(); ++num) checking_bound_(list_data[num].first, dt); - } - } - //=================================================================================================// - void MirrorBoundaryConditionInAxisDirection::MirrorBounding ::parallel_exec(Real dt) - { - setupDynamics(dt); - parallel_for(blocked_range(0, bound_cells_.size()), - [&](const blocked_range& r) { - for (size_t i = r.begin(); i < r.end(); ++i) { - ListDataVector& list_data = bound_cells_[i]->cell_list_data_; - for (size_t num = 0; num < list_data.size(); ++num) checking_bound_(list_data[num].first, dt); - } - }, ap); - } - //=================================================================================================// - void MirrorBoundaryConditionInAxisDirection - ::CreatingGhostParticles::checkLowerBound(size_t index_i, Real dt) - { - Vecd particle_position = pos_n_[index_i]; - if (particle_position[axis_] > body_domain_bounds_.first[axis_] - && particle_position[axis_] < (body_domain_bounds_.first[axis_] + cut_off_radius_max_)) - { - size_t expected_particle_index = particles_->insertAGhostParticle(index_i); - ghost_particles_.push_back(expected_particle_index); - /** mirror boundary condition */ - mirrorInAxisDirection(expected_particle_index, body_domain_bounds_.first, axis_); - Vecd translated_position = particles_->pos_n_[expected_particle_index]; - /** insert ghost particle to cell linked list */ - mesh_cell_linked_list_->InsertACellLinkedListDataEntry(expected_particle_index, translated_position); - } - } - //=================================================================================================// - void MirrorBoundaryConditionInAxisDirection - ::CreatingGhostParticles::checkUpperBound(size_t index_i, Real dt) - { - Vecd particle_position = pos_n_[index_i]; - if (particle_position[axis_] < body_domain_bounds_.second[axis_] - && particle_position[axis_] > (body_domain_bounds_.second[axis_] - cut_off_radius_max_)) - { - size_t expected_particle_index = particles_->insertAGhostParticle(index_i); - ghost_particles_.push_back(expected_particle_index); - /** mirror boundary condition */ - mirrorInAxisDirection(expected_particle_index, body_domain_bounds_.second, axis_); - Vecd translated_position = particles_->pos_n_[expected_particle_index]; - /** insert ghost particle to cell linked list */ - mesh_cell_linked_list_->InsertACellLinkedListDataEntry(expected_particle_index, translated_position); - } - } - //=================================================================================================// - void MirrorBoundaryConditionInAxisDirection::UpdatingGhostStates - ::checkLowerBound(size_t index_i, Real dt) - { - particles_->updateFromAnotherParticle(index_i, sorted_id_[index_i]); - mirrorInAxisDirection(index_i, body_domain_bounds_.first, axis_); - } - //=================================================================================================// - void MirrorBoundaryConditionInAxisDirection::UpdatingGhostStates - ::checkUpperBound(size_t index_i, Real dt) - { - particles_->updateFromAnotherParticle(index_i, sorted_id_[index_i]); - mirrorInAxisDirection(index_i, body_domain_bounds_.second, axis_); - } - //=================================================================================================// - void MirrorBoundaryConditionInAxisDirection::UpdatingGhostStates - ::exec(Real dt) - { - for (size_t i = 0; i != ghost_particles_.size(); ++i) { - checking_bound_update_(ghost_particles_[i], dt); - } - } - //=================================================================================================// - void MirrorBoundaryConditionInAxisDirection::UpdatingGhostStates - ::parallel_exec(Real dt) - { - parallel_for(blocked_range(0, ghost_particles_.size()), - [&](const blocked_range& r) { - for (size_t i = r.begin(); i < r.end(); ++i) { - checking_bound_update_(ghost_particles_[i], dt); - } - }, ap); - } - //=================================================================================================// - VelocityBoundCheck:: - VelocityBoundCheck(SPHBody* body, Real velocity_bound) - : ParticleDynamicsReduce(body), - GeneralDataDelegateSimple(body), - vel_n_(particles_->vel_n_), velocity_bound_(velocity_bound) - { - initial_reference_ = false; - } - //=================================================================================================// - bool VelocityBoundCheck::ReduceFunction(size_t index_i, Real dt) - { - return vel_n_[index_i].norm() > velocity_bound_; - } - //=================================================================================================// - UpperFrontInXDirection:: - UpperFrontInXDirection(SPHBody* body) : - ParticleDynamicsReduce(body), - GeneralDataDelegateSimple(body), - pos_n_(particles_->pos_n_) - { - quantity_name_ = "UpperFrontInXDirection"; - initial_reference_ = 0.0; - } - //=================================================================================================// - Real UpperFrontInXDirection::ReduceFunction(size_t index_i, Real dt) - { - return pos_n_[index_i][0]; - } - //=================================================================================================// - MaximumSpeed:: - MaximumSpeed(SPHBody* body) : - ParticleDynamicsReduce(body), - GeneralDataDelegateSimple(body), - vel_n_(particles_->vel_n_) - { - quantity_name_ = "MaximumSpeed"; - initial_reference_ = 0.0; - } - //=================================================================================================// - Real MaximumSpeed::ReduceFunction(size_t index_i, Real dt) - { - return vel_n_[index_i].norm(); - } - //=================================================================================================// - BodyLowerBound::BodyLowerBound(SPHBody* body) - : ParticleDynamicsReduce(body), - GeneralDataDelegateSimple(body), - pos_n_(particles_->pos_n_) - { - constexpr double max_real_number = (std::numeric_limits::max)(); - initial_reference_ = Vecd(max_real_number); - } - //=================================================================================================// - Vecd BodyLowerBound::ReduceFunction(size_t index_i, Real dt) - { - return pos_n_[index_i]; - } - //=================================================================================================// - BodyUpperBound:: - BodyUpperBound(SPHBody* body) : - ParticleDynamicsReduce(body), - GeneralDataDelegateSimple(body), - pos_n_(particles_->pos_n_) - { - constexpr double min_real_number = (std::numeric_limits::min)(); - initial_reference_ = Vecd(min_real_number); - } - //=================================================================================================// - Vecd BodyUpperBound::ReduceFunction(size_t index_i, Real dt) - { - return pos_n_[index_i]; - } - //=================================================================================================// - TotalMechanicalEnergy::TotalMechanicalEnergy(SPHBody* body, Gravity* gravity) - : ParticleDynamicsReduce>(body), - GeneralDataDelegateSimple(body), mass_(particles_->mass_), - vel_n_(particles_->vel_n_), pos_n_(particles_->pos_n_), gravity_(gravity) - { - quantity_name_ = "TotalMechanicalEnergy"; - initial_reference_ = 0.0; - } - //=================================================================================================// - Real TotalMechanicalEnergy::ReduceFunction(size_t index_i, Real dt) - { - return 0.5 * mass_[index_i] * vel_n_[index_i].normSqr() - + mass_[index_i] * gravity_->getPotential(pos_n_[index_i]); - } - //=================================================================================================// -} -//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/general_dynamics/general_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/general_dynamics/general_dynamics.h deleted file mode 100644 index 210575b953..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/general_dynamics/general_dynamics.h +++ /dev/null @@ -1,547 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file general_dynamics.h -* @brief This is the particle dynamics aplliable for all type bodies -* @author Chi ZHang and Xiangyu Hu -*/ - - -#ifndef GENERAL_DYNAMICS_H -#define GENERAL_DYNAMICS_H - - - -#include "all_particle_dynamics.h" - -#include - -namespace SPH -{ - typedef DataDelegateSimple GeneralDataDelegateSimple; - typedef DataDelegateContact GeneralDataDelegateContact; - /** - * @class TimeStepInitialization - * @brief initialize a time step for a body. - * including initialize particle acceleration - * induced by viscous, gravity and other forces, - * set the number of ghost particles into zero. - */ - class TimeStepInitialization - : public ParticleDynamicsSimple, public GeneralDataDelegateSimple - { - public: - TimeStepInitialization(SPHBody* body, Gravity* gravity = new Gravity(Vecd(0))); - virtual ~TimeStepInitialization() {}; - protected: - StdLargeVec& pos_n_,& dvel_dt_prior_; - Gravity* gravity_; - virtual void setupDynamics(Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class RandomizePartilePosition - * @brief Randomize the initial particle position - */ - class RandomizePartilePosition - : public ParticleDynamicsSimple, public GeneralDataDelegateSimple - { - public: - RandomizePartilePosition(SPHBody* body); - virtual ~RandomizePartilePosition() {}; - protected: - StdLargeVec& pos_n_; - Real randomize_scale_; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class BoundingInAxisDirection - * @brief Bounding particle position in a axis direction. - * The axis_direction must be 0, 1 for 2d and 0, 1, 2 for 3d - */ - class BoundingInAxisDirection : - public ParticleDynamics, public GeneralDataDelegateSimple - { - protected: - const int axis_; /**< the axis directions for bounding*/ - BoundingBox body_domain_bounds_; /**< lower and upper bound for checking. */ - StdLargeVec& pos_n_; - BaseMeshCellLinkedList* mesh_cell_linked_list_; - Real cut_off_radius_max_; /**< maximum cut off radius to avoid boundary particle depletion */ - public: - BoundingInAxisDirection(RealBody* real_body, int axis_direction); - virtual ~BoundingInAxisDirection() {}; - }; - - /** - * @class PeriodicConditionInAxisDirection - * @brief Base class for two different type periodic boundary conditions. - */ - class PeriodicConditionInAxisDirection - { - protected: - Vecd periodic_translation_; - StdVec bound_cells_; - void setPeriodicTranslation(BoundingBox& body_domain_bounds, int axis_direction); - - /** - * @class PeriodicBounding - * @brief Periodic bounding particle position in an axis direction - */ - class PeriodicBounding : public BoundingInAxisDirection - { - protected: - Vecd& periodic_translation_; - StdVec& bound_cells_; - - virtual void checkLowerBound(size_t index_i, Real dt = 0.0); - virtual void checkUpperBound(size_t index_i, Real dt = 0.0); - public: - PeriodicBounding(Vecd& periodic_translation, - StdVec& bound_cells, RealBody* real_body, int axis_direction) : - BoundingInAxisDirection(real_body, axis_direction), periodic_translation_(periodic_translation), - bound_cells_(bound_cells) {}; - virtual ~PeriodicBounding() {}; - - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override; - }; - - /** - * @class PeriodicCondition - * @brief implement periodic condition in an axis direction - */ - class PeriodicCondition : public BoundingInAxisDirection - { - protected: - Vecd& periodic_translation_; - StdVec& bound_cells_; - - virtual void checkLowerBound(ListData& list_data, Real dt = 0.0) = 0; - virtual void checkUpperBound(ListData& list_data, Real dt = 0.0) = 0; - public: - PeriodicCondition(Vecd& periodic_translation, - StdVec& bound_cells, RealBody* real_body, int axis_direction) : - BoundingInAxisDirection(real_body, axis_direction), periodic_translation_(periodic_translation), - bound_cells_(bound_cells) {}; - virtual ~PeriodicCondition() {}; - - /** This class is only implemented in sequential due to memory conflicts. - * Because the cell list data is not concurrent vector. - */ - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override { exec(); }; - }; - - public: - PeriodicConditionInAxisDirection(RealBody* real_body, int axis_direction); - virtual ~PeriodicConditionInAxisDirection() {}; - }; - - /** - * @class PeriodicConditionInAxisDirectionUsingCellLinkedList - * @brief The method imposing periodic boundary condition in an axis direction. - * It includes two different steps, i.e. imposing periodic bounding and condition. - * The first step is carried out before update cell linked list and - * the second after the updating. - * If the exec or parallel_exec is called directly, error message will be given. - */ - class PeriodicConditionInAxisDirectionUsingCellLinkedList : - public PeriodicConditionInAxisDirection - { - protected: - /** - * @class PeriodicCondition - * @brief Periodic boundary condition in an axis direction - */ - class PeriodicCellLinkedList : public PeriodicCondition - { - protected: - virtual void checkLowerBound(ListData& list_data, Real dt = 0.0) override; - virtual void checkUpperBound(ListData& list_data, Real dt = 0.0) override; - public: - - PeriodicCellLinkedList(Vecd& periodic_translation, - StdVec& bound_cells, RealBody* real_body, int axis_direction) - : PeriodicCondition(periodic_translation, bound_cells, real_body, axis_direction) {}; - virtual ~PeriodicCellLinkedList() {}; - }; - - public: - PeriodicConditionInAxisDirectionUsingCellLinkedList(RealBody* real_body, int axis_direction) : - PeriodicConditionInAxisDirection(real_body, axis_direction), - bounding_(this->periodic_translation_, this->bound_cells_, real_body, axis_direction), - update_cell_linked_list_(this->periodic_translation_, this->bound_cells_, real_body, axis_direction) {}; - virtual ~PeriodicConditionInAxisDirectionUsingCellLinkedList() {}; - - PeriodicBounding bounding_; - PeriodicCellLinkedList update_cell_linked_list_; - }; - - /** - * @class OpenBoundaryConditionInAxisDirection - * @brief In open boundary case, we transfer fluid particles to buffer paritcles at outlet - * @brief int axis_direction is used to choose direction in coordinate - * @brief bool positive is used to choose upper or lower bound in your choosed direction - */ - class OpenBoundaryConditionInAxisDirection - { - protected: - StdVec bound_cells_; - - class ParticleTypeTransfer : public BoundingInAxisDirection - { - protected: - StdVec&bound_cells_; - ParticleFunctor checking_bound_; - virtual void checkLowerBound(size_t index_i, Real dt = 0.0) ; - virtual void checkUpperBound(size_t index_i, Real dt = 0.0) ; - public: - ParticleTypeTransfer(StdVec& bound_cells, RealBody* real_body, int axis_direction, bool positive) : - BoundingInAxisDirection(real_body, axis_direction), - bound_cells_(bound_cells) - { - checking_bound_ = positive ? - std::bind(&OpenBoundaryConditionInAxisDirection::ParticleTypeTransfer::checkUpperBound, this, _1, _2) - : std::bind(&OpenBoundaryConditionInAxisDirection::ParticleTypeTransfer::checkLowerBound, this, _1, _2); - }; - virtual ~ParticleTypeTransfer() {}; - - /** This class is only implemented in sequential due to memory conflicts. - * Because the cell list data is not concurrent vector. - */ - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override { exec(); }; - }; - - public: - OpenBoundaryConditionInAxisDirection(RealBody* real_body, int axis_direction, bool positive); - virtual ~OpenBoundaryConditionInAxisDirection() {}; - - ParticleTypeTransfer particle_type_transfer; - }; - - /** - * @class PeriodicConditionInAxisDirectionUsingGhostParticles - * @brief The method imposing periodic boundary condition in an axis direction by using ghost particles. - * It includes three different steps, i.e. imposing periodic bounding, creating ghosts and update ghost state. - * The first step is carried out before update cell linked list and - * the second and third after the updating. - * If the exec or parallel_exec is called directly, error message will be given. - * Note that, currently, this class is not for periodic condition in combined directions, - * such as periodic condition in both x and y directions. - */ - class PeriodicConditionInAxisDirectionUsingGhostParticles : - public PeriodicConditionInAxisDirection - { - protected: - StdVec ghost_particles_; - - /** - * @class CreatPeriodicGhostParticles - * @brief create ghost particles in an axis direction - */ - class CreatPeriodicGhostParticles : public PeriodicBounding - { - protected: - StdVec& ghost_particles_; - virtual void setupDynamics(Real dt = 0.0) override; - virtual void checkLowerBound(size_t index_i, Real dt = 0.0) override; - virtual void checkUpperBound(size_t index_i, Real dt = 0.0) override; - public: - CreatPeriodicGhostParticles(Vecd& periodic_translation, StdVec& bound_cells, - StdVec& ghost_particles, RealBody* real_body, int axis_direction) : - PeriodicBounding(periodic_translation, bound_cells, real_body, axis_direction), - ghost_particles_(ghost_particles) {}; - virtual ~CreatPeriodicGhostParticles() {}; - - /** This class is only implemented in sequential due to memory conflicts. - * Because creating ghost particle allocate memory. - */ - virtual void parallel_exec(Real dt = 0.0) override { exec(); }; - }; - - /** - * @class UpdatePeriodicGhostParticles - * @brief update ghost particles in an axis direction - */ - class UpdatePeriodicGhostParticles : public PeriodicBounding - { - protected: - StdVec& ghost_particles_; - void checkLowerBound(size_t index_i, Real dt = 0.0) override; - void checkUpperBound(size_t index_i, Real dt = 0.0) override; - public: - UpdatePeriodicGhostParticles(Vecd& periodic_translation, StdVec& bound_cells, - StdVec& ghost_particles, RealBody* real_body, int axis_direction) : - PeriodicBounding(periodic_translation, bound_cells, real_body, axis_direction), - ghost_particles_(ghost_particles) {}; - virtual ~UpdatePeriodicGhostParticles() {}; - - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override; - }; - public: - PeriodicConditionInAxisDirectionUsingGhostParticles(RealBody* real_body, int axis_direction) : - PeriodicConditionInAxisDirection(real_body, axis_direction), - bounding_(this->periodic_translation_, this->bound_cells_, real_body, axis_direction), - ghost_creation_(this->periodic_translation_, this->bound_cells_, this->ghost_particles_, real_body, axis_direction), - ghost_update_(this->periodic_translation_, this->bound_cells_, this->ghost_particles_, real_body, axis_direction) - { - ghost_particles_.resize(2); - }; - - virtual ~PeriodicConditionInAxisDirectionUsingGhostParticles() {}; - - PeriodicBounding bounding_; - CreatPeriodicGhostParticles ghost_creation_; - UpdatePeriodicGhostParticles ghost_update_; - }; - - /** - * @class MirrorBoundaryConditionInAxisDirection - * @brief Mirror bounding particle position and velocity in an axis direction - * Note that, currently, this class is not for mirror condition in combined directions, - * such as mirror condition in both x and y directions. - */ - class MirrorBoundaryConditionInAxisDirection : public BoundingInAxisDirection - { - protected: - CellLists bound_cells_; - IndexVector ghost_particles_; - - class MirrorBounding : public BoundingInAxisDirection - { - protected: - CellLists& bound_cells_; - virtual void checkLowerBound(size_t index_i, Real dt = 0.0); - virtual void checkUpperBound(size_t index_i, Real dt = 0.0); - ParticleFunctor checking_bound_; - - StdLargeVec& vel_n_; - void mirrorInAxisDirection(size_t particle_index_i, Vecd body_bound, int axis_direction); - public: - MirrorBounding(CellLists& bound_cells, RealBody* real_body, int axis_direction, bool positive); - virtual ~MirrorBounding() {}; - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override; - }; - - /** - * @class CreatingGhostParticles - * @brief ghost particle created according to its corresponding real particle - */ - class CreatingGhostParticles : public MirrorBounding - { - protected: - IndexVector& ghost_particles_; - virtual void setupDynamics(Real dt = 0.0) override { ghost_particles_.clear(); }; - virtual void checkLowerBound(size_t index_i, Real dt = 0.0) override; - virtual void checkUpperBound(size_t index_i, Real dt = 0.0) override; - public: - CreatingGhostParticles(IndexVector& ghost_particles, CellLists& bound_cells, - RealBody* real_body, int axis_direction, bool positive); - virtual ~CreatingGhostParticles() {}; - /** This class is only implemented in sequential due to memory conflicts. */ - virtual void parallel_exec(Real dt = 0.0) override { exec(); }; - }; - - /** - * @class UpdatingGhostStates - * @brief the state of a ghost particle updated according to its corresponding real particle - */ - class UpdatingGhostStates : public MirrorBounding - { - protected: - IndexVector& ghost_particles_; - void checkLowerBound(size_t index_i, Real dt = 0.0) override; - void checkUpperBound(size_t index_i, Real dt = 0.0) override; - ParticleFunctor checking_bound_update_; - public: - UpdatingGhostStates(IndexVector& ghost_particles, CellLists& bound_cells, - RealBody* real_body, int axis_direction, bool positive); - virtual ~UpdatingGhostStates() {}; - - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override; - }; - - public: - MirrorBounding bounding_; - CreatingGhostParticles creating_ghost_particles_; - UpdatingGhostStates updating_ghost_states_; - - MirrorBoundaryConditionInAxisDirection(RealBody* real_body, int axis_direction, bool positive); - virtual ~MirrorBoundaryConditionInAxisDirection() {}; - - virtual void exec(Real dt = 0.0) override {}; - virtual void parallel_exec(Real dt = 0.0) override {}; - }; - - /** - * @class VelocityBoundCheck - * @brief check whether particle velocity within a given bound - */ - class VelocityBoundCheck : - public ParticleDynamicsReduce, - public GeneralDataDelegateSimple - { - public: - VelocityBoundCheck(SPHBody* body, Real velocity_bound); - virtual ~VelocityBoundCheck() {}; - protected: - StdLargeVec& vel_n_; - Real velocity_bound_; - bool ReduceFunction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class UpperFrontInXDirection - * @brief Get the upper front In X Direction for a SPH body - */ - class UpperFrontInXDirection : - public ParticleDynamicsReduce, - public GeneralDataDelegateSimple - { - public: - explicit UpperFrontInXDirection(SPHBody* body); - virtual ~UpperFrontInXDirection() {}; - protected: - StdLargeVec& pos_n_; - Real ReduceFunction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class MaximumSpeed - * @brief Get the maximum particle speed in a SPH body - */ - class MaximumSpeed : - public ParticleDynamicsReduce, - public GeneralDataDelegateSimple - { - public: - explicit MaximumSpeed(SPHBody* body); - virtual ~MaximumSpeed() {}; - protected: - StdLargeVec& vel_n_; - Real ReduceFunction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class BodyLowerBound - * @brief the lower bound of a body by reduced particle positions. - */ - class BodyLowerBound : - public ParticleDynamicsReduce, - public GeneralDataDelegateSimple - { - public: - explicit BodyLowerBound(SPHBody* body); - virtual ~BodyLowerBound() {}; - protected: - StdLargeVec& pos_n_; - Vecd ReduceFunction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class BodyUpperBound - * @brief the upper bound of a body by reduced particle positions. - */ - class BodyUpperBound : - public ParticleDynamicsReduce, - public GeneralDataDelegateSimple - { - public: - explicit BodyUpperBound(SPHBody* body); - virtual ~BodyUpperBound() {}; - protected: - StdLargeVec& pos_n_; - Vecd ReduceFunction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class BodySummation - * @brief Compute the summation of a particle variable in a body - */ - template - class BodySummation : - public ParticleDynamicsReduce>, - public GeneralDataDelegateSimple - { - public: - explicit BodySummation(SPHBody* body, std::string variable_name) : - ParticleDynamicsReduce>(body), - GeneralDataDelegateSimple(body), - variable_(*particles_->getVariableByName(variable_name)) - { - this->initial_reference_ = VariableType(0); - }; - virtual ~BodySummation() {}; - protected: - StdLargeVec& variable_; - VariableType ReduceFunction(size_t index_i, Real dt = 0.0) override - { - return variable_[index_i]; - }; - }; - - /** - * @class BodyMoment - * @brief Compute the moment of a body - */ - template - class BodyMoment : public BodySummation - { - public: - explicit BodyMoment(SPHBody* body, std::string variable_name) : - BodySummation(body, variable_name), - mass_(this->particles_->mass_) {}; - virtual ~BodyMoment() {}; - protected: - StdLargeVec& mass_; - VariableType ReduceFunction(size_t index_i, Real dt = 0.0) override - { - return mass_[index_i] * this->variable_[index_i]; - }; - }; - - /** - * @class TotalMechanicalEnergy - * @brief Compute the total mechanical (kinematic and potential) energy - */ - class TotalMechanicalEnergy - : public ParticleDynamicsReduce>, public GeneralDataDelegateSimple - { - public: - explicit TotalMechanicalEnergy(SPHBody* body, Gravity* gravity); - virtual ~TotalMechanicalEnergy() {}; - protected: - StdLargeVec& mass_; - StdLargeVec& vel_n_, & pos_n_; - Gravity* gravity_; - Real ReduceFunction(size_t index_i, Real dt = 0.0) override; - }; -} -#endif //GENERAL_DYNAMICS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/observer_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/observer_dynamics/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/observer_dynamics/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/observer_dynamics/observer_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/observer_dynamics/observer_dynamics.cpp deleted file mode 100644 index 30837a056f..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/observer_dynamics/observer_dynamics.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/** - * @file observer_dynamics.cpp - * @brief Here, Functions defined in observer_dyanmcis.h are detailed. - * @author Chi ZHang and Xiangyu Hu - */ - -#include "observer_dynamics.h" -//=================================================================================================// -using namespace SimTK; -//=================================================================================================// -namespace SPH -{ -//=================================================================================================// - namespace observer_dynamics - { - //=================================================================================================// - CorrectInterpolationKernelWeights:: - CorrectInterpolationKernelWeights(BaseBodyRelationContact* body_contact_relation) : - InteractionDynamics(body_contact_relation->sph_body_), - InterpolationContactData(body_contact_relation) - { - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); - } - } - //=================================================================================================// - void CorrectInterpolationKernelWeights::Interaction(size_t index_i, Real dt) - { - Vecd weight_correction(0.0); - Matd local_configuration(Eps); // small number added to diagonal to avoid divide zero - // Compute the first order consistent kernel weights - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - StdLargeVec& Vol_k = *(contact_Vol_[k]); - Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - Real weight_j = contact_neighborhood.W_ij_[n] * Vol_k[index_j]; - Vecd r_ji = -contact_neighborhood.r_ij_[n] * contact_neighborhood.e_ij_[n]; - Vecd gradw_ij = contact_neighborhood.dW_ij_[n] * contact_neighborhood.e_ij_[n]; - - weight_correction += r_ji * weight_j; - local_configuration += Vol_k[index_j] * SimTK::outer(r_ji, gradw_ij); - } - } - - // correction matrix for interacting configuration - Matd B_ = SimTK::inverse(local_configuration); - - // Add the kernel weight correction to W_ij_ of neighboring particles. - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - Vecd normalized_weight_correction = B_ * weight_correction; - contact_neighborhood.W_ij_[n] - -= dot(normalized_weight_correction, contact_neighborhood.e_ij_[n]) - * contact_neighborhood.dW_ij_[n]; - } - } - } - //=================================================================================================// - } -//=================================================================================================// -} - diff --git a/SPHINXsys/src/shared/particle_dynamics/observer_dynamics/observer_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/observer_dynamics/observer_dynamics.h deleted file mode 100644 index b0adccac46..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/observer_dynamics/observer_dynamics.h +++ /dev/null @@ -1,123 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file observer_dynamics.h - * @brief There are the classes for observser bodies to record the state of the flow or - * solid in given locations. Mostly, this is done by an interpolation algorithm. - * @author Xiangyu Hu and Chi Zhang - */ - - -#ifndef OBSERVER_DYNAMICS_H -#define OBSERVER_DYNAMICS_H - - - -#include "all_particle_dynamics.h" - -namespace SPH -{ - namespace observer_dynamics - { - typedef DataDelegateContact InterpolationContactData; - - /** - * @class InterpolatingAQuantity - * @brief Interpolate a given member data in the particles of a general body - */ - template - class InterpolatingAQuantity : public InteractionDynamics, public InterpolationContactData - { - public: - explicit InterpolatingAQuantity(BaseBodyRelationContact* body_contact_relation, std::string variable_name) : - InteractionDynamics(body_contact_relation->sph_body_), InterpolationContactData(body_contact_relation), - interpolated_quantities_(*particles_->createAVariable(variable_name)) - { - prepareContactData(variable_name); - }; - explicit InterpolatingAQuantity(BaseBodyRelationContact* body_contact_relation, - std::string interpolated_variable, std::string target_variable) : - InteractionDynamics(body_contact_relation->sph_body_), InterpolationContactData(body_contact_relation), - interpolated_quantities_(*particles_->getVariableByName(interpolated_variable)) - { - prepareContactData(target_variable); - }; - virtual ~InterpolatingAQuantity() {}; - - protected: - StdLargeVec& interpolated_quantities_; - StdVec*> contact_Vol_; - StdVec*> contact_data_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override - { - VariableType observed_quantity(0); - Real ttl_weight(0); - - for (size_t k = 0; k < this->contact_configuration_.size(); ++k) - { - StdLargeVec& Vol_k = *(contact_Vol_[k]); - StdLargeVec& data_k = *(contact_data_[k]); - Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - Real weight_j = contact_neighborhood.W_ij_[n] * Vol_k[index_j]; - - observed_quantity += weight_j * data_k[index_j]; - ttl_weight += weight_j; - } - } - interpolated_quantities_[index_i] = observed_quantity / (ttl_weight + TinyReal); - }; - - void prepareContactData(const std::string& variable_name) - { - for (size_t k = 0; k != this->contact_particles_.size(); ++k) - { - contact_Vol_.push_back(&(this->contact_particles_[k]->Vol_)); - StdLargeVec* contact_data = - this->contact_particles_[k]->template getVariableByName(variable_name); - contact_data_.push_back(contact_data); - } - } - }; - - /** - * @class CorrectInterpolationKernelWeights - * @brief correct kernel weights for interpolation between general bodies - */ - class CorrectInterpolationKernelWeights : - public InteractionDynamics, public InterpolationContactData - { - public: - CorrectInterpolationKernelWeights(BaseBodyRelationContact* body_contact_relation); - virtual ~CorrectInterpolationKernelWeights() {}; - protected: - StdVec*> contact_Vol_; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - } -} -#endif //OBSERVER_DYNAMICS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_algorithms.cpp b/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_algorithms.cpp deleted file mode 100644 index c4f9bb31c2..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_algorithms.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/** -* @file particle_dynamics_algorithms.cpp -* @brief This is the implementation of the template class particle dynamics algorithms -* @author Chi ZHang and Xiangyu Hu -*/ - -#include "particle_dynamics_algorithms.h" - -//=================================================================================================// -namespace SPH { - //=================================================================================================// - void ParticleDynamicsSimple::exec(Real dt) - { - setBodyUpdated(); - setupDynamics(dt); - size_t total_real_particles = base_particles_->total_real_particles_; - ParticleIterator(total_real_particles, functor_update_, dt); - } - //=================================================================================================// - void ParticleDynamicsSimple::parallel_exec(Real dt) - { - setBodyUpdated(); - setupDynamics(dt); - size_t total_real_particles = base_particles_->total_real_particles_; - ParticleIterator_parallel(total_real_particles, functor_update_, dt); - } - //=================================================================================================// - void InteractionDynamics::exec(Real dt) - { - setBodyUpdated(); - setupDynamics(dt); - for (size_t k = 0; k < pre_processes_.size(); ++k) pre_processes_[k]->exec(dt); - size_t total_real_particles = base_particles_->total_real_particles_; - ParticleIterator(total_real_particles, functor_interaction_, dt); - for (size_t k = 0; k < post_processes_.size(); ++k) post_processes_[k]->exec(dt); - } - //=================================================================================================// - void InteractionDynamics::parallel_exec(Real dt) - { - setBodyUpdated(); - setupDynamics(dt); - for (size_t k = 0; k < pre_processes_.size(); ++k) pre_processes_[k]->parallel_exec(dt); - size_t total_real_particles = base_particles_->total_real_particles_; - ParticleIterator_parallel(total_real_particles, functor_interaction_, dt); - for (size_t k = 0; k < post_processes_.size(); ++k) post_processes_[k]->parallel_exec(dt); - } - //=================================================================================================// - CombinedInteractionDynamics:: - CombinedInteractionDynamics(InteractionDynamics& dynamics_a, InteractionDynamics& dynamics_b) : - InteractionDynamics(dynamics_a.sph_body_), - dynamics_a_(dynamics_a), dynamics_b_(dynamics_b) - { - if (dynamics_a.sph_body_ != dynamics_b.sph_body_) - { - std::cout << "\n Error: CombinedInteractionDynamics does not have the same source body!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - - for (size_t k = 0; k < dynamics_a.pre_processes_.size(); ++k) - pre_processes_.push_back(dynamics_a.pre_processes_[k]); - for (size_t k = 0; k < dynamics_b.pre_processes_.size(); ++k) - pre_processes_.push_back(dynamics_b.pre_processes_[k]); - - for (size_t k = 0; k < dynamics_a.post_processes_.size(); ++k) - post_processes_.push_back(dynamics_a.post_processes_[k]); - for (size_t k = 0; k < dynamics_b.post_processes_.size(); ++k) - post_processes_.push_back(dynamics_b.post_processes_[k]); - } - //=================================================================================================// - void CombinedInteractionDynamics::setupDynamics(Real dt) - { - dynamics_a_.setupDynamics(dt); - dynamics_b_.setupDynamics(dt); - } - //=================================================================================================// - void CombinedInteractionDynamics::Interaction(size_t index_i, Real dt) - { - dynamics_a_.Interaction(index_i, dt); - dynamics_b_.Interaction(index_i, dt); - } - //=================================================================================================// - void InteractionDynamicsWithUpdate::exec(Real dt) - { - setBodyUpdated(); - setupDynamics(dt); - for (size_t k = 0; k < pre_processes_.size(); ++k) pre_processes_[k]->exec(dt); - size_t total_real_particles = base_particles_->total_real_particles_; - ParticleIterator(total_real_particles, functor_interaction_, dt); - for (size_t k = 0; k < post_processes_.size(); ++k) post_processes_[k]->exec(dt); - ParticleIterator(total_real_particles, functor_update_, dt); - } - //=================================================================================================// - void InteractionDynamicsWithUpdate::parallel_exec(Real dt) - { - setBodyUpdated(); - setupDynamics(dt); - for (size_t k = 0; k < pre_processes_.size(); ++k) pre_processes_[k]->parallel_exec(dt); - size_t total_real_particles = base_particles_->total_real_particles_; - ParticleIterator_parallel(total_real_particles, functor_interaction_, dt); - for (size_t k = 0; k < post_processes_.size(); ++k) post_processes_[k]->parallel_exec(dt); - ParticleIterator_parallel(total_real_particles, functor_update_, dt); - } - //=================================================================================================// - void ParticleDynamics1Level::exec(Real dt) - { - setBodyUpdated(); - setupDynamics(dt); - size_t total_real_particles = base_particles_->total_real_particles_; - ParticleIterator(total_real_particles, functor_initialization_, dt); - for (size_t k = 0; k < pre_processes_.size(); ++k) pre_processes_[k]->exec(dt); - ParticleIterator(total_real_particles, functor_interaction_, dt); - for (size_t k = 0; k < post_processes_.size(); ++k) post_processes_[k]->exec(dt); - ParticleIterator(total_real_particles, functor_update_, dt); - } - //=================================================================================================// - void ParticleDynamics1Level::parallel_exec(Real dt) - { - setBodyUpdated(); - setupDynamics(dt); - size_t total_real_particles = base_particles_->total_real_particles_; - ParticleIterator_parallel(total_real_particles, functor_initialization_, dt); - for (size_t k = 0; k < pre_processes_.size(); ++k) pre_processes_[k]->parallel_exec(dt); - ParticleIterator_parallel(total_real_particles, functor_interaction_, dt); - for (size_t k = 0; k < post_processes_.size(); ++k) post_processes_[k]->parallel_exec(dt); - ParticleIterator_parallel(total_real_particles, functor_update_, dt); - } - //=================================================================================================// - void InteractionDynamicsSplitting::exec(Real dt) - { - setBodyUpdated(); - setupDynamics(dt); - for (size_t k = 0; k < pre_processes_.size(); ++k) pre_processes_[k]->exec(dt); - ParticleIteratorSplittingSweep(split_cell_lists_, functor_interaction_, dt); - for (size_t k = 0; k < post_processes_.size(); ++k) post_processes_[k]->exec(dt); - } - //=================================================================================================// - void InteractionDynamicsSplitting::parallel_exec(Real dt) - { - setBodyUpdated(); - setupDynamics(dt); - for (size_t k = 0; k < pre_processes_.size(); ++k) pre_processes_[k]->parallel_exec(dt); - ParticleIteratorSplittingSweep_parallel(split_cell_lists_, functor_interaction_, dt); - for (size_t k = 0; k < post_processes_.size(); ++k) post_processes_[k]->parallel_exec(dt); - } - //=============================================================================================// -} -//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_algorithms.h b/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_algorithms.h deleted file mode 100644 index 947e0eca75..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_algorithms.h +++ /dev/null @@ -1,213 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file particle_dynamics_algorithms.h -* @brief This is the classes for algorithms particle dynamics. -* @detail Generally, there are four types dynamics. One is without particle interaction. -* One is with particle interaction within a body. One is with particle interaction -* between a center body and other contacted bodies. -* Still another is the combination of the last two. -* For the first dynamics, there is also reduce dynamics -* which carries reduced operations through the particles of the body. - -* @author Chi ZHang and Xiangyu Hu -*/ - -#ifndef PARTICLE_DYNAMICS_ALGORITHMS_H -#define PARTICLE_DYNAMICS_ALGORITHMS_H - - - -#include "base_particle_dynamics.h" -#include "base_particle_dynamics.hpp" - -namespace SPH -{ - /** - * @class ParticleDynamicsSimple - * @brief Simple particle dynamics without considering particle interaction - */ - class ParticleDynamicsSimple : public ParticleDynamics - { - public: - explicit ParticleDynamicsSimple(SPHBody* sph_body) : - ParticleDynamics(sph_body), - functor_update_(std::bind(&ParticleDynamicsSimple::Update, this, _1, _2)) {}; - virtual ~ParticleDynamicsSimple() {}; - - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override; - protected: - virtual void Update(size_t index_i, Real dt = 0.0) = 0; - ParticleFunctor functor_update_; - }; - - /** - * @class ParticleDynamicsReduce - * @brief Base abstract class for reduce - */ - template - class ParticleDynamicsReduce : public ParticleDynamics - { - public: - explicit ParticleDynamicsReduce(SPHBody* sph_body) : - ParticleDynamics(sph_body), quantity_name_("ReducedQuantity"), initial_reference_(), - functor_reduce_function_(std::bind(&ParticleDynamicsReduce::ReduceFunction, this, _1, _2)) {}; - virtual ~ParticleDynamicsReduce() {}; - - ReturnType InitialReference() { return initial_reference_; }; - std::string QuantityName() { return quantity_name_; }; - - virtual ReturnType exec(Real dt = 0.0) override - { - size_t total_real_particles = this->base_particles_->total_real_particles_; - this->setBodyUpdated(); - SetupReduce(); - ReturnType temp = ReduceIterator(total_real_particles, - initial_reference_, functor_reduce_function_, reduce_operation_, dt); - return OutputResult(temp); - }; - virtual ReturnType parallel_exec(Real dt = 0.0) override - { - size_t total_real_particles = this->base_particles_->total_real_particles_; - this->setBodyUpdated(); - SetupReduce(); - ReturnType temp = ReduceIterator_parallel(total_real_particles, - initial_reference_, functor_reduce_function_, reduce_operation_, dt); - return this->OutputResult(temp); - }; - protected: - ReduceOperation reduce_operation_; - std::string quantity_name_; - - /** inital or reference value */ - ReturnType initial_reference_; - virtual void SetupReduce() {}; - virtual ReturnType ReduceFunction(size_t index_i, Real dt = 0.0) = 0; - virtual ReturnType OutputResult(ReturnType reduced_value) { return reduced_value; }; - ReduceFunctor functor_reduce_function_; - }; - - /** - * @class InteractionDynamics - * @brief This is the class for particle interaction with other particles - */ - class InteractionDynamics : public ParticleDynamics - { - public: - explicit InteractionDynamics(SPHBody* sph_body) - : ParticleDynamics(sph_body), - functor_interaction_(std::bind(&InteractionDynamics::Interaction, - this, _1, _2)) {}; - virtual ~InteractionDynamics() {}; - - /** pre process such as update ghost state */ - StdVec*> pre_processes_; - /** post process such as impose constraint */ - StdVec*> post_processes_; - - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override; - protected: - friend class CombinedInteractionDynamics; - virtual void Interaction(size_t index_i, Real dt = 0.0) = 0; - ParticleFunctor functor_interaction_; - }; - - /** - * @class CombinedInteractionDynamics - * @brief This is the class for combining several interactions dynamics, - * which share the particle loop but are independent from each other, - * aiming to increase computing intensity under the data caching environment - */ - class CombinedInteractionDynamics : public InteractionDynamics - { - public: - explicit CombinedInteractionDynamics(InteractionDynamics& dynamics_a, InteractionDynamics& dynamics_b); - virtual ~CombinedInteractionDynamics() {}; - - protected: - InteractionDynamics& dynamics_a_, & dynamics_b_; - virtual void setupDynamics(Real dt = 0.0) override; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class InteractionDynamicsWithUpdate - * @brief This class includes an interaction and a update steps - */ - class InteractionDynamicsWithUpdate : public InteractionDynamics - { - public: - InteractionDynamicsWithUpdate(SPHBody* sph_body) - : InteractionDynamics(sph_body), - functor_update_(std::bind(&InteractionDynamicsWithUpdate::Update, - this, _1, _2)) {} - virtual ~InteractionDynamicsWithUpdate() {}; - - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override; - protected: - virtual void Update(size_t index_i, Real dt = 0.0) = 0; - ParticleFunctor functor_update_; - }; - - /** - * @class ParticleDynamics1Level - * @brief This class includes an initialization, an interaction and a update steps - */ - class ParticleDynamics1Level : public InteractionDynamicsWithUpdate - { - public: - ParticleDynamics1Level(SPHBody* sph_body) - : InteractionDynamicsWithUpdate(sph_body), - functor_initialization_(std::bind(&ParticleDynamics1Level::Initialization, - this, _1, _2)) {} - virtual ~ParticleDynamics1Level() {}; - - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override; - protected: - virtual void Initialization(size_t index_i, Real dt = 0.0) = 0; - ParticleFunctor functor_initialization_; - }; - - /** - * @class InteractionDynamicsSplitting - * @brief This is for the splitting algorithm - */ - class InteractionDynamicsSplitting : public InteractionDynamics - { - public: - explicit InteractionDynamicsSplitting(SPHBody* sph_body) : - InteractionDynamics(sph_body), - split_cell_lists_(sph_body->split_cell_lists_) {}; - virtual ~InteractionDynamicsSplitting() {}; - - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override; - protected: - SplitCellLists& split_cell_lists_; - }; -} -#endif //PARTICLE_DYNAMICS_ALGORITHMS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_bodypart.cpp b/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_bodypart.cpp deleted file mode 100644 index e341c1a6a4..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_bodypart.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/** -* @file particle_dynamics_bodypart.cpp -* @brief This is the implementation of the template class -* @author Chi ZHang and Xiangyu Hu -*/ - -#include "particle_dynamics_bodypart.h" - -namespace SPH { - //=================================================================================================// - void PartDynamicsByParticle::exec(Real dt) - { - setBodyUpdated(); - setupDynamics(dt); - for (size_t i = 0; i < body_part_particles_.size(); ++i) - { - particle_functor_(body_part_particles_[i], dt); - } - } - //=================================================================================================// - void PartDynamicsByParticle::parallel_exec(Real dt) - { - setBodyUpdated(); - setupDynamics(dt); - parallel_for(blocked_range(0, body_part_particles_.size()), - [&](const blocked_range& r) { - for (size_t i = r.begin(); i < r.end(); ++i) { - particle_functor_(body_part_particles_[i], dt); - } - }, ap); - } - //=================================================================================================// - PartSimpleDynamicsByParticle:: - PartSimpleDynamicsByParticle(SPHBody* sph_body, BodyPartByParticle *body_part) : - PartDynamicsByParticle(sph_body, body_part) - { - particle_functor_ = std::bind(&PartSimpleDynamicsByParticle::Update, this, _1, _2); - }; - //=================================================================================================// - PartInteractionDynamicsByParticle:: - PartInteractionDynamicsByParticle(SPHBody* sph_body, BodyPartByParticle* body_part) : - PartDynamicsByParticle(sph_body, body_part) - { - particle_functor_ = std::bind(&PartInteractionDynamicsByParticle::Interaction, this, _1, _2); - } - //=================================================================================================// - PartInteractionDynamicsByParticleWithUpdate:: - PartInteractionDynamicsByParticleWithUpdate(SPHBody* sph_body, BodyPartByParticle* body_part) : - PartInteractionDynamicsByParticle(sph_body, body_part) - { - functor_update_ = std::bind(&PartInteractionDynamicsByParticleWithUpdate::Update, this, _1, _2); - } - //=================================================================================================// - void PartInteractionDynamicsByParticleWithUpdate::exec(Real dt) - { - setBodyUpdated(); - setupDynamics(dt); - - for (size_t i = 0; i < body_part_particles_.size(); ++i) - { - particle_functor_(body_part_particles_[i], dt); - } - - for (size_t i = 0; i < body_part_particles_.size(); ++i) - { - functor_update_(body_part_particles_[i], dt); - } - } - //=================================================================================================// - void PartInteractionDynamicsByParticleWithUpdate::parallel_exec(Real dt) - { - setBodyUpdated(); - setupDynamics(dt); - - parallel_for(blocked_range(0, body_part_particles_.size()), - [&](const blocked_range& r) { - for (size_t i = r.begin(); i < r.end(); ++i) { - particle_functor_(body_part_particles_[i], dt); - } - }, ap); - - parallel_for(blocked_range(0, body_part_particles_.size()), - [&](const blocked_range& r) { - for (size_t i = r.begin(); i < r.end(); ++i) { - functor_update_(body_part_particles_[i], dt); - } - }, ap); - } - //=================================================================================================// - void PartInteractionDynamicsByParticle1Level::exec(Real dt) - { - setBodyUpdated(); - setupDynamics(dt); - - for (size_t i = 0; i < body_part_particles_.size(); ++i) - { - functor_initialization_(body_part_particles_[i], dt); - } - - for (size_t i = 0; i < body_part_particles_.size(); ++i) - { - particle_functor_(body_part_particles_[i], dt); - } - - for (size_t i = 0; i < body_part_particles_.size(); ++i) - { - functor_update_(body_part_particles_[i], dt); - } - } - //=================================================================================================// - void PartInteractionDynamicsByParticle1Level::parallel_exec(Real dt) - { - setBodyUpdated(); - setupDynamics(dt); - - parallel_for(blocked_range(0, body_part_particles_.size()), - [&](const blocked_range& r) { - for (size_t i = r.begin(); i < r.end(); ++i) { - functor_initialization_(body_part_particles_[i], dt); - } - }, ap); - - parallel_for(blocked_range(0, body_part_particles_.size()), - [&](const blocked_range& r) { - for (size_t i = r.begin(); i < r.end(); ++i) { - particle_functor_(body_part_particles_[i], dt); - } - }, ap); - - parallel_for(blocked_range(0, body_part_particles_.size()), - [&](const blocked_range& r) { - for (size_t i = r.begin(); i < r.end(); ++i) { - functor_update_(body_part_particles_[i], dt); - } - }, ap); - } - //=================================================================================================// - void PartDynamicsByCell::exec(Real dt) - { - setBodyUpdated(); - setupDynamics(dt); - for (size_t i = 0; i != body_part_cells_.size(); ++i) { - ListDataVector& list_data = body_part_cells_[i]->cell_list_data_; - for (size_t num = 0; num < list_data.size(); ++num) Update(list_data[num].first, dt); - } - } - //=================================================================================================// - void PartDynamicsByCell::parallel_exec(Real dt) - { - setBodyUpdated(); - setupDynamics(dt); - parallel_for(blocked_range(0, body_part_cells_.size()), - [&](const blocked_range& r) { - for (size_t i = r.begin(); i < r.end(); ++i) { - ListDataVector& list_data = body_part_cells_[i]->cell_list_data_; - for (size_t num = 0; num < list_data.size(); ++num) Update(list_data[num].first, dt); - } - }, ap); - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_bodypart.h b/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_bodypart.h deleted file mode 100644 index 9a80322e1a..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_bodypart.h +++ /dev/null @@ -1,264 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file particle_dynamics_bodypart.h - * @brief Dynamics for bodypart. - * The dynamics is constrained to a part of the body, - * such as in a subregion or on the surface of the body. - * The particles of a body part can be defined in an Eulerian or Lagrangian fashion. - * @author Chi ZHang and Xiangyu Hu - */ - -#ifndef PARTICLE_DYNAMICS_BODYPART_H -#define PARTICLE_DYNAMICS_BODYPART_H - - - -#include "base_particle_dynamics.h" -#include "base_particle_dynamics.hpp" - -namespace SPH { - - /** - * @class PartDynamicsByParticle - * @brief Abstract class for imposing body part dynamics by particles. - * That is the constrained particles will be the same - * during the simulation. - */ - class PartDynamicsByParticle : public ParticleDynamics - { - public: - PartDynamicsByParticle(SPHBody* sph_body, BodyPartByParticle *body_part) - : ParticleDynamics(sph_body), - body_part_particles_(body_part->body_part_particles_) {}; - virtual ~PartDynamicsByParticle() {}; - - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override; - protected: - IndexVector& body_part_particles_; - ParticleFunctor particle_functor_; - }; - - /** - * @class PartSimpleDynamicsByParticle - * @brief Abstract class for body part simple particle dynamics. - */ - class PartSimpleDynamicsByParticle : public PartDynamicsByParticle - { - public: - PartSimpleDynamicsByParticle(SPHBody* sph_body, BodyPartByParticle *body_part); - virtual ~PartSimpleDynamicsByParticle() {}; - protected: - virtual void Update(size_t index_i, Real dt = 0.0) = 0; - }; - - /** - * @class PartInteractionDynamicsByParticle - * @brief Abstract class for particle interaction involving in a body part. - */ - class PartInteractionDynamicsByParticle : public PartDynamicsByParticle - { - public: - PartInteractionDynamicsByParticle(SPHBody* sph_body, BodyPartByParticle* body_part); - virtual ~PartInteractionDynamicsByParticle() {}; - protected: - virtual void Interaction(size_t index_i, Real dt = 0.0) = 0; - }; - - /** - * @class PartInteractionDynamicsByParticleWithUpdate - * @brief Abstract class for particle interaction involving in a body part with an extra update step. - */ - class PartInteractionDynamicsByParticleWithUpdate : public PartInteractionDynamicsByParticle - { - public: - PartInteractionDynamicsByParticleWithUpdate(SPHBody* sph_body, BodyPartByParticle* body_part); - virtual ~PartInteractionDynamicsByParticleWithUpdate() {}; - - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override; - protected: - virtual void Update(size_t index_i, Real dt = 0.0) = 0; - ParticleFunctor functor_update_; - }; - - /** - * @class PartInteractionDynamicsByParticleWithUpdate - * @brief Abstract class for particle interaction involving in a body part with an extra update step. - */ - class PartInteractionDynamicsByParticle1Level : public PartInteractionDynamicsByParticleWithUpdate - { - public: - PartInteractionDynamicsByParticle1Level(SPHBody* sph_body, BodyPartByParticle* body_part) : - PartInteractionDynamicsByParticleWithUpdate(sph_body, body_part), - functor_initialization_(std::bind(&PartInteractionDynamicsByParticle1Level::Initialization, - this, _1, _2)) {}; - virtual ~PartInteractionDynamicsByParticle1Level() {}; - - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override; - protected: - virtual void Initialization(size_t index_i, Real dt = 0.0) = 0; - ParticleFunctor functor_initialization_; - }; - - /** - * @class PartDynamicsByCell - * @brief Abstract class for imposing Eulerian constrain to a body. - * The constrained particles are in the tagged cells . - */ - class PartDynamicsByCell : public ParticleDynamics - { - public: - PartDynamicsByCell(SPHBody* sph_body, BodyPartByCell *body_part) - : ParticleDynamics(sph_body), - body_part_cells_(body_part->body_part_cells_) {}; - virtual ~PartDynamicsByCell() {}; - - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override; - protected: - CellLists& body_part_cells_; - - virtual void Update(size_t index_i, Real dt = 0.0) = 0; - }; - /** - * @class PartDynamicsByCellReduce - * @brief Abstract class for reduce operation in a Eulerian constrain region. - */ - template - class PartDynamicsByCellReduce : public ParticleDynamics - { - public: - PartDynamicsByCellReduce(SPHBody* sph_body, BodyPartByCell* body_part) - : ParticleDynamics(sph_body), body_part_cells_(body_part->body_part_cells_), - quantity_name_("ReducedQuantity"), initial_reference_() {}; - virtual ~PartDynamicsByCellReduce() {}; - - ReturnType InitialReference() { return initial_reference_; }; - std::string QuantityName() { return quantity_name_; }; - - virtual ReturnType exec(Real dt = 0.0) override - { - ReturnType temp = initial_reference_; - this->SetupReduce(); - for (size_t i = 0; i != body_part_cells_.size(); ++i) - { - ListDataVector& list_data = body_part_cells_[i]->cell_list_data_; - for (size_t num = 0; num < list_data.size(); ++num) - { - temp = reduce_operation_(temp, ReduceFunction(list_data[num].first, dt)); - } - } - return OutputResult(temp); - }; - - virtual ReturnType parallel_exec(Real dt = 0.0) override - { - ReturnType temp = initial_reference_; - this->SetupReduce(); - temp = parallel_reduce(blocked_range(0, body_part_cells_.size()), - temp, - [&](const blocked_range& r, ReturnType temp0)->ReturnType - { - for (size_t i = r.begin(); i != r.end(); ++i) - { - ListDataVector& list_data = body_part_cells_[i]->cell_list_data_; - for (size_t num = 0; num < list_data.size(); ++num) - { - temp0 = reduce_operation_(temp0, ReduceFunction(list_data[num].first, dt)); - } - } - return temp0; - }, [this](ReturnType x, ReturnType y)->ReturnType { return reduce_operation_(x, y); } - ); - - return OutputResult(temp); - }; - protected: - ReduceOperation reduce_operation_; - CellLists& body_part_cells_; - std::string quantity_name_; - ReturnType initial_reference_; - virtual void SetupReduce() {}; - virtual ReturnType ReduceFunction(size_t index_i, Real dt = 0.0) = 0; - virtual ReturnType OutputResult(ReturnType reduced_value) { return reduced_value; }; - }; - /** - * @class PartDynamicsByParticleReduce - * @brief reduce operation in a Lagrangian contrained region. - */ - template - class PartDynamicsByParticleReduce : public ParticleDynamics - { - public: - PartDynamicsByParticleReduce(SPHBody* sph_body, BodyPartByParticle *body_part) - : ParticleDynamics(sph_body), - body_part_particles_(body_part->body_part_particles_), - quantity_name_("ReducedQuantity"), initial_reference_() {}; - virtual ~PartDynamicsByParticleReduce() {}; - - ReturnType InitialReference() { return initial_reference_; }; - std::string QuantityName() { return quantity_name_; }; - - virtual ReturnType exec(Real dt = 0.0) override - { - ReturnType temp = initial_reference_; - this->SetupReduce(); - for (size_t i = 0; i < body_part_particles_.size(); ++i) - { - temp = reduce_operation_(temp, ReduceFunction(body_part_particles_[i], dt)); - } - return OutputResult(temp); - }; - virtual ReturnType parallel_exec(Real dt = 0.0) override - { - ReturnType temp = initial_reference_; - this->SetupReduce(); - temp = parallel_reduce(blocked_range(0, body_part_particles_.size()), - temp, - [&](const blocked_range& r, ReturnType temp0)->ReturnType { - for (size_t n = r.begin(); n != r.end(); ++n) { - temp0 = reduce_operation_(temp0, ReduceFunction(body_part_particles_[n], dt)); - } - return temp0; - }, - [this](ReturnType x, ReturnType y)->ReturnType { - return reduce_operation_(x, y); - } - ); - - return OutputResult(temp); - }; - protected: - ReduceOperation reduce_operation_; - IndexVector& body_part_particles_; - std::string quantity_name_; - ReturnType initial_reference_; - virtual void SetupReduce() {}; - virtual ReturnType ReduceFunction(size_t index_i, Real dt = 0.0) = 0; - virtual ReturnType OutputResult(ReturnType reduced_value) { return reduced_value; }; - }; -} -#endif //PARTICLE_DYNAMICS_BODYPART_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/relax_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/relax_dynamics/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/relax_dynamics/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/relax_dynamics/relax_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/relax_dynamics/relax_dynamics.cpp deleted file mode 100644 index e922354ce7..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/relax_dynamics/relax_dynamics.cpp +++ /dev/null @@ -1,239 +0,0 @@ -/** - * @file relax_dynamics.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#include "relax_dynamics.h" -#include "particle_generator_lattice.h" -#include "geometry_level_set.h" - -using namespace SimTK; -//=================================================================================================// -namespace SPH -{ -//=================================================================================================// - namespace relax_dynamics - { - //=================================================================================================// - GetTimeStepSizeSquare::GetTimeStepSizeSquare(SPHBody* body) : - ParticleDynamicsReduce(body), - RelaxDataDelegateSimple(body), dvel_dt_(particles_->dvel_dt_) - { - h_ref_ = body->particle_adaptation_->ReferenceSmoothingLength(); - //given by sound criteria by assuming unit speed of sound and zero particle velocity - initial_reference_ = 1.0 / h_ref_; - } - //=================================================================================================// - Real GetTimeStepSizeSquare::ReduceFunction(size_t index_i, Real dt) - { - return dvel_dt_[index_i].norm(); - } - //=================================================================================================// - Real GetTimeStepSizeSquare::OutputResult(Real reduced_value) - { - return 0.0625 * h_ref_ / (reduced_value + TinyReal); - } - //=================================================================================================// - RelaxationAccelerationInner::RelaxationAccelerationInner(BaseBodyRelationInner* body_inner_relation) : - InteractionDynamics(body_inner_relation->sph_body_), - RelaxDataDelegateInner(body_inner_relation), - Vol_(particles_->Vol_), dvel_dt_(particles_->dvel_dt_), pos_n_(particles_->pos_n_) {} - //=================================================================================================// - void RelaxationAccelerationInner::Interaction(size_t index_i, Real dt) - { - Vecd acceleration(0); - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - acceleration -= 2.0 * inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n] * Vol_[index_j]; - } - dvel_dt_[index_i] = acceleration; - } - //=================================================================================================// - RelaxationAccelerationInnerWithLevelSetCorrection:: - RelaxationAccelerationInnerWithLevelSetCorrection(BaseBodyRelationInner* body_inner_relation) : - RelaxationAccelerationInner(body_inner_relation) - { - level_set_complex_shape_ = dynamic_cast(body_->body_shape_); - if (level_set_complex_shape_ == nullptr) - { - std::cout << "\n FAILURE: LevelSetComplexShape is undefined!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - } - //=================================================================================================// - void RelaxationAccelerationInnerWithLevelSetCorrection::Interaction(size_t index_i, Real dt) - { - RelaxationAccelerationInner::Interaction(index_i, dt); - dvel_dt_[index_i] -= 2.0 * level_set_complex_shape_-> - computeKernelGradientIntegral(pos_n_[index_i], particle_adaptation_->SmoothingLengthRatio(index_i)); - } - //=================================================================================================// - UpdateParticlePosition::UpdateParticlePosition(SPHBody* body) : - ParticleDynamicsSimple(body), RelaxDataDelegateSimple(body), - pos_n_(particles_->pos_n_), dvel_dt_(particles_->dvel_dt_) {} - //=================================================================================================// - void UpdateParticlePosition::Update(size_t index_i, Real dt_square) - { - pos_n_[index_i] += dvel_dt_[index_i] * dt_square * 0.5 / particle_adaptation_->SmoothingLengthRatio(index_i); - } - //=================================================================================================// - UpdateSolidParticlePosition::UpdateSolidParticlePosition(SPHBody* body) : - ParticleDynamicsSimple(body), solid_dynamics::SolidDataSimple(body), - pos_0_(particles_->pos_0_), pos_n_(particles_->pos_n_), dvel_dt_(particles_->dvel_dt_) {} - //=================================================================================================// - void UpdateSolidParticlePosition::Update(size_t index_i, Real dt_square) - { - pos_n_[index_i] += dvel_dt_[index_i] * dt_square * 0.5 / particle_adaptation_->SmoothingLengthRatio(index_i); - pos_0_[index_i] = pos_n_[index_i]; - } - //=================================================================================================// - UpdateSmoothingLengthRatioByBodyShape::UpdateSmoothingLengthRatioByBodyShape(SPHBody* body) : - ParticleDynamicsSimple(body), RelaxDataDelegateSimple(body), - h_ratio_(*particles_->getVariableByName("SmoothingLengthRatio")), - Vol_(particles_->Vol_), pos_n_(particles_->pos_n_), - body_shape_(*body->body_shape_), kernel_(*body->particle_adaptation_->getKernel()) - { - particle_spacing_by_body_shape_ = - dynamic_cast(body->particle_adaptation_); - } - //=================================================================================================// - void UpdateSmoothingLengthRatioByBodyShape::Update(size_t index_i, Real dt_square) - { - Real local_spacing = particle_spacing_by_body_shape_->getLocalSpacing(body_shape_, pos_n_[index_i]); - h_ratio_[index_i] = particle_adaptation_->ReferenceSpacing() / local_spacing; - Vol_[index_i] = powerN(local_spacing, Dimensions); - } - //=================================================================================================// - RelaxationAccelerationComplex:: - RelaxationAccelerationComplex(ComplexBodyRelation* body_complex_relation) : - InteractionDynamics(body_complex_relation->sph_body_), - RelaxDataDelegateComplex(body_complex_relation), - Vol_(particles_->Vol_), dvel_dt_(particles_->dvel_dt_) - { - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); - } - } - //=================================================================================================// - void RelaxationAccelerationComplex::Interaction(size_t index_i, Real dt) - { - Vecd acceleration(0); - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - acceleration -= 2.0 * inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]* Vol_[index_j]; - } - - /** Contact interaction. */ - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - StdLargeVec& Vol_k = *(contact_Vol_[k]); - Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - - acceleration -= 2.0 * Vol_k[index_j] - * contact_neighborhood.dW_ij_[n] * contact_neighborhood.e_ij_[n]; - } - } - - dvel_dt_[index_i] = acceleration; - } - //=================================================================================================// - ShapeSurfaceBounding:: - ShapeSurfaceBounding(SPHBody* body, NearShapeSurface* body_part) : - PartDynamicsByCell(body, body_part), RelaxDataDelegateSimple(body), - pos_n_(particles_->pos_n_), - constrained_distance_(0.5 * body->particle_adaptation_->MinimumSpacing()) {} - //=================================================================================================// - void ShapeSurfaceBounding::Update(size_t index_i, Real dt) - { - Real phi = body_->body_shape_->findSignedDistance(pos_n_[index_i]); - - if (phi > -constrained_distance_) - { - Vecd unit_normal = body_->body_shape_->findNormalDirection(pos_n_[index_i]); - unit_normal /= unit_normal.norm() + TinyReal; - pos_n_[index_i] -= (phi + constrained_distance_) * unit_normal; - } - } - //=================================================================================================// - ConstraintSurfaceParticles:: - ConstraintSurfaceParticles(SPHBody* body, ShapeSurface* body_part) - :PartSimpleDynamicsByParticle(body, body_part), RelaxDataDelegateSimple(body), - constrained_distance_(0.5 * body->particle_adaptation_->MinimumSpacing()), - pos_n_(particles_->pos_n_) - { - } - //=================================================================================================// - void ConstraintSurfaceParticles::Update(size_t index_i, Real dt) - { - Real phi = body_->body_shape_->findSignedDistance(pos_n_[index_i]); - Vecd unit_normal = body_->body_shape_->findNormalDirection(pos_n_[index_i]); - unit_normal /= unit_normal.norm() + TinyReal; - pos_n_[index_i] -= (phi + constrained_distance_) * unit_normal; - } - //=================================================================================================// - RelaxationStepInner:: - RelaxationStepInner(BaseBodyRelationInner* body_inner_relation, bool level_set_correction) : - ParticleDynamics(body_inner_relation->sph_body_), - real_body_(body_inner_relation->real_body_), inner_relation_(body_inner_relation), - relaxation_acceleration_inner_(nullptr), - get_time_step_square_(real_body_), update_particle_position_(real_body_), - surface_bounding_(real_body_, new NearShapeSurface(real_body_)) - { - relaxation_acceleration_inner_ = !level_set_correction ? - new RelaxationAccelerationInner(body_inner_relation) : - new RelaxationAccelerationInnerWithLevelSetCorrection(body_inner_relation); - } - //=================================================================================================// - void RelaxationStepInner::exec(Real dt) - { - real_body_->updateCellLinkedList(); - inner_relation_->updateConfiguration(); - relaxation_acceleration_inner_->exec(); - Real dt_square = get_time_step_square_.exec(); - update_particle_position_.exec(dt_square); - surface_bounding_.exec(); - } - //=================================================================================================// - void RelaxationStepInner::parallel_exec(Real dt) - { - real_body_->updateCellLinkedList(); - inner_relation_->updateConfiguration(); - relaxation_acceleration_inner_->parallel_exec(); - Real dt_square = get_time_step_square_.parallel_exec(); - update_particle_position_.parallel_exec(dt_square); - surface_bounding_.parallel_exec(); - } - //=================================================================================================// - void SolidRelaxationStepInner::exec(Real dt) - { - real_body_->updateCellLinkedList(); - inner_relation_->updateConfiguration(); - relaxation_acceleration_inner_->exec(); - Real dt_square = get_time_step_square_.exec(); - update_solid_particle_position_.exec(dt_square); - surface_bounding_.exec(); - } - //=================================================================================================// - void SolidRelaxationStepInner::parallel_exec(Real dt) - { - real_body_->updateCellLinkedList(); - inner_relation_->updateConfiguration(); - relaxation_acceleration_inner_->parallel_exec(); - Real dt_square = get_time_step_square_.parallel_exec(); - update_solid_particle_position_.parallel_exec(dt_square); - surface_bounding_.parallel_exec(); - } - //=================================================================================================// - } - //=================================================================================================// -} -//=================================================================================================// diff --git a/SPHINXsys/src/shared/particle_dynamics/relax_dynamics/relax_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/relax_dynamics/relax_dynamics.h deleted file mode 100644 index c4e3a6339d..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/relax_dynamics/relax_dynamics.h +++ /dev/null @@ -1,260 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file relax_dynamics.h -* @brief This is the classes of particle relaxation in order to produce body fitted -* initial particle distribution. -* @author Chi ZHang and Xiangyu Hu -*/ - - -#ifndef RELAX_DYNAMICS_H -#define RELAX_DYNAMICS_H - - - -#include "math.h" - -#include "all_particle_dynamics.h" -#include "neighbor_relation.h" -#include "base_kernel.h" -#include "mesh_cell_linked_list.h" -#include "solid_dynamics.h" - -namespace SPH -{ - class ComplexShape; - class LevelSetComplexShape; - - namespace relax_dynamics - { - typedef DataDelegateSimple RelaxDataDelegateSimple; - - typedef DataDelegateInner RelaxDataDelegateInner; - - typedef DataDelegateComplex RelaxDataDelegateComplex; - - /** - * @class GetTimeStepSizeSquare - * @brief relaxation dynamics for particle initialization - * computing the square of time step size - */ - class GetTimeStepSizeSquare : - public ParticleDynamicsReduce, - public RelaxDataDelegateSimple - { - public: - explicit GetTimeStepSizeSquare(SPHBody* body); - virtual ~GetTimeStepSizeSquare() {}; - protected: - StdLargeVec& dvel_dt_; - Real h_ref_; - Real ReduceFunction(size_t index_i, Real dt = 0.0) override; - Real OutputResult(Real reduced_value) override; - }; - - /** - * @class RelaxationAccelerationInner - * @brief simple algorithm for physics relaxation - * without considering contact interaction. - * this is usually used for solid like bodies - */ - class RelaxationAccelerationInner : - public InteractionDynamics, public RelaxDataDelegateInner - { - public: - RelaxationAccelerationInner(BaseBodyRelationInner* body_inner_relation); - virtual ~RelaxationAccelerationInner() {}; - protected: - StdLargeVec& Vol_; - StdLargeVec& dvel_dt_; - StdLargeVec& pos_n_; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class RelaxationAccelerationInnerWithLevelSetCorrection - * @brief we constrain particles to a level function representing the interafce. - */ - class RelaxationAccelerationInnerWithLevelSetCorrection : - public RelaxationAccelerationInner - { - public: - RelaxationAccelerationInnerWithLevelSetCorrection(BaseBodyRelationInner* body_inner_relation); - virtual ~RelaxationAccelerationInnerWithLevelSetCorrection() {}; - protected: - LevelSetComplexShape* level_set_complex_shape_; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class UpdateParticlePosition - * @brief update the particle position for a time step - */ - class UpdateParticlePosition : - public ParticleDynamicsSimple, public RelaxDataDelegateSimple - { - public: - explicit UpdateParticlePosition(SPHBody* body); - virtual ~UpdateParticlePosition() {}; - protected: - StdLargeVec& pos_n_, & dvel_dt_; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class UpdateSmoothingLengthRatioByBodyShape - * @brief update the particle smoothing length ratio - */ - class UpdateSmoothingLengthRatioByBodyShape : - public ParticleDynamicsSimple, public RelaxDataDelegateSimple - { - public: - explicit UpdateSmoothingLengthRatioByBodyShape(SPHBody* body); - virtual ~UpdateSmoothingLengthRatioByBodyShape() {}; - protected: - StdLargeVec& h_ratio_, & Vol_; - StdLargeVec& pos_n_; - ComplexShape& body_shape_; - Kernel& kernel_; - ParticleSpacingByBodyShape* particle_spacing_by_body_shape_; - - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class RelaxationAccelerationComplex - * @brief compute relaxation acceleration while consider the present of contact bodies - * with considering contact interaction - * this is usually used for fluid like bodies - */ - class RelaxationAccelerationComplex : - public InteractionDynamics, - public RelaxDataDelegateComplex - { - public: - RelaxationAccelerationComplex(ComplexBodyRelation* body_complex_relation); - virtual ~RelaxationAccelerationComplex() {}; - protected: - StdLargeVec& Vol_; - StdLargeVec& dvel_dt_; - StdVec*> contact_Vol_; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class ShapeSurfaceBounding - * @brief constrain surface particles by - * map contrained particles to geometry face and - * r = r + phi * norm (vector distance to face) - */ - class ShapeSurfaceBounding : - public PartDynamicsByCell, - public RelaxDataDelegateSimple - { - public: - ShapeSurfaceBounding(SPHBody *body, NearShapeSurface* body_part); - virtual ~ShapeSurfaceBounding() {}; - protected: - StdLargeVec& pos_n_; - Real constrained_distance_; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class ConstraintSurfaceParticles - * @brief constrain surface particles by - * map contrained particles to geometry face and - * r = r + phi * norm (vector distance to face) - */ - class ConstraintSurfaceParticles : - public PartSimpleDynamicsByParticle, - public RelaxDataDelegateSimple - { - public: - ConstraintSurfaceParticles(SPHBody* body, ShapeSurface* body_part); - virtual ~ConstraintSurfaceParticles() {}; - protected: - Real constrained_distance_; - StdLargeVec& pos_n_; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class RelaxationStepInner - * @brief carry out particle relaxation step of particles within the body - */ - class RelaxationStepInner : public ParticleDynamics - { - protected: - RealBody* real_body_; - BaseBodyRelationInner* inner_relation_; - public: - explicit RelaxationStepInner(BaseBodyRelationInner* body_inner_relation, bool level_set_correction = false); - virtual ~RelaxationStepInner() {}; - - RelaxationAccelerationInner* relaxation_acceleration_inner_; - GetTimeStepSizeSquare get_time_step_square_; - UpdateParticlePosition update_particle_position_; - ShapeSurfaceBounding surface_bounding_; - - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override; - }; - - /** - * @class UpdateParticlePosition - * @brief update the particle position for a time step - */ - class UpdateSolidParticlePosition : - public ParticleDynamicsSimple, public solid_dynamics::SolidDataSimple - { - public: - explicit UpdateSolidParticlePosition(SPHBody* body); - virtual ~UpdateSolidParticlePosition() {}; - protected: - StdLargeVec& pos_0_, & pos_n_, &dvel_dt_; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class SolidRelaxationStepInner - * @brief carry out particle relaxation step of particles within the body - */ - class SolidRelaxationStepInner : public RelaxationStepInner - { - public: - explicit SolidRelaxationStepInner(BaseBodyRelationInner* body_inner_relation, bool level_set_correction = false) : - RelaxationStepInner(body_inner_relation, level_set_correction), - update_solid_particle_position_(real_body_) {}; - virtual ~SolidRelaxationStepInner() {}; - - UpdateSolidParticlePosition update_solid_particle_position_; - - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override; - }; - - } -} -#endif //RELAX_DYNAMICS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/all_solid_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/all_solid_dynamics.h deleted file mode 100644 index 5249b900eb..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/all_solid_dynamics.h +++ /dev/null @@ -1,10 +0,0 @@ -/** @file -This is the header file that user code should include to pick up all -solid dynamics used in SPHinXsys. **/ -#pragma once - -#include "solid_dynamics.h" -#include "thin_structure_dynamics.h" -#include "fluid_structure_interaction.h" -#include "thin_structure_math.h" -#include "inelastic_dynamics.h" diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/fluid_structure_interaction.cpp b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/fluid_structure_interaction.cpp deleted file mode 100644 index 6cb99a0c04..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/fluid_structure_interaction.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/** - * @file fluid_structure_interaction.cpp - * @author Luhui Han, Chi Zhang and Xiangyu Hu - */ - -#include "fluid_structure_interaction.h" - -namespace SPH -{ - namespace solid_dynamics - { - //=================================================================================================// - FluidViscousForceOnSolid:: - FluidViscousForceOnSolid(BaseBodyRelationContact* body_contact_relation) : - InteractionDynamics(body_contact_relation->sph_body_), - FSIContactData(body_contact_relation), - Vol_(particles_->Vol_), vel_ave_(particles_->vel_ave_), - viscous_force_from_fluid_(*particles_->createAVariable("ViscousForceFromFluid")) - { - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); - contact_rho_n_.push_back(&(contact_particles_[k]->rho_n_)); - contact_vel_n_.push_back(&(contact_particles_[k]->vel_n_)); - - mu_.push_back(contact_material_[k]->ReferenceViscosity()); - smoothing_length_.push_back(contact_bodies_[k]->particle_adaptation_->ReferenceSmoothingLength()); - } - } - //=================================================================================================// - void FluidViscousForceOnSolid::Interaction(size_t index_i, Real dt) - { - Real Vol_i = Vol_[index_i]; - Vecd& vel_ave_i = vel_ave_[index_i]; - - Vecd force(0); - /** Contact interaction. */ - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - Real mu_k = mu_[k]; - Real smoothing_length_k = smoothing_length_[k]; - StdLargeVec& Vol_k = *(contact_Vol_[k]); - StdLargeVec& vel_n_k = *(contact_vel_n_[k]); - Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - - Vecd vel_derivative = 2.0 * (vel_ave_i - vel_n_k[index_j]) - / (contact_neighborhood.r_ij_[n] + 0.01 * smoothing_length_k); - - force += 2.0 * mu_k * vel_derivative * Vol_i * Vol_k[index_j] - * contact_neighborhood.dW_ij_[n]; - } - } - - viscous_force_from_fluid_[index_i] = force; - } - //=================================================================================================// - void FluidAngularConservativeViscousForceOnSolid::Interaction(size_t index_i, Real dt) - { - Real Vol_i = Vol_[index_i]; - Vecd& vel_ave_i = vel_ave_[index_i]; - - Vecd force(0); - /** Contact interaction. */ - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - Real mu_k = mu_[k]; - Real smoothing_length_k = smoothing_length_[k]; - StdLargeVec& Vol_k = *(contact_Vol_[k]); - StdLargeVec& rho_n_k = *(contact_rho_n_[k]); - StdLargeVec& vel_n_k = *(contact_vel_n_[k]); - Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - - /** The following viscous force is given in Monaghan 2005 (Rep. Prog. Phys.) */ - Real v_r_ij = dot(vel_ave_i - vel_n_k[index_j], - contact_neighborhood.r_ij_[n] * contact_neighborhood.e_ij_[n]); - Real vel_difference = 0.0 * (vel_ave_i - vel_n_k[index_j]).norm() - * contact_neighborhood.r_ij_[n]; - Real eta_ij = 8.0 * SMAX(mu_k, rho_n_k[index_j] * vel_difference) * v_r_ij / - (contact_neighborhood.r_ij_[n] * contact_neighborhood.r_ij_[n] + 0.01 * smoothing_length_k); - force += eta_ij * Vol_i * Vol_k[index_j] - * contact_neighborhood.dW_ij_[n] * contact_neighborhood.e_ij_[n]; - } - } - - viscous_force_from_fluid_[index_i] = force; - } - //=================================================================================================// - TotalViscousForceOnSolid - ::TotalViscousForceOnSolid(SolidBody* body) : - ParticleDynamicsReduce>(body), - SolidDataSimple(body), - viscous_force_from_fluid_(*particles_->getVariableByName("ViscousForceFromFluid")) - { - quantity_name_ = "TotalViscousForceOnSolid"; - initial_reference_ = Vecd(0); - } - //=================================================================================================// - Vecd TotalViscousForceOnSolid::ReduceFunction(size_t index_i, Real dt) - { - return viscous_force_from_fluid_[index_i]; - } - //=================================================================================================// - TotalForceOnSolid::TotalForceOnSolid(SolidBody* body) : - ParticleDynamicsReduce>(body), - SolidDataSimple(body), - force_from_fluid_(particles_->force_from_fluid_) - { - quantity_name_ = "TotalForceOnSolid"; - initial_reference_ = Vecd(0); - } - //=================================================================================================// - Vecd TotalForceOnSolid::ReduceFunction(size_t index_i, Real dt) - { - return force_from_fluid_[index_i]; - } - //=================================================================================================// - InitializeDisplacement:: - InitializeDisplacement(SolidBody* body, StdLargeVec& pos_temp) : - ParticleDynamicsSimple(body), SolidDataSimple(body), - pos_temp_(pos_temp), pos_n_(particles_->pos_n_), - vel_ave_(particles_->vel_ave_), dvel_dt_ave_(particles_->dvel_dt_ave_) - { - } - //=================================================================================================// - void InitializeDisplacement::Update(size_t index_i, Real dt) - { - pos_temp_[index_i] = pos_n_[index_i]; - } - //=================================================================================================// - void UpdateAverageVelocityAndAcceleration::Update(size_t index_i, Real dt) - { - Vecd updated_vel_ave = (pos_n_[index_i] - pos_temp_[index_i]) / (dt + Eps); - dvel_dt_ave_[index_i] = (updated_vel_ave - vel_ave_[index_i]) / (dt + Eps); - vel_ave_[index_i] = updated_vel_ave; - } - //=================================================================================================// - AverageVelocityAndAcceleration:: - AverageVelocityAndAcceleration(SolidBody* body) : - pos_temp_(*body->base_particles_->createAVariable("TemporaryPosition")), - initialize_displacement_(body, pos_temp_), - update_averages_(body, pos_temp_) {} - //=================================================================================================// - } -} diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/fluid_structure_interaction.h b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/fluid_structure_interaction.h deleted file mode 100644 index 3c9fee3c2e..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/fluid_structure_interaction.h +++ /dev/null @@ -1,234 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file fluid_structure_interaction.h -* @brief Here, we define the algorithm classes for fluid structure interaction. -* @author Chi ZHang and Xiangyu Hu -*/ - -#ifndef FLUID_STRUCTURE_INTERACTION_H -#define FLUID_STRUCTURE_INTERACTION_H - - - -#include "all_particle_dynamics.h" -#include "base_material.h" -#include "fluid_dynamics_complex.h" -#include "riemann_solver.h" - -namespace SPH -{ - namespace solid_dynamics - { - typedef DataDelegateSimple SolidDataSimple; - typedef DataDelegateContact FSIContactData; - - /** - * @class FluidViscousForceOnSolid - * @brief Computing the viscous force from the fluid - */ - class FluidViscousForceOnSolid : - public InteractionDynamics, public FSIContactData - { - public: - FluidViscousForceOnSolid(BaseBodyRelationContact* body_contact_relation); - virtual ~FluidViscousForceOnSolid() {}; - protected: - StdLargeVec& Vol_; - StdLargeVec& vel_ave_, & viscous_force_from_fluid_; - StdVec*> contact_Vol_, contact_rho_n_; - StdVec*> contact_vel_n_; - StdVec mu_; - StdVec smoothing_length_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class FluidAngularConservativeViscousForceOnSolid - * @brief Computing the viscous force from the fluid - */ - class FluidAngularConservativeViscousForceOnSolid : public FluidViscousForceOnSolid - { - public: - FluidAngularConservativeViscousForceOnSolid(BaseBodyRelationContact* body_contact_relation) - : FluidViscousForceOnSolid(body_contact_relation) {}; - virtual ~FluidAngularConservativeViscousForceOnSolid() {}; - protected: - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class BaseFluidPressureForceOnSolidRiemann - * @brief Template class fro computing the pressure force from the fluid with different Riemann solvers. - * The pressrue force is added on the viscous force of the latter is computed. - * This class is for FSI applications to achieve smaller solid dynamics - * time step size compared to the fluid dynamics - */ - template - class BaseFluidPressureForceOnSolid : public FluidViscousForceOnSolid - { - public: - BaseFluidPressureForceOnSolid(BaseBodyRelationContact* body_contact_relation) : - FluidViscousForceOnSolid(body_contact_relation), - force_from_fluid_(particles_->force_from_fluid_), - dvel_dt_ave_(particles_->dvel_dt_ave_), n_(particles_->n_) - { - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - contact_p_.push_back(&(contact_particles_[k]->p_)); - contact_dvel_dt_prior_.push_back(&(contact_particles_[k]->dvel_dt_prior_)); - riemann_solvers_.push_back(new RiemannSolverType(*contact_material_[k], *contact_material_[k])); - } - }; - virtual ~BaseFluidPressureForceOnSolid() {}; - protected: - StdLargeVec& force_from_fluid_, & dvel_dt_ave_, & n_; - StdVec*> contact_p_; - StdVec*> contact_dvel_dt_prior_; - StdVec riemann_solvers_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override - { - Vecd& dvel_dt_ave_i = dvel_dt_ave_[index_i]; - Real Vol_i = Vol_[index_i]; - Vecd& vel_ave_i = vel_ave_[index_i]; - Vecd& n_i = n_[index_i]; - - Vecd force(0); - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - StdLargeVec& Vol_k = *(contact_Vol_[k]); - StdLargeVec& rho_n_k = *(contact_rho_n_[k]); - StdLargeVec& p_k = *(contact_p_[k]); - StdLargeVec& vel_n_k = *(contact_vel_n_[k]); - StdLargeVec& dvel_dt_prior_k = *(contact_dvel_dt_prior_[k]); - Fluid* fluid_k = contact_material_[k]; - RiemannSolverType* riemann_solver_k = riemann_solvers_[k]; - Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - Vecd e_ij = contact_neighborhood.e_ij_[n]; - Real r_ij = contact_neighborhood.r_ij_[n]; - Real face_wall_external_acceleration - = dot((dvel_dt_prior_k[index_j] - dvel_dt_ave_i), e_ij); - Real p_in_wall = p_k[index_j] + rho_n_k[index_j] * r_ij * SMAX(0.0, face_wall_external_acceleration); - Real rho_in_wall = fluid_k->DensityFromPressure(p_in_wall); - Vecd vel_in_wall = 2.0 * vel_ave_i - vel_n_k[index_j]; - - FluidState state_l(rho_n_k[index_j], vel_n_k[index_j], p_k[index_j]); - FluidState state_r(rho_in_wall, vel_in_wall, p_in_wall); - Real p_star = riemann_solver_k->getPStar(state_l, state_r, n_i); - force -= 2.0 * p_star * e_ij * Vol_i * Vol_k[index_j] * contact_neighborhood.dW_ij_[n]; - } - } - force_from_fluid_[index_i] = viscous_force_from_fluid_[index_i] + force; - }; - }; - using FluidPressureForceOnSolid = BaseFluidPressureForceOnSolid; - using FluidPressureForceOnSolidRiemann = BaseFluidPressureForceOnSolid; - - /** - * @class TotalViscousForceOnSolid - * @brief Computing the total viscous force from fluid - */ - class TotalViscousForceOnSolid : - public ParticleDynamicsReduce>, public SolidDataSimple - { - public: - explicit TotalViscousForceOnSolid(SolidBody* body); - virtual ~TotalViscousForceOnSolid() {}; - protected: - StdLargeVec& viscous_force_from_fluid_; - Vecd ReduceFunction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class TotalForceOnSolid - * @brief Computing total force from fluid. - */ - class TotalForceOnSolid : - public ParticleDynamicsReduce>, public SolidDataSimple - { - public: - explicit TotalForceOnSolid(SolidBody* body); - virtual ~TotalForceOnSolid() {}; - protected: - StdLargeVec& force_from_fluid_; - Vecd ReduceFunction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class InitializeDisplacement - * @brief initialize the displacement for computing average velocity. - * This class is for FSI applications to achieve smaller solid dynamics - * time step size compared to the fluid dynamics - */ - class InitializeDisplacement : - public ParticleDynamicsSimple, public SolidDataSimple - { - public: - explicit InitializeDisplacement(SolidBody* body, StdLargeVec& pos_temp); - virtual ~InitializeDisplacement() {}; - protected: - StdLargeVec& pos_temp_, & pos_n_, & vel_ave_, & dvel_dt_ave_; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class UpdateAverageVelocityAndAcceleration - * @brief Computing average velocity. - * This class is for FSI applications to achieve smaller solid dynamics - * time step size compared to the fluid dynamics - */ - class UpdateAverageVelocityAndAcceleration : public InitializeDisplacement - { - public: - explicit UpdateAverageVelocityAndAcceleration(SolidBody* body, StdLargeVec& pos_temp) - : InitializeDisplacement(body, pos_temp) {}; - virtual ~UpdateAverageVelocityAndAcceleration() {}; - protected: - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class AverageVelocityAndAcceleration - * @brief Impose force matching between fluid and solid dynamics. - * Note that the fluid time step should be larger than that of solid time step. - * Otherwise numerical instability may occur. - */ - class AverageVelocityAndAcceleration - { - protected: - StdLargeVec& pos_temp_; - public: - InitializeDisplacement initialize_displacement_; - UpdateAverageVelocityAndAcceleration update_averages_; - - AverageVelocityAndAcceleration(SolidBody* body); - ~AverageVelocityAndAcceleration() {}; - }; - } -} -#endif //FLUID_STRUCTURE_INTERACTION_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.cpp deleted file mode 100644 index 70ad5ebce0..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.cpp +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @file inelastic_dynamics.cpp - * @author Xiaojing Tang, Chi Zhang and Xiangyu Hu - */ - -#include "inelastic_dynamics.h" - -using namespace SimTK; - -namespace SPH -{ - namespace solid_dynamics - { - //=================================================================================================// - PlasticStressRelaxationFirstHalf:: - PlasticStressRelaxationFirstHalf(BaseBodyRelationInner* body_inner_relation) : - StressRelaxationFirstHalf(body_inner_relation), - plastic_solid_(dynamic_cast(material_)) - { - numerical_dissipation_factor_ = 0.25; - } - //=================================================================================================// - void PlasticStressRelaxationFirstHalf::Initialization(size_t index_i, Real dt) - { - pos_n_[index_i] += vel_n_[index_i] * dt * 0.5; - F_[index_i] += dF_dt_[index_i] * dt * 0.5; - rho_n_[index_i] = rho0_ / SimTK::det(F_[index_i]); - - stress_PK1_[index_i] = plastic_solid_->PlasticConstitutiveRelation(F_[index_i], index_i, dt); - } - //=================================================================================================// - } -} diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.h deleted file mode 100644 index 5b3207f4ba..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.h +++ /dev/null @@ -1,56 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file inelastic_solid_dynamics.h -* @brief Here, we define the algorithm classes for inelastic_solid dynamics. -* @details We consider here a weakly compressible solids. -* @author Xiaojing Tang, Chi Zhang and Xiangyu Hu -*/ -#pragma once - -#include "solid_dynamics.h" - -namespace SPH -{ - namespace solid_dynamics - { - /** - * @class PlasticStressRelaxationFirstHalf - * @brief computing stress relaxation process by verlet time stepping - * This is the first step - */ - class PlasticStressRelaxationFirstHalf - : public StressRelaxationFirstHalf - { - public: - PlasticStressRelaxationFirstHalf(BaseBodyRelationInner* body_inner_relation); - virtual ~PlasticStressRelaxationFirstHalf() {}; - protected: - PlasticSolid* plastic_solid_; - - virtual void Initialization(size_t index_i, Real dt = 0.0) override; - }; - } -} - - diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.cpp deleted file mode 100644 index fdd09b07a4..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.cpp +++ /dev/null @@ -1,796 +0,0 @@ -/** - * @file solid_dynamics.cpp - * @author Luhui Han, Chi Zhang and Xiangyu Hu - */ - -#include "solid_dynamics.h" -#include "general_dynamics.h" - -using namespace SimTK; - -namespace SPH -{ - namespace solid_dynamics - { - //=================================================================================================// - ContactDensitySummation:: - ContactDensitySummation(SolidBodyRelationContact* solid_body_contact_relation) : - PartInteractionDynamicsByParticle(solid_body_contact_relation->sph_body_, - &solid_body_contact_relation->body_surface_layer_), - ContactDynamicsData(solid_body_contact_relation), - mass_(particles_->mass_), contact_density_(particles_->contact_density_) - { - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - contact_mass_.push_back(&(contact_particles_[k]->mass_)); - } - } - //=================================================================================================// - void ContactDensitySummation::Interaction(size_t index_i, Real dt) - { - /** Contact interaction. */ - Real sigma = 0.0; - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - StdLargeVec& contact_mass_k = *(contact_mass_[k]); - Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - sigma += contact_neighborhood.W_ij_[n] * contact_mass_k[contact_neighborhood.j_[n]]; - } - } - contact_density_[index_i] = sigma; - } - //=================================================================================================// - ContactForce::ContactForce(SolidBodyRelationContact* solid_body_contact_relation) : - PartInteractionDynamicsByParticle(solid_body_contact_relation->sph_body_, - &solid_body_contact_relation->body_surface_layer_), - ContactDynamicsData(solid_body_contact_relation), - contact_density_(particles_->contact_density_), - Vol_(particles_->Vol_), mass_(particles_->mass_), - dvel_dt_prior_(particles_->dvel_dt_prior_), - contact_force_(particles_->contact_force_) - { - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); - contact_contact_density_.push_back(&(contact_particles_[k]->contact_density_)); - } - } - //=================================================================================================// - void ContactForce::Interaction(size_t index_i, Real dt) - { - Real Vol_i = Vol_[index_i]; - Real p_i = contact_density_[index_i] * material_->ContactStiffness(); - /** Contact interaction. */ - Vecd force(0.0); - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - StdLargeVec& contact_density_k = *(contact_contact_density_[k]); - StdLargeVec& Vol_k = *(contact_Vol_[k]); - Solid* solid_k = contact_material_[k]; - - Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - Vecd e_ij = contact_neighborhood.e_ij_[n]; - - Real p_star = 0.5 * (p_i + contact_density_k[index_j] * solid_k->ContactStiffness()); - //force due to pressure - force -= 2.0 * p_star * e_ij * Vol_i * Vol_k[index_j] * contact_neighborhood.dW_ij_[n]; - } - } - contact_force_[index_i] = force; - dvel_dt_prior_[index_i] += force / mass_[index_i]; - } - //=================================================================================================// - DynamicContactForce:: - DynamicContactForce(SolidBodyRelationContact* solid_body_contact_relation, Real penalty_strength) : - PartInteractionDynamicsByParticle(solid_body_contact_relation->sph_body_, - &solid_body_contact_relation->body_surface_layer_), - ContactDynamicsData(solid_body_contact_relation), - Vol_(particles_->Vol_), mass_(particles_->mass_), - vel_n_(particles_->vel_n_), dvel_dt_prior_(particles_->dvel_dt_prior_), - contact_force_(particles_->contact_force_), penalty_strength_(penalty_strength) - { - Real impedence = material_->ReferenceDensity() * sqrt(material_->ContactStiffness()); - Real reference_pressure = material_->ReferenceDensity() * material_->ContactStiffness(); - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); - contact_vel_n_.push_back(&(contact_particles_[k]->vel_n_)); - Real contact_impedence = - contact_material_[k]->ReferenceDensity()*sqrt(contact_material_[k]->ContactStiffness()); - contact_impedence_.push_back(2.0 * impedence * contact_impedence - / (impedence + contact_impedence)); - Real contact_reference_pressure = - contact_material_[k]->ReferenceDensity() * contact_material_[k]->ContactStiffness(); - contact_reference_pressure_.push_back(2.0 * reference_pressure * contact_reference_pressure - / (reference_pressure + contact_reference_pressure)); - } - } - //=================================================================================================// - void DynamicContactForce::Interaction(size_t index_i, Real dt) - { - Real Vol_i = Vol_[index_i]; - Vecd vel_i = vel_n_[index_i]; - - /** Contact interaction. */ - Vecd force(0.0); - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - Real particle_spacing_j1 = 1.0 / contact_bodies_[k]->particle_adaptation_->ReferenceSpacing(); - Real particle_spacing_ratio2 = 1.0 / (this->body_->particle_adaptation_->ReferenceSpacing() * particle_spacing_j1); - particle_spacing_ratio2 *= 0.1 * particle_spacing_ratio2; - - StdLargeVec& Vol_k = *(contact_Vol_[k]); - StdLargeVec& vel_n_k = *(contact_vel_n_[k]); - - Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - Vecd e_ij = contact_neighborhood.e_ij_[n]; - - Real impedence_p = 0.5 * contact_impedence_[k] * (SimTK::dot(vel_i - vel_n_k[index_j], -e_ij)); - Real overlap = contact_neighborhood.r_ij_[n]; - Real delta = 2.0 * overlap * particle_spacing_j1; - Real beta = delta < 1.0 ? (1.0 - delta) * (1.0 - delta) * particle_spacing_ratio2 : 0.0; - Real penalty_p =penalty_strength_* beta* overlap* contact_reference_pressure_[k]; - - //force due to pressure - force -= 2.0 * (impedence_p + penalty_p) * e_ij * Vol_i * Vol_k[index_j] * contact_neighborhood.dW_ij_[n]; - } - } - - contact_force_[index_i] = force; - dvel_dt_prior_[index_i] += force / mass_[index_i]; - } - //=================================================================================================// - ContactForceWithWall:: - ContactForceWithWall(SolidBodyRelationContact* solid_body_contact_relation, Real penalty_strength) : - PartInteractionDynamicsByParticle(solid_body_contact_relation->sph_body_, - &solid_body_contact_relation->body_surface_layer_), - ContactDynamicsData(solid_body_contact_relation), - Vol_(particles_->Vol_), mass_(particles_->mass_), - vel_n_(particles_->vel_n_), dvel_dt_prior_(particles_->dvel_dt_prior_), - contact_force_(particles_->contact_force_), penalty_strength_(penalty_strength) - { - impedence_ = material_->ReferenceDensity() * sqrt(material_->ContactStiffness()); - reference_pressure_ = material_->ReferenceDensity() * material_->ContactStiffness(); - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); - contact_vel_n_.push_back(&(contact_particles_[k]->vel_n_)); - contact_n_.push_back(&(contact_particles_[k]->n_)); - } - } - //=================================================================================================// - void ContactForceWithWall::Interaction(size_t index_i, Real dt) - { - Real Vol_i = Vol_[index_i]; - Vecd vel_i = vel_n_[index_i]; - - /** Contact interaction. */ - Vecd force(0.0); - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - Real particle_spacing_j1 = 1.0 / contact_bodies_[k]->particle_adaptation_->ReferenceSpacing(); - Real particle_spacing_ratio2 = 1.0 / (body_->particle_adaptation_->ReferenceSpacing() * particle_spacing_j1); - particle_spacing_ratio2 *= 0.1 * particle_spacing_ratio2; - - StdLargeVec& Vol_k = *(contact_Vol_[k]); - StdLargeVec& n_k = *(contact_n_[k]); - StdLargeVec& vel_n_k = *(contact_vel_n_[k]); - - Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - Vecd e_ij = contact_neighborhood.e_ij_[n]; - Vecd n_k_j = n_k[index_j]; - - Real impedence_p = 0.5 * impedence_ * (SimTK::dot(vel_i - vel_n_k[index_j], -n_k_j)); - Real overlap = contact_neighborhood.r_ij_[n] * SimTK::dot(n_k_j, e_ij); - Real delta = 2.0 * overlap * particle_spacing_j1; - Real beta = delta < 1.0 ? (1.0 - delta) * (1.0 - delta) * particle_spacing_ratio2 : 0.0; - Real penalty_p = penalty_strength_ * beta * overlap * reference_pressure_; - - //force due to pressure - force -= 2.0 * (impedence_p + penalty_p) * dot(e_ij, n_k_j) * - n_k_j * Vol_i * Vol_k[index_j] * contact_neighborhood.dW_ij_[n]; - } - } - - contact_force_[index_i] = force; - dvel_dt_prior_[index_i] += force / mass_[index_i]; - } - //=================================================================================================// - AcousticTimeStepSize::AcousticTimeStepSize(SolidBody* body, Real CFL) : - ParticleDynamicsReduce(body), - ElasticSolidDataSimple(body), CFL_(CFL), - vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_) - { - smoothing_length_ = particle_adaptation_->ReferenceSmoothingLength(); - initial_reference_ = DBL_MAX; - } - //=================================================================================================// - Real AcousticTimeStepSize::ReduceFunction(size_t index_i, Real dt) - { - //since the particle does not change its configuration in pressure relaxation step - //I chose a time-step size according to Eulerian method - Real sound_speed = material_->ReferenceSoundSpeed(); - return CFL_ * SMIN(sqrt(smoothing_length_ / (dvel_dt_[index_i].norm() + TinyReal)), - smoothing_length_ / (sound_speed + vel_n_[index_i].norm())); - } - //=================================================================================================// - CorrectConfiguration:: - CorrectConfiguration(BaseBodyRelationInner* body_inner_relation) : - InteractionDynamics(body_inner_relation->sph_body_), - SolidDataInner(body_inner_relation), - Vol_(particles_->Vol_), B_(particles_->B_) - { - } - //=================================================================================================// - void CorrectConfiguration::Interaction(size_t index_i, Real dt) - { - Matd local_configuration(Eps); // a small number added to diagonal to avoid divide zero - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - - Vecd gradw_ij = inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]; - Vecd r_ji = inner_neighborhood.r_ij_[n] * inner_neighborhood.e_ij_[n]; - local_configuration -= Vol_[index_j] * SimTK::outer(r_ji, gradw_ij); - } - B_[index_i] = SimTK::inverse(local_configuration); - } - //=================================================================================================// - ConstrainSolidBodyRegion:: - ConstrainSolidBodyRegion(SPHBody* body, BodyPartByParticle* body_part) : - PartSimpleDynamicsByParticle(body, body_part), SolidDataSimple(body), - pos_n_(particles_->pos_n_), pos_0_(particles_->pos_0_), - n_(particles_->n_), n_0_(particles_->n_0_), - vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), - vel_ave_(particles_->vel_ave_), dvel_dt_ave_(particles_->dvel_dt_ave_) - { - } - //=================================================================================================// - void ConstrainSolidBodyRegion::Update(size_t index_i, Real dt) - { - Vecd pos_0 = pos_0_[index_i]; - Vecd pos_n = pos_n_[index_i]; - Vecd vel_n = vel_n_[index_i]; - Vecd dvel_dt = dvel_dt_[index_i]; - - pos_n_[index_i] = getDisplacement(pos_0, pos_n); - vel_n_[index_i] = getVelocity(pos_0, pos_n, vel_n); - dvel_dt_[index_i] = getAcceleration(pos_0, pos_n, dvel_dt); - /** the average values are prescirbed also. */ - vel_ave_[index_i] = vel_n_[index_i]; - dvel_dt_ave_[index_i] = dvel_dt_[index_i]; - } - //=================================================================================================// - PositionSolidBody:: - PositionSolidBody(SPHBody* body, BodyPartByParticle* body_part, Real start_time, Real end_time, Vecd pos_end_center): - PartSimpleDynamicsByParticle(body, body_part), SolidDataSimple(body), - pos_n_(particles_->pos_n_), pos_0_(particles_->pos_0_), - vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), - vel_ave_(particles_->vel_ave_), dvel_dt_ave_(particles_->dvel_dt_ave_), - start_time_(start_time), end_time_(end_time), pos_end_center_(pos_end_center) - { - BoundingBox bounds = body->getBodyDomainBounds(); - pos_0_center_ = (bounds.first + bounds.second) * 0.5; - translation_ = pos_end_center_ - pos_0_center_; - } - //=================================================================================================// - Vecd PositionSolidBody::getDisplacement(size_t index_i, Real dt) - { - Vecd displacement; - try { - // displacement from the initial position - Vecd pos_final = pos_0_[index_i] + translation_; - displacement = (pos_final - pos_n_[index_i]) * dt / (end_time_ - GlobalStaticVariables::physical_time_); - } - catch(out_of_range& e){ - throw runtime_error(string("PositionSolidBody::getDisplacement: particle index out of bounds") + to_string(index_i)); - } - return displacement; - } - //=================================================================================================// - void PositionSolidBody::Update(size_t index_i, Real dt) - { - try { - // only apply in the defined time period - if (GlobalStaticVariables::physical_time_ >= start_time_ && GlobalStaticVariables::physical_time_ <= end_time_) - { - pos_n_[index_i] = pos_n_[index_i] + getDisplacement(index_i, dt); // displacement from the initial position - vel_n_[index_i] = getVelocity(); - dvel_dt_[index_i] = getAcceleration(); - /** the average values are prescirbed also. */ - vel_ave_[index_i] = vel_n_[index_i]; - dvel_dt_ave_[index_i] = dvel_dt_[index_i]; - } - } - catch(out_of_range& e){ - throw runtime_error(string("PositionSolidBody::Update: particle index out of bounds") + to_string(index_i)); - } - } - //=================================================================================================// - PositionScaleSolidBody:: - PositionScaleSolidBody(SPHBody* body, BodyPartByParticle* body_part, Real start_time, Real end_time, Real end_scale): - PartSimpleDynamicsByParticle(body, body_part), SolidDataSimple(body), - pos_n_(particles_->pos_n_), pos_0_(particles_->pos_0_), - vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), - vel_ave_(particles_->vel_ave_), dvel_dt_ave_(particles_->dvel_dt_ave_), - start_time_(start_time), end_time_(end_time), end_scale_(end_scale) - { - BoundingBox bounds = body->getBodyDomainBounds(); - pos_0_center_ = (bounds.first + bounds.second) * 0.5; - } - //=================================================================================================// - Vecd PositionScaleSolidBody::getDisplacement(size_t index_i, Real dt) - { - Vecd displacement; - try { - // displacement from the initial position - Vecd pos_final = pos_0_center_ + end_scale_ * (pos_0_[index_i] - pos_0_center_); - displacement = (pos_final - pos_n_[index_i]) * dt / (end_time_ - GlobalStaticVariables::physical_time_); - } - catch(out_of_range& e){ - throw runtime_error(string("PositionScaleSolidBody::getDisplacement: particle index out of bounds") + to_string(index_i)); - } - return displacement; - } - //=================================================================================================// - void PositionScaleSolidBody::Update(size_t index_i, Real dt) - { - try { - // only apply in the defined time period - if (GlobalStaticVariables::physical_time_ >= start_time_ && GlobalStaticVariables::physical_time_ <= end_time_) - { - pos_n_[index_i] = pos_n_[index_i] + getDisplacement(index_i, dt); // displacement from the initial position - vel_n_[index_i] = getVelocity(); - dvel_dt_[index_i] = getAcceleration(); - /** the average values are prescirbed also. */ - vel_ave_[index_i] = vel_n_[index_i]; - dvel_dt_ave_[index_i] = dvel_dt_[index_i]; - } - } - catch(out_of_range& e){ - throw runtime_error(string("PositionScaleSolidBody::Update: particle index out of bounds") + to_string(index_i)); - } - } - //=================================================================================================// - TranslateSolidBody:: - TranslateSolidBody(SPHBody* body, BodyPartByParticle* body_part, Real start_time, Real end_time, Vecd translation): - PartSimpleDynamicsByParticle(body, body_part), SolidDataSimple(body), - pos_n_(particles_->pos_n_), pos_0_(particles_->pos_0_), - vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), - vel_ave_(particles_->vel_ave_), dvel_dt_ave_(particles_->dvel_dt_ave_), - start_time_(start_time), end_time_(end_time), translation_(translation) - { - } - //=================================================================================================// - Vecd TranslateSolidBody::getDisplacement(size_t index_i, Real dt) - { - // displacement from the initial position - Vecd displacement = translation_ * dt / (end_time_ - GlobalStaticVariables::physical_time_); - return displacement; - } - //=================================================================================================// - void TranslateSolidBody::Update(size_t index_i, Real dt) - { - try { - // only apply in the defined time period - if (GlobalStaticVariables::physical_time_ >= start_time_ && GlobalStaticVariables::physical_time_ <= end_time_) - { - pos_n_[index_i] = pos_n_[index_i] + getDisplacement(index_i, dt); // displacement from the initial position - vel_n_[index_i] = getVelocity(); - dvel_dt_[index_i] = getAcceleration(); - /** the average values are prescirbed also. */ - vel_ave_[index_i] = vel_n_[index_i]; - dvel_dt_ave_[index_i] = dvel_dt_[index_i]; - } - } - catch(out_of_range& e){ - throw runtime_error(string("TranslateSolidBody::Update: particle index out of bounds") + to_string(index_i)); - } - } - //=================================================================================================// - SoftConstrainSolidBodyRegion:: - SoftConstrainSolidBodyRegion(BaseBodyRelationInner* body_inner_relation, BodyPartByParticle* body_part) : - PartInteractionDynamicsByParticleWithUpdate(body_inner_relation->sph_body_, body_part), - SolidDataInner(body_inner_relation), - Vol_(particles_->Vol_), - vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), - vel_ave_(particles_->vel_ave_), dvel_dt_ave_(particles_->dvel_dt_ave_), - vel_temp_(*particles_->createAVariable("TemporaryVelocity")), - dvel_dt_temp_(*particles_->createAVariable("TemporaryAcceleration")) {} - //=================================================================================================// - void SoftConstrainSolidBodyRegion::Interaction(size_t index_i, Real dt) - { - Real ttl_weight(Eps); - Vecd vel_i = vel_n_[index_i]; - Vecd dvel_dt_i = dvel_dt_[index_i]; - - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - Real weight_j = inner_neighborhood.W_ij_[n] * Vol_[index_j]; - - ttl_weight += weight_j; - vel_i += vel_n_[index_j] * weight_j; - dvel_dt_i += dvel_dt_[index_j] * weight_j; - } - - vel_temp_[index_i] = vel_i / ttl_weight; - dvel_dt_temp_[index_i] = dvel_dt_i / ttl_weight; - } - //=================================================================================================// - void SoftConstrainSolidBodyRegion::Update(size_t index_i, Real dt) - { - vel_n_[index_i] = vel_temp_[index_i]; - dvel_dt_[index_i] = dvel_dt_temp_[index_i]; - /** the average values are prescirbed also. */ - vel_ave_[index_i] = vel_n_[index_i]; - dvel_dt_ave_[index_i] = dvel_dt_[index_i]; - } - //=================================================================================================// - ClampConstrainSolidBodyRegion:: - ClampConstrainSolidBodyRegion(BaseBodyRelationInner* body_inner_relation, BodyPartByParticle* body_part) : - ParticleDynamics(body_inner_relation->sph_body_), - constrianing_(new ConstrainSolidBodyRegion(body_inner_relation->sph_body_, body_part)), - softing_(new SoftConstrainSolidBodyRegion(body_inner_relation, body_part)) {} - //=================================================================================================// - void ClampConstrainSolidBodyRegion::exec(Real dt) - { - constrianing_->exec(); - softing_->exec(); - } - //=================================================================================================// - void ClampConstrainSolidBodyRegion::parallel_exec(Real dt) - { - constrianing_->parallel_exec(); - softing_->parallel_exec(); - } - //=================================================================================================// - ConstrainSolidBodyMassCenter:: - ConstrainSolidBodyMassCenter(SPHBody* body, Vecd constrain_direction) : - ParticleDynamicsSimple(body), SolidDataSimple(body), - correction_matrix_(Matd(1.0)), vel_n_(particles_->vel_n_) - { - for (int i = 0; i != Dimensions; ++i) correction_matrix_[i][i] = constrain_direction[i]; - BodySummation compute_total_mass_(body, "Mass"); - total_mass_ = compute_total_mass_.parallel_exec(); - compute_total_momentum_ = new BodyMoment(body, "Velocity"); - } - //=================================================================================================// - void ConstrainSolidBodyMassCenter::setupDynamics(Real dt) - { - velocity_correction_ = - correction_matrix_ * compute_total_momentum_->parallel_exec(dt) / total_mass_; - } - //=================================================================================================// - void ConstrainSolidBodyMassCenter::Update(size_t index_i, Real dt) - { - vel_n_[index_i] -= velocity_correction_; - } - //=================================================================================================// - ImposeExternalForce:: - ImposeExternalForce(SolidBody* body, SolidBodyPartForSimbody* body_part) : - PartSimpleDynamicsByParticle(body, body_part), SolidDataSimple(body), - pos_0_(particles_->pos_0_), vel_n_(particles_->vel_n_), - vel_ave_(particles_->vel_ave_) {} - //=================================================================================================// - void ImposeExternalForce::Update(size_t index_i, Real dt) - { - Vecd induced_acceleration = getAcceleration(pos_0_[index_i]); - vel_n_[index_i] += induced_acceleration * dt; - vel_ave_[index_i] = vel_n_[index_i]; - } - //=================================================================================================// - SpringDamperConstraintParticleWise - ::SpringDamperConstraintParticleWise(SolidBody* body, Vecd stiffness, Real damping_ratio) - : ParticleDynamicsSimple(body), SolidDataSimple(body), - pos_n_(particles_->pos_n_), - pos_0_(particles_->pos_0_), - vel_n_(particles_->vel_n_), - dvel_dt_prior_(particles_->dvel_dt_prior_) - { - // calculate total mass - total_mass_ = 0.0; - for (size_t i = 0; i < particles_->mass_.size(); i++) - { - total_mass_ += particles_->mass_[i]; - } - // scale stiffness and damping by mass here, so it's not necessary in each iteration - stiffness_ = stiffness / total_mass_; - damping_coeff_ = stiffness * damping_ratio / total_mass_; - } - //=================================================================================================// - SpringDamperConstraintParticleWise::~SpringDamperConstraintParticleWise() - {} - //=================================================================================================// - void SpringDamperConstraintParticleWise::setupDynamics(Real dt) - { - particles_->total_ghost_particles_ = 0; - } - //=================================================================================================// - Vecd SpringDamperConstraintParticleWise::getSpringForce(size_t index_i, Vecd& disp) - { - Vecd spring_force(0); - for(int i = 0; i < disp.size(); i++) - { - spring_force[i] = -stiffness_[i] * disp[i]; - } - return spring_force; - } - //=================================================================================================// - Vecd SpringDamperConstraintParticleWise::getDampingForce(size_t index_i) - { - Vecd damping_force(0); - for(int i = 0; i < vel_n_[index_i].size(); i++) - { - damping_force[i] = -damping_coeff_[i] * vel_n_[index_i][i]; - } - return damping_force; - } - //=================================================================================================// - void SpringDamperConstraintParticleWise::Update(size_t index_i, Real dt) - { - Vecd delta_x = pos_n_[index_i] - pos_0_[index_i]; - dvel_dt_prior_[index_i] += getSpringForce(index_i, delta_x); - dvel_dt_prior_[index_i] += getDampingForce(index_i); - } - //=================================================================================================// - AccelerationForBodyPartInBoundingBox:: - AccelerationForBodyPartInBoundingBox(SolidBody* body, BoundingBox* bounding_box, Vecd acceleration) : - ParticleDynamicsSimple(body), SolidDataSimple(body), - pos_n_(particles_->pos_n_), - dvel_dt_prior_(particles_->dvel_dt_prior_), - bounding_box_(bounding_box), - acceleration_(acceleration){} - //=================================================================================================// - void AccelerationForBodyPartInBoundingBox::setupDynamics(Real dt) - { - particles_->total_ghost_particles_ = 0; - } - //=================================================================================================// - void AccelerationForBodyPartInBoundingBox::Update(size_t index_i, Real dt) - { - if (pos_n_.size() > index_i) - { - Vecd point = pos_n_[index_i]; - if (point.size() >= 3 && bounding_box_ != nullptr && bounding_box_->first.size() >= 3 && - bounding_box_->second.size() >= 3 && point[0] >= bounding_box_->first[0] && - point[0] <= bounding_box_->second[0] && - point[1] >= bounding_box_->first[1] && point[1] <= bounding_box_->second[1] && - point[2] >= bounding_box_->first[2] && point[2] <= bounding_box_->second[2]) - { - dvel_dt_prior_[index_i] += acceleration_; - } - } - } - //=================================================================================================// - ElasticDynamicsInitialCondition:: - ElasticDynamicsInitialCondition(SolidBody* body) : - ParticleDynamicsSimple(body), - ElasticSolidDataSimple(body), - pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_) - { - } - //=================================================================================================// - UpdateElasticNormalDirection:: - UpdateElasticNormalDirection(SolidBody* elastic_body) : - ParticleDynamicsSimple(elastic_body), - ElasticSolidDataSimple(elastic_body), - n_(particles_->n_), n_0_(particles_->n_0_), F_(particles_->F_) - { - } - //=================================================================================================// - DeformationGradientTensorBySummation:: - DeformationGradientTensorBySummation(BaseBodyRelationInner* body_inner_relation) : - InteractionDynamics(body_inner_relation->sph_body_), - ElasticSolidDataInner(body_inner_relation), - Vol_(particles_->Vol_), pos_n_(particles_->pos_n_), - B_(particles_->B_), F_(particles_->F_) - { - } - //=================================================================================================// - void DeformationGradientTensorBySummation::Interaction(size_t index_i, Real dt) - { - Vecd& pos_n_i = pos_n_[index_i]; - - Matd deformation(0.0); - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - - Vecd gradw_ij = inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]; - deformation -= Vol_[index_j] * SimTK::outer((pos_n_i - pos_n_[index_j]), gradw_ij); - } - - F_[index_i] = B_[index_i] * deformation; - } - //=================================================================================================// - BaseElasticRelaxation:: - BaseElasticRelaxation(BaseBodyRelationInner* body_inner_relation) : - ParticleDynamics1Level(body_inner_relation->sph_body_), - ElasticSolidDataInner(body_inner_relation), Vol_(particles_->Vol_), - rho_n_(particles_->rho_n_), mass_(particles_->mass_), - pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), - B_(particles_->B_), F_(particles_->F_), dF_dt_(particles_->dF_dt_) {} - //=================================================================================================// - StressRelaxationFirstHalf:: - StressRelaxationFirstHalf(BaseBodyRelationInner* body_inner_relation) : - BaseElasticRelaxation(body_inner_relation), - dvel_dt_prior_(particles_->dvel_dt_prior_), force_from_fluid_(particles_->force_from_fluid_), - stress_PK1_(particles_->stress_PK1_) - { - rho0_ = material_->ReferenceDensity(); - inv_rho0_ = 1.0 / rho0_; - smoothing_length_ = particle_adaptation_->ReferenceSmoothingLength(); - numerical_dissipation_factor_ = 0.25; - } - //=================================================================================================// - void StressRelaxationFirstHalf::Initialization(size_t index_i, Real dt) - { - pos_n_[index_i] += vel_n_[index_i] * dt * 0.5; - F_[index_i] += dF_dt_[index_i] * dt * 0.5; - rho_n_[index_i] = rho0_ / det(F_[index_i]); - //obtain the first Piola-Kirchhoff stress from the second Piola-Kirchhoff stress - stress_PK1_[index_i] = F_[index_i] * material_->ConstitutiveRelation(F_[index_i], index_i); - } - //=================================================================================================// - void StressRelaxationFirstHalf::Interaction(size_t index_i, Real dt) - { - //including gravity and force from fluid - Vecd acceleration = dvel_dt_prior_[index_i] - + force_from_fluid_[index_i] / mass_[index_i]; - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - Vecd e_ij = inner_neighborhood.e_ij_[n]; - Real r_ij = inner_neighborhood.r_ij_[n]; - Real dim_r_ij_1 = Dimensions / r_ij; - Vecd pos_jump = pos_n_[index_i] - pos_n_[index_j]; - Vecd vel_jump = vel_n_[index_i] - vel_n_[index_j]; - Real strain_rate = SimTK::dot(pos_jump, vel_jump) * dim_r_ij_1 * dim_r_ij_1; - Real weight = inner_neighborhood.W_ij_[n] * inv_W0_; - Matd numerical_stress_ij = 0.5 * (F_[index_i] + F_[index_j]) * material_->NumericalDamping(strain_rate, smoothing_length_); - acceleration += (stress_PK1_[index_i] + stress_PK1_[index_j] + numerical_dissipation_factor_ * weight * numerical_stress_ij) - * inner_neighborhood.dW_ij_[n] * e_ij * Vol_[index_j] * inv_rho0_; - } - - dvel_dt_[index_i] = acceleration; - } - //=================================================================================================// - void StressRelaxationFirstHalf::Update(size_t index_i, Real dt) - { - vel_n_[index_i] += dvel_dt_[index_i] * dt; - } - //=================================================================================================// - KirchhoffStressRelaxationFirstHalf:: - KirchhoffStressRelaxationFirstHalf(BaseBodyRelationInner* body_inner_relation) - : StressRelaxationFirstHalf(body_inner_relation), - J_to_minus_2_over_diemsnion_(*particles_->createAVariable("DeterminantTerm")), - stress_on_particle_(*particles_->createAVariable("StressOnParticle")), - inverse_F_T_(*particles_->createAVariable("InverseTransposedDeformation")) {}; - //=================================================================================================// - void KirchhoffStressRelaxationFirstHalf::Initialization(size_t index_i, Real dt) - { - pos_n_[index_i] += vel_n_[index_i] * dt * 0.5; - F_[index_i] += dF_dt_[index_i] * dt * 0.5; - Real J = det(F_[index_i]); - Real one_over_J = 1.0 / J; - rho_n_[index_i] = rho0_ * one_over_J; - J_to_minus_2_over_diemsnion_[index_i] = pow(one_over_J * one_over_J, one_over_dimensions_); - inverse_F_T_[index_i] = ~SimTK::inverse(F_[index_i]); - Matd be = F_[index_i] * ~F_[index_i]; - stress_on_particle_[index_i] = (Matd(1.0) * 0.5 * material_->BulkModulus() * J * (J - 1.0) - - material_->ShearModulus() * J_to_minus_2_over_diemsnion_[index_i] *(Matd(1.0) * be.trace() * one_over_dimensions_)); - stress_PK1_[index_i] = F_[index_i] * material_->ConstitutiveRelation(F_[index_i], index_i); - } - //=================================================================================================// - void KirchhoffStressRelaxationFirstHalf::Interaction(size_t index_i, Real dt) - { - //including gravity and force from fluid - Vecd acceleration = dvel_dt_prior_[index_i] - + force_from_fluid_[index_i] / mass_[index_i]; - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - Vecd extension = (pos_n_[index_i] - pos_n_[index_j]) / inner_neighborhood.r_ij_[n]; - Matd stress_ij = material_->ShearModulus() * SimTK::outer(extension, extension) - * (J_to_minus_2_over_diemsnion_[index_i] + J_to_minus_2_over_diemsnion_[index_j]); - Vecd extension_rate = (vel_n_[index_i] - vel_n_[index_j]) / inner_neighborhood.r_ij_[n]; - Real strain_rate = SimTK::dot(extension, extension_rate); - Real weight = inner_neighborhood.W_ij_[n] * inv_W0_; - Matd numerical_stress_ij = Matd(1.0) * weight * material_->NumericalDamping(strain_rate, smoothing_length_); - acceleration += ((stress_on_particle_[index_i] + stress_on_particle_[index_j] - + stress_ij + numerical_stress_ij) * (inverse_F_T_[index_i] + inverse_F_T_[index_j]) * 0.5) - * inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n] * Vol_[index_j] * inv_rho0_; - } - dvel_dt_[index_i] = acceleration; - } - //=================================================================================================// - void StressRelaxationSecondHalf::Initialization(size_t index_i, Real dt) - { - pos_n_[index_i] += vel_n_[index_i] * dt * 0.5; - } - //=================================================================================================// - void StressRelaxationSecondHalf::Interaction(size_t index_i, Real dt) - { - Vecd& vel_n_i = vel_n_[index_i]; - - Matd deformation_gradient_change_rate(0); - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - - Vecd gradw_ij = inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]; - deformation_gradient_change_rate -= Vol_[index_j] * SimTK::outer((vel_n_i - vel_n_[index_j]), gradw_ij); - } - - dF_dt_[index_i] = deformation_gradient_change_rate * B_[index_i]; - } - //=================================================================================================// - void StressRelaxationSecondHalf::Update(size_t index_i, Real dt) - { - F_[index_i] += dF_dt_[index_i] * dt * 0.5; - } - //=================================================================================================// - ConstrainSolidBodyPartBySimBody::ConstrainSolidBodyPartBySimBody(SolidBody* body, - SolidBodyPartForSimbody* body_part, - SimTK::MultibodySystem& MBsystem, - SimTK::MobilizedBody& mobod, - SimTK::Force::DiscreteForces& force_on_bodies, - SimTK::RungeKuttaMersonIntegrator& integ) - : ConstrainSolidBodyRegion(body, body_part), - MBsystem_(MBsystem), mobod_(mobod), force_on_bodies_(force_on_bodies), integ_(integ) - { - simbody_state_ = &integ_.getState(); - MBsystem_.realize(*simbody_state_, Stage::Acceleration); - initial_mobod_origin_location_ = mobod_.getBodyOriginLocation(*simbody_state_); - } - //=================================================================================================// - void ConstrainSolidBodyPartBySimBody::setupDynamics(Real dt) - { - body_->setNewlyUpdated(); - simbody_state_ = &integ_.getState(); - MBsystem_.realize(*simbody_state_, Stage::Acceleration); - } - //=================================================================================================// - TotalForceOnSolidBodyPartForSimBody - ::TotalForceOnSolidBodyPartForSimBody(SolidBody* body, - SolidBodyPartForSimbody* body_part, - SimTK::MultibodySystem& MBsystem, - SimTK::MobilizedBody& mobod, - SimTK::Force::DiscreteForces& force_on_bodies, - SimTK::RungeKuttaMersonIntegrator& integ) - : PartDynamicsByParticleReduce>(body, body_part), - SolidDataSimple(body), - force_from_fluid_(particles_->force_from_fluid_), contact_force_(particles_->contact_force_), - pos_n_(particles_->pos_n_), - MBsystem_(MBsystem), mobod_(mobod), force_on_bodies_(force_on_bodies), integ_(integ) - { - initial_reference_ = SpatialVec(Vec3(0), Vec3(0)); - } - //=================================================================================================// - void TotalForceOnSolidBodyPartForSimBody::SetupReduce() - { - simbody_state_ = &integ_.getState(); - MBsystem_.realize(*simbody_state_, Stage::Acceleration); - current_mobod_origin_location_ = mobod_.getBodyOriginLocation(*simbody_state_); - } - //=================================================================================================// - } -} diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.h deleted file mode 100644 index b220d272aa..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.h +++ /dev/null @@ -1,618 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file solid_dynamics.h -* @brief Here, we define the algorithm classes for solid dynamics. -* @details We consider here a weakly compressible solids. -* @author Luhui Han, Chi ZHang and Xiangyu Hu -*/ - -#ifndef SOLID_DYNAMICS_H -#define SOLID_DYNAMICS_H - - -#include "all_particle_dynamics.h" -#include "elastic_solid.h" -#include "weakly_compressible_fluid.h" -#include "base_kernel.h" -#include "all_fluid_dynamics.h" - -namespace SPH -{ - template - class BodySummation; - template - class BodyMoment; - - namespace solid_dynamics - { - //---------------------------------------------------------------------- - // for general solid dynamics - //---------------------------------------------------------------------- - typedef DataDelegateSimple SolidDataSimple; - typedef DataDelegateInner SolidDataInner; - typedef DataDelegateContact ContactDynamicsData; - - /** - * @class SolidDynamicsInitialCondition - * @brief set initial condition for solid fluid body - * This is a abstract class to be override for case specific initial conditions. - */ - class SolidDynamicsInitialCondition : - public ParticleDynamicsSimple, public SolidDataSimple - { - public: - SolidDynamicsInitialCondition(SolidBody* body) : - ParticleDynamicsSimple(body), SolidDataSimple(body) {}; - virtual ~SolidDynamicsInitialCondition() {}; - }; - - /** - * @class ContactDensitySummation - * @brief Computing the summation density due to solid-solid contact model. - */ - class ContactDensitySummation : - public PartInteractionDynamicsByParticle, public ContactDynamicsData - { - public: - ContactDensitySummation(SolidBodyRelationContact* solid_body_contact_relation); - virtual ~ContactDensitySummation() {}; - protected: - StdLargeVec& mass_, & contact_density_; - StdVec*> contact_mass_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class ContactForce - * @brief Computing the contact force. - */ - class ContactForce : - public PartInteractionDynamicsByParticle, public ContactDynamicsData - { - public: - ContactForce(SolidBodyRelationContact* solid_body_contact_relation); - virtual ~ContactForce() {}; - protected: - StdLargeVec& contact_density_, & Vol_, & mass_; - StdLargeVec& dvel_dt_prior_, & contact_force_; - StdVec*> contact_contact_density_, contact_Vol_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class DynamicContactForce - * @brief Computing the contact force for problems dominainted by the contact dynamic process itself. - * For example, the high speed impact problems in which the detailed contact behavior is crutial for - * physical sound solutions. Therefore, for simple low speed problem in which contact force is - * used merely prevent penetration. We can still use the simple formualation in the class ContactForce. - * The idea is to introduce conact force based on Riemann problem like formulation, - * in which the artficial dissipation is the main interaction force to prevent - * penetration. Furthermore, a panelty type force is used as supplementrary to prevent penetration - * when the contact velocity is small. - */ - class DynamicContactForce : - public PartInteractionDynamicsByParticle, public ContactDynamicsData - { - public: - DynamicContactForce(SolidBodyRelationContact* solid_body_contact_relation, Real penalty_strength = 1.0); - virtual ~DynamicContactForce() {}; - protected: - StdLargeVec& Vol_, & mass_; - StdLargeVec& vel_n_, & dvel_dt_prior_, & contact_force_; - StdVec*> contact_Vol_; - StdVec*>contact_vel_n_; - Real penalty_strength_; - StdVec contact_impedence_, contact_reference_pressure_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class ContactForceWithWall - * @brief Computing the contact force with a rigid wall. - * Note that the body surface of the wall should be - * updated beforce computing the contact force. - */ - class ContactForceWithWall : - public PartInteractionDynamicsByParticle, public ContactDynamicsData - { - public: - ContactForceWithWall(SolidBodyRelationContact* solid_body_contact_relation, Real penalty_strength = 1.0); - virtual ~ContactForceWithWall() {}; - protected: - StdLargeVec& Vol_, & mass_; - StdLargeVec& vel_n_, & dvel_dt_prior_, & contact_force_; - StdVec*> contact_Vol_; - StdVec*>contact_vel_n_, contact_n_; - Real penalty_strength_; - Real impedence_, reference_pressure_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class CorrectConfiguration - * @brief obtain the corrected initial configuration in strong form - */ - class CorrectConfiguration : - public InteractionDynamics, public SolidDataInner - { - public: - CorrectConfiguration(BaseBodyRelationInner* body_inner_relation); - virtual ~CorrectConfiguration() {}; - protected: - StdLargeVec& Vol_; - StdLargeVec& B_; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class ConstrainSolidBodyRegion - * @brief Constrain a solid body part with prescribed motion. - * Note the average values for FSI are prescirbed also. - */ - class ConstrainSolidBodyRegion : - public PartSimpleDynamicsByParticle, public SolidDataSimple - { - public: - ConstrainSolidBodyRegion(SPHBody* body, BodyPartByParticle* body_part); - virtual ~ConstrainSolidBodyRegion() {}; - protected: - StdLargeVec& pos_n_, & pos_0_; - StdLargeVec& n_, & n_0_; - StdLargeVec& vel_n_, & dvel_dt_, & vel_ave_, & dvel_dt_ave_; - virtual Vecd getDisplacement(Vecd& pos_0, Vecd& pos_n) { return pos_n; }; - virtual Vecd getVelocity(Vecd& pos_0, Vecd& pos_n, Vecd& vel_n) { return Vecd(0); }; - virtual Vecd getAcceleration(Vecd& pos_0, Vecd& pos_n, Vecd& dvel_dt) { return Vecd(0); }; - virtual SimTK::Rotation getBodyRotation(Vecd& pos_0, Vecd& pos_n, Vecd& dvel_dt) { return SimTK::Rotation(); } - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class PositionSolidBody - * @brief Moves the body into a defined position in a given time interval - position driven boundary condition - * Note the average values for FSI are prescirbed also. - */ - class PositionSolidBody: - public PartSimpleDynamicsByParticle, public SolidDataSimple - { - public: - PositionSolidBody(SPHBody* body, BodyPartByParticle* body_part, Real start_time, Real end_time, Vecd pos_end_center); - virtual ~PositionSolidBody() {}; - StdLargeVec& GetParticlePos0(){ return pos_0_; }; - StdLargeVec& GetParticlePosN(){ return pos_n_; }; - protected: - StdLargeVec& pos_n_, &pos_0_; - StdLargeVec& vel_n_, &dvel_dt_, &vel_ave_, &dvel_dt_ave_; - Real start_time_, end_time_; - Vecd pos_0_center_, pos_end_center_, translation_; - Vecd getDisplacement(size_t index_i, Real dt); - virtual Vecd getVelocity() { return Vecd(0); }; - virtual Vecd getAcceleration() { return Vecd(0); }; - virtual SimTK::Rotation getBodyRotation() { return SimTK::Rotation(); } - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class PositionScaleSolidBody - * @brief Scales the body in a given time interval - position driven boundary condition - * Note the average values for FSI are prescirbed also. - */ - class PositionScaleSolidBody: - public PartSimpleDynamicsByParticle, public SolidDataSimple - { - public: - PositionScaleSolidBody(SPHBody* body, BodyPartByParticle* body_part, Real start_time, Real end_time, Real end_scale); - virtual ~PositionScaleSolidBody() {}; - StdLargeVec& GetParticlePos0(){ return pos_0_; }; - StdLargeVec& GetParticlePosN(){ return pos_n_; }; - protected: - StdLargeVec& pos_n_, &pos_0_; - StdLargeVec& vel_n_, &dvel_dt_, &vel_ave_, &dvel_dt_ave_; - Real start_time_, end_time_, end_scale_; - Vecd pos_0_center_; - Vecd getDisplacement(size_t index_i, Real dt); - virtual Vecd getVelocity() { return Vecd(0); }; - virtual Vecd getAcceleration() { return Vecd(0); }; - virtual SimTK::Rotation getBodyRotation() { return SimTK::Rotation(); } - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class TranslateSolidBody - * @brief Translates the body in a given time interval -translation driven boundary condition; only moving the body; end position irrelevant; - * Note the average values for FSI are prescirbed also. - */ - class TranslateSolidBody: - public PartSimpleDynamicsByParticle, public SolidDataSimple - { - public: - TranslateSolidBody(SPHBody* body, BodyPartByParticle* body_part, Real start_time, Real end_time, Vecd translation); - virtual ~TranslateSolidBody() {}; - StdLargeVec& GetParticlePos0(){ return pos_0_; }; - StdLargeVec& GetParticlePosN(){ return pos_n_; }; - protected: - StdLargeVec& pos_n_, &pos_0_; - StdLargeVec& vel_n_, &dvel_dt_, &vel_ave_, &dvel_dt_ave_; - Real start_time_, end_time_; - Vecd translation_; - Vecd getDisplacement(size_t index_i, Real dt); - virtual Vecd getVelocity() { return Vecd(0); }; - virtual Vecd getAcceleration() { return Vecd(0); }; - virtual SimTK::Rotation getBodyRotation() { return SimTK::Rotation(); } - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class ConstrainSolidBodyRegionVelocity - * @brief Constrain the velocity of a solid body part. - */ - class ConstrainSolidBodyRegionVelocity : public ConstrainSolidBodyRegion - { - public: - ConstrainSolidBodyRegionVelocity(SPHBody* body, BodyPartByParticle* body_part, - Vecd constrained_direction = Vecd(0)) : - solid_dynamics::ConstrainSolidBodyRegion(body, body_part), - constrain_matrix_(Matd(1.0)) - { - for (int k = 0; k != Dimensions; ++k) - constrain_matrix_[k][k] = constrained_direction[k]; - }; - virtual ~ConstrainSolidBodyRegionVelocity() {}; - protected: - Matd constrain_matrix_; - virtual Vecd getVelocity(Vecd& pos_0, Vecd& pos_n, Vecd& vel_n) - { - return constrain_matrix_ * vel_n; - }; - }; - - /** - * @class SoftConstrainSolidBodyRegion - * @brief Soft the constrain of a solid body part - */ - class SoftConstrainSolidBodyRegion : - public PartInteractionDynamicsByParticleWithUpdate, - public SolidDataInner - { - public: - SoftConstrainSolidBodyRegion(BaseBodyRelationInner* body_inner_relation, BodyPartByParticle* body_part); - virtual ~SoftConstrainSolidBodyRegion() {}; - protected: - StdLargeVec& Vol_; - StdLargeVec& vel_n_, & dvel_dt_, & vel_ave_, & dvel_dt_ave_; - StdLargeVec& vel_temp_, & dvel_dt_temp_; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class ClampConstrainSolidBodyRegion - * @brief Constrain a solid body part with prescribed motion and smoothing to mimic the clamping effect. - */ - class ClampConstrainSolidBodyRegion : public ParticleDynamics - { - public: - ConstrainSolidBodyRegion* constrianing_; - SoftConstrainSolidBodyRegion* softing_; - - ClampConstrainSolidBodyRegion(BaseBodyRelationInner* body_inner_relation, BodyPartByParticle* body_part); - virtual ~ClampConstrainSolidBodyRegion() {}; - - virtual void exec(Real dt = 0.0) override; - virtual void parallel_exec(Real dt = 0.0) override; - }; - - /** - * @class ConstrainSolidBodyMassCenter - * @brief Constrain the mass center of a solid body. - */ - class ConstrainSolidBodyMassCenter : - public ParticleDynamicsSimple, public SolidDataSimple - { - public: - ConstrainSolidBodyMassCenter(SPHBody* body, Vecd constrain_direction = Vecd(1.0)); - virtual ~ConstrainSolidBodyMassCenter() {}; - protected: - virtual void setupDynamics(Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - private: - Real total_mass_; - Matd correction_matrix_; - Vecd velocity_correction_; - StdLargeVec& vel_n_; - BodyMoment* compute_total_momentum_; - }; - - /**@class ImposeExternalForce - * @brief impose external force on a solid body part - * by add extra acceleration - */ - class ImposeExternalForce : - public PartSimpleDynamicsByParticle, public SolidDataSimple - { - public: - ImposeExternalForce(SolidBody* body, SolidBodyPartForSimbody* body_part); - virtual ~ImposeExternalForce() {}; - protected: - StdLargeVec& pos_0_, & vel_n_, & vel_ave_; - /** - * @brief acceleration will be specified by the application - */ - virtual Vecd getAcceleration(Vecd& pos) = 0; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - /** - * @class SpringDamperConstraintParticleWise - * @brief Exerts spring force and damping force in the form of acceleration to each particle. - * The spring force is calculated based on the difference from the particle's initial position. - * The damping force is calculated based on the particle's current velocity. - * Only for 3D applications - */ - class SpringDamperConstraintParticleWise - : public ParticleDynamicsSimple, public SolidDataSimple - { - public: - SpringDamperConstraintParticleWise(SolidBody* body, Vecd stiffness, Real damping_ratio = 0.05); - ~SpringDamperConstraintParticleWise(); - protected: - Real total_mass_; - StdLargeVec& pos_n_,& pos_0_,& vel_n_,& dvel_dt_prior_; - Vecd stiffness_; - Vecd damping_coeff_; // damping component parallel to the spring force component - - virtual void setupDynamics(Real dt = 0.0) override; - virtual Vecd getSpringForce(size_t index_i, Vecd& disp); - virtual Vecd getDampingForce(size_t index_i); - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - /** - * @class AccelerationForBodyPartInBoundingBox - * @brief Adds acceleration to the part of the body that's inside a bounding box - */ - class AccelerationForBodyPartInBoundingBox - : public ParticleDynamicsSimple, public SolidDataSimple - { - public: - AccelerationForBodyPartInBoundingBox(SolidBody* body, BoundingBox* bounding_box, Vecd acceleration); - virtual ~AccelerationForBodyPartInBoundingBox() {}; - protected: - StdLargeVec& pos_n_,& dvel_dt_prior_; - BoundingBox* bounding_box_; - Vecd acceleration_; - virtual void setupDynamics(Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - //---------------------------------------------------------------------- - // for elastic solid dynamics - //---------------------------------------------------------------------- - typedef DataDelegateSimple ElasticSolidDataSimple; - typedef DataDelegateInner ElasticSolidDataInner; - - /** - * @class ElasticDynamicsInitialCondition - * @brief set initial condition for a solid body with different material - * This is a abstract class to be override for case specific initial conditions. - */ - class ElasticDynamicsInitialCondition : - public ParticleDynamicsSimple, public ElasticSolidDataSimple - { - public: - ElasticDynamicsInitialCondition(SolidBody *body); - virtual ~ElasticDynamicsInitialCondition() {}; - protected: - StdLargeVec& pos_n_, & vel_n_; - }; - - /** - * @class UpdateElasticNormalDirection - * @brief update particle normal directions for elastic solid - */ - class UpdateElasticNormalDirection : - public ParticleDynamicsSimple, public ElasticSolidDataSimple - { - public: - explicit UpdateElasticNormalDirection(SolidBody *elastic_body); - virtual ~UpdateElasticNormalDirection() {}; - protected: - StdLargeVec& n_, & n_0_; - StdLargeVec& F_; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class AcousticTimeStepSize - * @brief Computing the acoustic time step size - * computing time step size - */ - class AcousticTimeStepSize : - public ParticleDynamicsReduce, - public ElasticSolidDataSimple - { - public: - explicit AcousticTimeStepSize(SolidBody* body, Real CFL = 0.6); - virtual ~AcousticTimeStepSize() {}; - protected: - Real CFL_; - StdLargeVec& vel_n_, & dvel_dt_; - Real smoothing_length_; - Real ReduceFunction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class DeformationGradientTensorBySummation - * @brief computing deformation gradient tensor by summation - */ - class DeformationGradientTensorBySummation : - public InteractionDynamics, public ElasticSolidDataInner - { - public: - DeformationGradientTensorBySummation(BaseBodyRelationInner* body_inner_relation); - virtual ~DeformationGradientTensorBySummation() {}; - protected: - StdLargeVec& Vol_; - StdLargeVec& pos_n_; - StdLargeVec& B_, & F_; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class BaseElasticRelaxation - * @brief base class for elastic relaxation - */ - class BaseElasticRelaxation - : public ParticleDynamics1Level, public ElasticSolidDataInner - { - public: - BaseElasticRelaxation(BaseBodyRelationInner* body_inner_relation); - virtual ~BaseElasticRelaxation() {}; - protected: - StdLargeVec& Vol_, & rho_n_, & mass_; - StdLargeVec& pos_n_, & vel_n_, & dvel_dt_; - StdLargeVec& B_, & F_, & dF_dt_; - }; - - /** - * @class StressRelaxationFirstHalf - * @brief computing stress relaxation process by verlet time stepping - * This is the first step - */ - class StressRelaxationFirstHalf : public BaseElasticRelaxation - { - public: - StressRelaxationFirstHalf(BaseBodyRelationInner* body_inner_relation); - virtual ~StressRelaxationFirstHalf() {}; - protected: - Real rho0_, inv_rho0_; - StdLargeVec& dvel_dt_prior_, & force_from_fluid_; - StdLargeVec& stress_PK1_; - Real numerical_dissipation_factor_; - Real smoothing_length_; - Real inv_W0_ = 1.0 / body_->particle_adaptation_->getKernel()->W0(Vecd(0)); - - virtual void Initialization(size_t index_i, Real dt = 0.0) override; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class KirchhoffStressRelaxationFirstHalf - * @brief Decompose the stress into particle stress includes isetropic stress - * and the stress due to non-homogeneous material properties. - * The preliminary shear stress is introduced by particle pair to avoid - * sprurious stress and deforamtion. - */ - class KirchhoffStressRelaxationFirstHalf : public StressRelaxationFirstHalf - { - public: - KirchhoffStressRelaxationFirstHalf(BaseBodyRelationInner* body_inner_relation); - virtual ~KirchhoffStressRelaxationFirstHalf() {}; - protected: - StdLargeVec& J_to_minus_2_over_diemsnion_; - StdLargeVec& stress_on_particle_, & inverse_F_T_; - const Real one_over_dimensions_ = 1.0 / (Real)Dimensions; - - virtual void Initialization(size_t index_i, Real dt = 0.0) override; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class StressRelaxationSecondHalf - * @brief computing stress relaxation process by verlet time stepping - * This is the second step - */ - class StressRelaxationSecondHalf : public BaseElasticRelaxation - { - public: - StressRelaxationSecondHalf(BaseBodyRelationInner* body_inner_relation) : - BaseElasticRelaxation(body_inner_relation) {}; - virtual ~StressRelaxationSecondHalf() {}; - protected: - virtual void Initialization(size_t index_i, Real dt = 0.0) override; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class ConstrainSolidBodyPartBySimBody - * @brief Constrain a solid body part from the motion - * computed from Simbody. - */ - class ConstrainSolidBodyPartBySimBody : public ConstrainSolidBodyRegion - { - public: - ConstrainSolidBodyPartBySimBody(SolidBody* body, - SolidBodyPartForSimbody* body_part, - SimTK::MultibodySystem& MBsystem, - SimTK::MobilizedBody& mobod, - SimTK::Force::DiscreteForces& force_on_bodies, - SimTK::RungeKuttaMersonIntegrator& integ); - virtual ~ConstrainSolidBodyPartBySimBody() {}; - protected: - SimTK::MultibodySystem& MBsystem_; - SimTK::MobilizedBody& mobod_; - SimTK::Force::DiscreteForces& force_on_bodies_; - SimTK::RungeKuttaMersonIntegrator& integ_; - const SimTK::State* simbody_state_; - Vec3d initial_mobod_origin_location_; - - virtual void setupDynamics(Real dt = 0.0) override; - void virtual Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class TotalForceOnSolidBodyPartForSimBody - * @brief Compute the force acting on the solid body part - * for applying to simbody forces latter - */ - class TotalForceOnSolidBodyPartForSimBody : - public PartDynamicsByParticleReduce>, - public SolidDataSimple - { - public: - TotalForceOnSolidBodyPartForSimBody(SolidBody* body, - SolidBodyPartForSimbody* body_part, - SimTK::MultibodySystem& MBsystem, - SimTK::MobilizedBody& mobod, - SimTK::Force::DiscreteForces& force_on_bodies, - SimTK::RungeKuttaMersonIntegrator& integ); - virtual ~TotalForceOnSolidBodyPartForSimBody() {}; - protected: - StdLargeVec& force_from_fluid_, & contact_force_, & pos_n_; - SimTK::MultibodySystem& MBsystem_; - SimTK::MobilizedBody& mobod_; - SimTK::Force::DiscreteForces& force_on_bodies_; - SimTK::RungeKuttaMersonIntegrator& integ_; - const SimTK::State* simbody_state_; - Vec3d current_mobod_origin_location_; - - virtual void SetupReduce() override; - virtual SimTK::SpatialVec ReduceFunction(size_t index_i, Real dt = 0.0) override; - }; - } -} -#endif //SOLID_DYNAMICS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.cpp deleted file mode 100644 index 9e15cc4d00..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.cpp +++ /dev/null @@ -1,521 +0,0 @@ -/** - * @file thin_structure_dynamics.cpp - * @author Dong Wu and Xiangyu Hu - */ - -#include "thin_structure_dynamics.h" -#include "thin_structure_math.h" - -using namespace SimTK; - -namespace SPH -{ - namespace thin_structure_dynamics - { - //=================================================================================================// - ShellDynamicsInitialCondition:: - ShellDynamicsInitialCondition(SolidBody* body) : - ParticleDynamicsSimple(body), - ShellDataSimple(body), - n_0_(particles_->n_0_), n_(particles_->n_), pseudo_n_(particles_->pseudo_n_), - pos_0_(particles_->pos_0_), - transformation_matrix_(particles_->transformation_matrix_) {} - //=================================================================================================// - ShellAcousticTimeStepSize::ShellAcousticTimeStepSize(SolidBody* body) : - ParticleDynamicsReduce(body), - ShellDataSimple(body), - vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), - angular_vel_(particles_->angular_vel_), dangular_vel_dt_(particles_->dangular_vel_dt_), - shell_thickness_(particles_->shell_thickness_) - { - smoothing_length_ = particle_adaptation_->ReferenceSmoothingLength(); - initial_reference_ = DBL_MAX; - rho0_ = material_->ReferenceDensity(); - E0_ = material_->YoungsModulus(); - nu_ = material_->PoissonRatio(); - } - //=================================================================================================// - Real ShellAcousticTimeStepSize::ReduceFunction(size_t index_i, Real dt) - { - // Since the particle does not change its configuration in pressure relaxation step, - // I chose a time-step size according to Eulerian method. - Real sound_speed = material_->ReferenceSoundSpeed(); - Real time_setp_0 = 0.6 * SMIN(sqrt(smoothing_length_ / (dvel_dt_[index_i].norm() + TinyReal)), - smoothing_length_ / (sound_speed + vel_n_[index_i].norm())); - Real time_setp_1 = 0.6 * SMIN(sqrt(1.0 / (dangular_vel_dt_[index_i].norm() + TinyReal)), - 1.0 / (angular_vel_[index_i].norm() + TinyReal)); - Real time_setp_2 = smoothing_length_ * sqrt(rho0_ * (1.0 - nu_ * nu_) / E0_ / - (2.0 + (Pi * Pi / 12.0) * (1.0 - nu_) * (1.0 + 1.5 * powerN(smoothing_length_ / shell_thickness_[index_i], 2))) - ); - return SMIN(time_setp_0, time_setp_1, time_setp_2); - } - //=================================================================================================// - ShellCorrectConfiguration:: - ShellCorrectConfiguration(BaseBodyRelationInner* body_inner_relation) : - InteractionDynamics(body_inner_relation->sph_body_), - ShellDataInner(body_inner_relation), - Vol_(particles_->Vol_), B_(particles_->B_), - n_0_(particles_->n_0_), transformation_matrix_(particles_->transformation_matrix_) {} - //=================================================================================================// - void ShellCorrectConfiguration::Interaction(size_t index_i, Real dt) - { - /** A small number is added to diagonal to avoid dividing by zero. */ - Matd global_configuration(Eps); - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - - Vecd gradw_ij = inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]; - Vecd r_ji = -inner_neighborhood.r_ij_[n] * inner_neighborhood.e_ij_[n]; - global_configuration += Vol_[index_j] * SimTK::outer(r_ji, gradw_ij); - } - Matd local_configuration = - transformation_matrix_[index_i] * global_configuration * (~transformation_matrix_[index_i]); - /** correction matrix is obtained from local configuration. */ - B_[index_i] = SimTK::inverse(local_configuration) * reduced_unit_matrix; - } - //=================================================================================================// - ShellDeformationGradientTensor:: - ShellDeformationGradientTensor(BaseBodyRelationInner* body_inner_relation) : - InteractionDynamics(body_inner_relation->sph_body_), - ShellDataInner(body_inner_relation), - Vol_(particles_->Vol_), pos_n_(particles_->pos_n_), - pseudo_n_(particles_->pseudo_n_), n_0_(particles_->n_0_), - B_(particles_->B_), F_(particles_->F_), F_bending_(particles_->F_bending_), - transformation_matrix_(particles_->transformation_matrix_) {} - //=================================================================================================// - void ShellDeformationGradientTensor::Interaction(size_t index_i, Real dt) - { - Vecd& pseudo_n_i = pseudo_n_[index_i]; - Vecd& pos_n_i = pos_n_[index_i]; - Matd& transformation_matrix_i = transformation_matrix_[index_i]; - - Matd deformation_part_one(0.0); - Matd deformation_part_two(0.0); - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - Vecd gradw_ij = inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]; - deformation_part_one -= Vol_[index_j] * SimTK::outer((pos_n_i - pos_n_[index_j]), gradw_ij); - deformation_part_two -= Vol_[index_j] * SimTK::outer( - ((pseudo_n_i - n_0_[index_i]) - (pseudo_n_[index_j] - n_0_[index_j])), gradw_ij); - } - F_[index_i] = transformation_matrix_i * deformation_part_one * (~transformation_matrix_i) * B_[index_i]; - F_[index_i].col(Dimensions - 1) = transformation_matrix_i * pseudo_n_[index_i]; - F_bending_[index_i] = transformation_matrix_i * deformation_part_two * (~transformation_matrix_i) * B_[index_i]; - } - //=================================================================================================// - BaseShellRelaxation::BaseShellRelaxation(BaseBodyRelationInner* body_inner_relation) : - ParticleDynamics1Level(body_inner_relation->sph_body_), - ShellDataInner(body_inner_relation), Vol_(particles_->Vol_), - rho_n_(particles_->rho_n_), mass_(particles_->mass_), - shell_thickness_(particles_->shell_thickness_), - pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), - dvel_dt_prior_(particles_->dvel_dt_prior_), force_from_fluid_(particles_->force_from_fluid_), - n_0_(particles_->n_0_), pseudo_n_(particles_->pseudo_n_), - dpseudo_n_dt_(particles_->dpseudo_n_dt_), dpseudo_n_d2t_(particles_->dpseudo_n_d2t_), - rotation_(particles_->rotation_), angular_vel_(particles_->angular_vel_), - dangular_vel_dt_(particles_->dangular_vel_dt_), - B_(particles_->B_), F_(particles_->F_), dF_dt_(particles_->dF_dt_), - F_bending_(particles_->F_bending_), dF_bending_dt_(particles_->dF_bending_dt_), - transformation_matrix_(particles_->transformation_matrix_) {} - //=================================================================================================// - ShellStressRelaxationFirstHalf:: - ShellStressRelaxationFirstHalf(BaseBodyRelationInner* body_inner_relation, - int number_of_gaussian_points) : BaseShellRelaxation(body_inner_relation), - stress_PK1_(particles_->stress_PK1_), - global_stress_(particles_->global_stress_), - global_moment_(particles_->global_moment_), - global_shear_stress_(particles_->global_shear_stress_), - n_(particles_->n_), - number_of_gaussian_points_(number_of_gaussian_points) - { - rho0_ = material_->ReferenceDensity(); - inv_rho0_ = 1.0 / rho0_; - smoothing_length_ = particle_adaptation_->ReferenceSmoothingLength(); - - /** Note that, only three-point and five-point Gaussian quadrature rules are defined. */ - switch (number_of_gaussian_points) - { - case 5: - gaussian_point_ = five_gaussian_points_; - gaussian_weight_ = five_gaussian_weights_; - break; - default: - gaussian_point_ = three_gaussian_points_; - gaussian_weight_ = three_gaussian_weights_; - } - } - //=================================================================================================// - void ShellStressRelaxationFirstHalf::Initialization(size_t index_i, Real dt) - { - // Note that F_[index_i], F_bending_[index_i], dF_dt_[index_i], dF_bending_dt_[index_i] - // and rotation_[index_i], angular_vel_[index_i], dangular_vel_dt_[index_i] - // are defined in local coordinates, while others in global coordinates. - pos_n_[index_i] += vel_n_[index_i] * dt * 0.5; - rotation_[index_i] += angular_vel_[index_i] * dt * 0.5; - pseudo_n_[index_i] += dpseudo_n_dt_[index_i] * dt * 0.5; - - F_[index_i] += dF_dt_[index_i] * dt * 0.5; - F_bending_[index_i] += dF_bending_dt_[index_i] * dt * 0.5; - rho_n_[index_i] = rho0_ / det(F_[index_i]); - - /** Calculate the current normal direction of mid-surface. */ - Matd F_i = F_[index_i]; - F_i.col(Dimensions - 1) = Vecd(0.0); - n_[index_i] = (~transformation_matrix_[index_i]) * getNormalFromDeformationGradientTensor(F_i); - /** Get transformation matrix from global coordinates to current local coordinates. */ - Matd current_transformation_matrix = getTransformationMatrix(n_[index_i]); - - /** Initialize the local stress to 0. */ - Matd resultant_stress(0); - Matd resultant_moment(0); - Vecd resultant_shear_stress(0); - for (int i = 0; i != number_of_gaussian_points_; ++i) - { - Matd F_gaussian_point = F_[index_i] + gaussian_point_[i] * F_bending_[index_i] * shell_thickness_[index_i] * 0.5; - Matd dF_gaussian_point_dt = dF_dt_[index_i] + gaussian_point_[i] * dF_bending_dt_[index_i] * shell_thickness_[index_i] * 0.5; - Matd stress_PK2_gaussian_point = material_->ConstitutiveRelation(F_gaussian_point, index_i) - + material_->NumericalDampingStress(F_gaussian_point, dF_gaussian_point_dt, smoothing_length_, index_i); - - /** Get the mid-surface stress to output the von-Mises equivalent stress. */ - if (i == 0) stress_PK1_[index_i] = F_gaussian_point * stress_PK2_gaussian_point; - - /** Get Cauchy stress. */ - Matd cauchy_stress = current_transformation_matrix * (~transformation_matrix_[index_i]) - * F_gaussian_point * stress_PK2_gaussian_point * (~F_gaussian_point) - * transformation_matrix_[index_i] * (~current_transformation_matrix) / det(F_gaussian_point); - - /** Impose modeling assumptions. */ - cauchy_stress.col(Dimensions - 1) *= shear_correction_factor_; - cauchy_stress.row(Dimensions - 1) *= shear_correction_factor_; - cauchy_stress[Dimensions - 1][Dimensions - 1] = 0.0; - - stress_PK2_gaussian_point = det(F_gaussian_point) * SimTK::inverse(F_gaussian_point) - * transformation_matrix_[index_i] * (~current_transformation_matrix) * cauchy_stress - * current_transformation_matrix * (~transformation_matrix_[index_i]) - * (~SimTK::inverse(F_gaussian_point)); - Vecd shear_stress_PK2_gaussian_point = -stress_PK2_gaussian_point.col(Dimensions - 1); - Matd moment_PK2_gaussian_point = stress_PK2_gaussian_point * gaussian_point_[i] * shell_thickness_[index_i] * 0.5; - - resultant_stress += - 0.5 * shell_thickness_[index_i] * gaussian_weight_[i] * F_gaussian_point * stress_PK2_gaussian_point; - resultant_moment += - 0.5 * shell_thickness_[index_i] * gaussian_weight_[i] * F_gaussian_point * moment_PK2_gaussian_point; - resultant_shear_stress += - 0.5 * shell_thickness_[index_i] * gaussian_weight_[i] * F_gaussian_point * shear_stress_PK2_gaussian_point; - } - /** Only one (for 2D) or two (for 3D) angular momentum equations left. */ - resultant_moment.col(Dimensions - 1) = Vecd(0); - resultant_moment.row(Dimensions - 1) = ~Vecd(0); - resultant_shear_stress[Dimensions - 1] = 0.0; - - /** stress and moment in global coordinates for pair interaction */ - global_stress_[index_i] = - (~transformation_matrix_[index_i]) * resultant_stress * transformation_matrix_[index_i]; - global_moment_[index_i] = - (~transformation_matrix_[index_i]) * resultant_moment * transformation_matrix_[index_i]; - global_shear_stress_[index_i] = (~transformation_matrix_[index_i]) * resultant_shear_stress; - } - //=================================================================================================// - void ShellStressRelaxationFirstHalf::Interaction(size_t index_i, Real dt) - { - Vecd& global_shear_stress_i = global_shear_stress_[index_i]; - Matd& global_stress_i = global_stress_[index_i]; - Matd& global_moment_i = global_moment_[index_i]; - - Vecd acceleration(0.0); - Vecd pseudo_normal_acceleration = global_shear_stress_i; - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - acceleration += (global_stress_i + global_stress_[index_j]) - * inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n] * Vol_[index_j]; - pseudo_normal_acceleration += (global_moment_i + global_moment_[index_j]) - * inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n] * Vol_[index_j]; - } - /** including external force (body force) and force from fluid */ - dvel_dt_[index_i] = acceleration * inv_rho0_ / shell_thickness_[index_i] - + dvel_dt_prior_[index_i] + force_from_fluid_[index_i] / mass_[index_i]; - dpseudo_n_d2t_[index_i] = pseudo_normal_acceleration * inv_rho0_ - * 12.0 / powerN(shell_thickness_[index_i], 3); - - /** the relation between pseudo-normal and rotations */ - Vecd local_dpseudo_n_d2t = transformation_matrix_[index_i] * dpseudo_n_d2t_[index_i]; - dangular_vel_dt_[index_i] = getRotationFromPseudoNormalForSmallDeformation - (local_dpseudo_n_d2t, rotation_[index_i], angular_vel_[index_i], dt); - } - //=================================================================================================// - void ShellStressRelaxationFirstHalf::Update(size_t index_i, Real dt) - { - vel_n_[index_i] += dvel_dt_[index_i] * dt; - angular_vel_[index_i] += dangular_vel_dt_[index_i] * dt; - } - //=================================================================================================// - void ShellStressRelaxationSecondHalf::Initialization(size_t index_i, Real dt) - { - pos_n_[index_i] += vel_n_[index_i] * dt * 0.5; - rotation_[index_i] += angular_vel_[index_i] * dt * 0.5; - dpseudo_n_dt_[index_i] = (~transformation_matrix_[index_i]) - * getVectorChangeRateAfterThinStructureRotation(local_pseudo_n_0, rotation_[index_i], angular_vel_[index_i]); - pseudo_n_[index_i] += dpseudo_n_dt_[index_i] * dt * 0.5; - } - //=================================================================================================// - void ShellStressRelaxationSecondHalf::Interaction(size_t index_i, Real dt) - { - Vecd& vel_n_i = vel_n_[index_i]; - Vecd& dpseudo_n_dt_i = dpseudo_n_dt_[index_i]; - Matd& transformation_matrix_i = transformation_matrix_[index_i]; - - Matd deformation_gradient_change_rate_part_one(0.0); - Matd deformation_gradient_change_rate_part_two(0.0); - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - - Vecd gradw_ij = inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]; - deformation_gradient_change_rate_part_one -= Vol_[index_j] * SimTK::outer( - (vel_n_i - vel_n_[index_j]), gradw_ij); - deformation_gradient_change_rate_part_two -= Vol_[index_j] * SimTK::outer( - (dpseudo_n_dt_i - dpseudo_n_dt_[index_j]), gradw_ij); - } - dF_dt_[index_i] = transformation_matrix_i * deformation_gradient_change_rate_part_one - * (~transformation_matrix_i) * B_[index_i]; - dF_dt_[index_i].col(Dimensions - 1) = transformation_matrix_i * dpseudo_n_dt_[index_i]; - dF_bending_dt_[index_i] = transformation_matrix_i * deformation_gradient_change_rate_part_two - * (~transformation_matrix_i) * B_[index_i]; - } - //=================================================================================================// - void ShellStressRelaxationSecondHalf::Update(size_t index_i, Real dt) - { - F_[index_i] += dF_dt_[index_i] * dt * 0.5; - F_bending_[index_i] += dF_bending_dt_[index_i] * dt * 0.5; - } - //=================================================================================================// - ConstrainShellBodyRegion:: - ConstrainShellBodyRegion(SolidBody* body, BodyPartByParticle* body_part) : - PartSimpleDynamicsByParticle(body, body_part), ShellDataSimple(body), - pos_n_(particles_->pos_n_), pos_0_(particles_->pos_0_), n_(particles_->n_), - vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), - vel_ave_(particles_->vel_ave_), dvel_dt_ave_(particles_->dvel_dt_ave_), - rotation_(particles_->rotation_), angular_vel_(particles_->angular_vel_), - dangular_vel_dt_(particles_->dangular_vel_dt_), - pseudo_n_(particles_->pseudo_n_), dpseudo_n_dt_(particles_->dpseudo_n_dt_) - { - } - //=================================================================================================// - void ConstrainShellBodyRegion::Update(size_t index_i, Real dt) - { - Vecd pos_0 = pos_0_[index_i]; - Vecd pos_n = pos_n_[index_i]; - Vecd vel_n = vel_n_[index_i]; - Vecd dvel_dt = dvel_dt_[index_i]; - Vecd rotation_0(0.0); - Vecd angular_vel(0.0); - Vecd dangular_vel_dt(0.0); - Vecd dpseudo_normal_dt(0.0); - - pos_n_[index_i] = getDisplacement(pos_0, pos_n); - vel_n_[index_i] = getVelocity(pos_0, pos_n, vel_n); - dvel_dt_[index_i] = GetAcceleration(pos_0, pos_n, dvel_dt); - rotation_[index_i] = GetRotationAngle(pos_0, pos_n, rotation_0); - angular_vel_[index_i] = GetAngularVelocity(pos_0, pos_n, angular_vel); - dangular_vel_dt_[index_i] = GetAngularAcceleration(pos_0, pos_n, dangular_vel_dt); - pseudo_n_[index_i] = GetPseudoNormal(pos_0, pos_n, local_pseudo_n_0); - dpseudo_n_dt_[index_i] = GetPseudoNormalChangeRate(pos_0, pos_n, dpseudo_normal_dt); - - /** the average values are prescirbed also. */ - vel_ave_[index_i] = vel_n_[index_i]; - dvel_dt_ave_[index_i] = dvel_dt_[index_i]; - } - //=================================================================================================// - FixedFreeRotateShellBoundary:: - FixedFreeRotateShellBoundary(BaseBodyRelationInner* body_inner_relation, - BodyPartByParticle* body_part, Vecd constrained_direction) : - PartInteractionDynamicsByParticle1Level(body_inner_relation->sph_body_, body_part), - ShellDataInner(body_inner_relation), - W0_(particle_adaptation_->getKernel()->W0(Vecd(0))), - constrain_matrix_(Matd(0)), recover_matrix_(Matd(1.0)), - Vol_(particles_->Vol_), vel_n_(particles_->vel_n_), - angular_vel_(particles_->angular_vel_), - vel_n_temp_(*particles_->createAVariable("TemporaryVelocity")), - angular_vel_temp_(*particles_->createAVariable("TemporaryAngularVelocity")) - { - for (int k = 0; k != Dimensions; ++k) - { - constrain_matrix_[k][k] = constrained_direction[k]; - recover_matrix_[k][k] = 1.0 - constrain_matrix_[k][k]; - } - } - //=================================================================================================// - void FixedFreeRotateShellBoundary::Initialization(size_t index_i, Real dt) - { - vel_n_[index_i] = constrain_matrix_ * vel_n_[index_i]; - angular_vel_[index_i] = Vecd(0); - } - //=================================================================================================// - void FixedFreeRotateShellBoundary::Interaction(size_t index_i, Real dt) - { - Real ttl_weight = W0_ * Vol_[index_i]; - Vecd vel_i = recover_matrix_ * vel_n_[index_i] * ttl_weight; - Real ttl_weight_angular = W0_* Vol_[index_i]; - Vecd angular_vel_i = angular_vel_[index_i] * ttl_weight_angular; - - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - Real weight_j = inner_neighborhood.W_ij_[n] * Vol_[index_j]; - - ttl_weight += weight_j; - vel_i += recover_matrix_ * vel_n_[index_j] * weight_j; - //exclude boundary particles to achieve extrapolation - if (angular_vel_[index_j].norm() >= Eps) { - angular_vel_i += angular_vel_[index_j] * weight_j; - ttl_weight_angular += weight_j; - } - } - - vel_n_temp_[index_i] = vel_i / ttl_weight; - angular_vel_temp_[index_i] = angular_vel_i / ttl_weight_angular; - } - //=================================================================================================// - void FixedFreeRotateShellBoundary::Update(size_t index_i, Real dt) - { - vel_n_[index_i] += vel_n_temp_[index_i]; - angular_vel_[index_i] = angular_vel_temp_[index_i]; - } - //=================================================================================================// - ClampConstrainShellBodyRegion:: - ClampConstrainShellBodyRegion(BaseBodyRelationInner* body_inner_relation, BodyPartByParticle* body_part) : - PartInteractionDynamicsByParticle1Level(body_inner_relation->sph_body_, body_part), - ShellDataInner(body_inner_relation), - Vol_(particles_->Vol_), vel_n_(particles_->vel_n_), - angular_vel_(particles_->angular_vel_), - vel_n_temp_(*particles_->createAVariable("TemporaryVelocity")), - angular_vel_temp_(*particles_->createAVariable("TemporaryAngularVelocity")) {} - //=================================================================================================// - void ClampConstrainShellBodyRegion::Initialization(size_t index_i, Real dt) - { - vel_n_[index_i] = Vecd(0); - angular_vel_[index_i] = Vecd(0); - } - //=================================================================================================// - void ClampConstrainShellBodyRegion::Interaction(size_t index_i, Real dt) - { - Real ttl_weight(Eps); - Vecd vel_i = vel_n_[index_i]; - Vecd angular_vel_i = angular_vel_[index_i]; - - Neighborhood& inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) - { - size_t index_j = inner_neighborhood.j_[n]; - Real weight_j = inner_neighborhood.W_ij_[n] * Vol_[index_j]; - - ttl_weight += weight_j; - vel_i += vel_n_[index_j] * weight_j; - angular_vel_i += angular_vel_[index_j] * weight_j; - } - - vel_n_temp_[index_i] = vel_i / ttl_weight; - angular_vel_temp_[index_i] = angular_vel_i / ttl_weight; - } - //=================================================================================================// - void ClampConstrainShellBodyRegion::Update(size_t index_i, Real dt) - { - vel_n_[index_i] = vel_n_temp_[index_i]; - angular_vel_[index_i] = angular_vel_temp_[index_i]; - } - //=================================================================================================// - ConstrainShellBodyRegionInAxisDirection:: - ConstrainShellBodyRegionInAxisDirection(SolidBody* body, BodyPartByParticle* body_part, int axis_direction) : - PartSimpleDynamicsByParticle(body, body_part), ShellDataSimple(body), - axis_(axis_direction), pos_n_(particles_->pos_n_), pos_0_(particles_->pos_0_), - vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), - vel_ave_(particles_->vel_ave_), dvel_dt_ave_(particles_->dvel_dt_ave_), - rotation_(particles_->rotation_), angular_vel_(particles_->angular_vel_), - dangular_vel_dt_(particles_->dangular_vel_dt_) {} - //=================================================================================================// - void ConstrainShellBodyRegionInAxisDirection::Update(size_t index_i, Real dt) - { - vel_n_[index_i][axis_] = 0.0; - vel_n_[index_i][2] = 0.0; - dvel_dt_[index_i][axis_] = 0.0; - dvel_dt_[index_i][2] = 0.0; - - angular_vel_[index_i][1 - axis_] = 0.0; - dangular_vel_dt_[index_i][1 - axis_] = 0.0; - - /** the average values are prescirbed also. */ - vel_ave_[index_i] = vel_n_[index_i]; - dvel_dt_ave_[index_i] = dvel_dt_[index_i]; - } - //=================================================================================================// - DistributingAPointForceToShell:: - DistributingAPointForceToShell(SolidBody* body, BodyPartByParticle* body_part, - Vecd point_force, Vecd reference_position, - Real time_to_smallest_h_ratio, Real time_to_full_external_force, - Real particle_spacing_ref, Real h_spacing_ratio) - : PartSimpleDynamicsByParticle(body, body_part), ShellDataSimple(body), - point_force_(point_force), reference_position_(reference_position), - time_to_smallest_h_ratio_(time_to_smallest_h_ratio), - time_to_full_external_force_(time_to_full_external_force), - particle_spacing_ref_(particle_spacing_ref), h_spacing_ratio_(h_spacing_ratio), - pos_0_(particles_->pos_0_), dvel_dt_prior_(particles_->dvel_dt_prior_), - Vol_(particles_->Vol_), mass_(particles_->mass_), shell_thickness_(particles_->shell_thickness_), - weight_(*particles_->createAVariable("Weight")) - { - } - //=================================================================================================// - void DistributingAPointForceToShell::getWeight() - { - sum_of_weight_ = 0.0; - Real current_time = GlobalStaticVariables::physical_time_; - Real h_spacing_ratio_time = current_time * (0.6 - h_spacing_ratio_) - / time_to_smallest_h_ratio_ + h_spacing_ratio_; - Real h_spacing_ratio = current_time < time_to_smallest_h_ratio_ ? h_spacing_ratio_time : 0.6; - - Real smooth_length = h_spacing_ratio * particle_spacing_ref_; - Real cutoff_radius_sqr = powerN(2.0 * smooth_length, 2); - for (size_t i = 0; i < particles_->total_real_particles_; ++i) - { - weight_[i] = 0.0; - Vecd displacement = reference_position_ - pos_0_[i]; - if (displacement.normSqr() <= cutoff_radius_sqr) - { - Kernel* kernel_ = body_->particle_adaptation_->getKernel(); - if (Dimensions == 2) - { - weight_[i] = 3.0 / 4.0 / smooth_length - * kernel_->W_2D(displacement.norm() / smooth_length) * Vol_[i]; - } - else - { - weight_[i] = 7.0 / (4.0 * Pi) / smooth_length / smooth_length - * kernel_->W_3D(displacement.norm() / smooth_length) * Vol_[i]; - } - sum_of_weight_ += weight_[i]; - } - } - } - //=================================================================================================// - void DistributingAPointForceToShell::getForce() - { - Real current_time = GlobalStaticVariables::physical_time_; - point_force_time_ = current_time < time_to_full_external_force_ ? - current_time * point_force_ / time_to_full_external_force_ : point_force_; - } - //=================================================================================================// - void DistributingAPointForceToShell::Update(size_t index_i, Real dt) - { - Vecd force = weight_[index_i] / (sum_of_weight_ + TinyReal) * point_force_time_; - dvel_dt_prior_[index_i] = force / mass_[index_i] / shell_thickness_[index_i]; - } - //=================================================================================================// - } -} diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.h deleted file mode 100644 index b3c28322a1..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.h +++ /dev/null @@ -1,302 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file thin_structure_dynamics.h -* @brief Here, we define the algorithm classes for thin structure dynamics. -* @details We consider here a weakly compressible solids. -* @author Dong Wu, Chi Zhang and Xiangyu Hu -*/ - -#ifndef THIN_STRUCTURE_DYNAMICS_H -#define THIN_STRUCTURE_DYNAMICS_H - - -#include "all_particle_dynamics.h" -#include "elastic_solid.h" -#include "weakly_compressible_fluid.h" -#include "base_kernel.h" -#include "all_fluid_dynamics.h" - -namespace SPH -{ - namespace thin_structure_dynamics - { - typedef DataDelegateSimple ShellDataSimple; - typedef DataDelegateInner ShellDataInner; - - /** - * @class ShellDynamicsInitialCondition - * @brief set initial condition for shell particles - * This is a abstract class to be override for case specific initial conditions. - */ - class ShellDynamicsInitialCondition : - public ParticleDynamicsSimple, public ShellDataSimple - { - public: - ShellDynamicsInitialCondition(SolidBody *body); - virtual ~ShellDynamicsInitialCondition() {}; - protected: - StdLargeVec& n_0_, &n_, &pseudo_n_, &pos_0_; - StdLargeVec& transformation_matrix_; - }; - - /** - * @class ShellAcousticTimeStepSize - * @brief Computing the acoustic time step size for shell - */ - class ShellAcousticTimeStepSize : - public ParticleDynamicsReduce, - public ShellDataSimple - { - public: - explicit ShellAcousticTimeStepSize(SolidBody* body); - virtual ~ShellAcousticTimeStepSize() {}; - protected: - StdLargeVec& vel_n_, &dvel_dt_, &angular_vel_, &dangular_vel_dt_; - StdLargeVec& shell_thickness_; - Real rho0_, physical_viscosity_, E0_, nu_; - Real smoothing_length_; - Real ReduceFunction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class ShellCorrectConfiguration - * @brief obtain the corrected initial configuration in strong form - */ - class ShellCorrectConfiguration : - public InteractionDynamics, public ShellDataInner - { - public: - ShellCorrectConfiguration(BaseBodyRelationInner* body_inner_relation); - virtual ~ShellCorrectConfiguration() {}; - protected: - StdLargeVec& Vol_; - StdLargeVec& B_; - StdLargeVec& n_0_; - StdLargeVec& transformation_matrix_; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class ShellDeformationGradientTensor - * @brief computing deformation gradient tensor for shell - */ - class ShellDeformationGradientTensor : - public InteractionDynamics, public ShellDataInner - { - public: - ShellDeformationGradientTensor(BaseBodyRelationInner* body_inner_relation); - virtual ~ShellDeformationGradientTensor() {}; - protected: - StdLargeVec& Vol_; - StdLargeVec& pos_n_, &pseudo_n_, &n_0_; - StdLargeVec& B_, &F_, &F_bending_; - StdLargeVec& transformation_matrix_; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class BaseShellRelaxation - * @brief abstract class for preparing shell relaxation - */ - class BaseShellRelaxation : public ParticleDynamics1Level, public ShellDataInner - { - public: - BaseShellRelaxation(BaseBodyRelationInner* body_inner_relation); - virtual ~BaseShellRelaxation() {}; - protected: - StdLargeVec& Vol_, & rho_n_, & mass_, & shell_thickness_; - StdLargeVec& pos_n_, & vel_n_, & dvel_dt_, & dvel_dt_prior_, & force_from_fluid_; - StdLargeVec& n_0_, & pseudo_n_, & dpseudo_n_dt_, & dpseudo_n_d2t_, & rotation_, - & angular_vel_, dangular_vel_dt_; - StdLargeVec& B_, & F_, & dF_dt_, & F_bending_, & dF_bending_dt_; - StdLargeVec& transformation_matrix_; - }; - - /** - * @class ShellStressRelaxationFirstHalf - * @brief computing stress relaxation process by verlet time stepping - * This is the first step - */ - class ShellStressRelaxationFirstHalf : public BaseShellRelaxation - { - public: - ShellStressRelaxationFirstHalf(BaseBodyRelationInner* body_inner_relation, - int number_of_gaussian_points = 3); - virtual ~ShellStressRelaxationFirstHalf() {}; - protected: - Real rho0_, inv_rho0_; - StdLargeVec& stress_PK1_, & global_stress_, & global_moment_; - StdLargeVec& global_shear_stress_, & n_; - Real smoothing_length_; - - const Real shear_correction_factor_ = 5.0/6.0; - const StdVec three_gaussian_points_ = { 0.0, 0.77459667, -0.77459667 }; - const StdVec three_gaussian_weights_ = { 8.0 / 9.0, 5.0 / 9.0, 5.0 / 9.0 }; - const StdVec five_gaussian_points_ = { 0.0, 0.538469, -0.538469, 0.90618, -0.90618 }; - const StdVec five_gaussian_weights_ = { 0.568889, 0.478629, 0.478629, 0.236927, 0.236927 }; - int number_of_gaussian_points_; - StdVec gaussian_point_; - StdVec gaussian_weight_; - - virtual void Initialization(size_t index_i, Real dt = 0.0) override; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class ShellStressRelaxationSecondHalf - * @brief computing stress relaxation process by verlet time stepping - * This is the second step - */ - class ShellStressRelaxationSecondHalf : public BaseShellRelaxation - { - public: - ShellStressRelaxationSecondHalf(BaseBodyRelationInner* body_inner_relation) - : BaseShellRelaxation(body_inner_relation) {}; - virtual ~ShellStressRelaxationSecondHalf() {}; - protected: - virtual void Initialization(size_t index_i, Real dt = 0.0) override; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /**@class ConstrainShellBodyRegion - * @brief Fix the position and angle of a shell body part. - * Note that the average values for FSI are prescirbed also. - */ - class ConstrainShellBodyRegion : - public PartSimpleDynamicsByParticle, public ShellDataSimple - { - public: - ConstrainShellBodyRegion(SolidBody* body, BodyPartByParticle* body_part); - virtual ~ConstrainShellBodyRegion() {}; - protected: - StdLargeVec& pos_n_, &pos_0_; - StdLargeVec& n_; - StdLargeVec& vel_n_, &dvel_dt_, &vel_ave_, &dvel_dt_ave_; - StdLargeVec& rotation_, &angular_vel_, &dangular_vel_dt_; - StdLargeVec& pseudo_n_, &dpseudo_n_dt_; - virtual Vecd getDisplacement(const Vecd& pos_0, const Vecd& pos_n) { return pos_0; }; - virtual Vecd getVelocity(const Vecd& pos_0, const Vecd& pos_n, const Vecd& vel_n) { return Vecd(0); }; - virtual Vecd GetAcceleration(const Vecd& pos_0, const Vecd& pos_n, const Vecd& dvel_dt) { return Vecd(0); }; - virtual Vecd GetRotationAngle(const Vecd& pos_0, const Vecd& pos_n, const Vecd& rotation_angles_0_) { return rotation_angles_0_; }; - virtual Vecd GetAngularVelocity(const Vecd& pos_0, const Vecd& pos_n, const Vecd& angular_vel_) { return Vecd(0); }; - virtual Vecd GetAngularAcceleration(const Vecd& pos_0, const Vecd& pos_n, const Vecd& dangular_vel_dt_) { return Vecd(0); }; - virtual Vecd GetPseudoNormal(const Vecd& pos_0, const Vecd& pos_n, const Vecd& n_0) { return n_0; }; - virtual Vecd GetPseudoNormalChangeRate(const Vecd& pos_0, const Vecd& pos_n, const Vecd& dpseudo_normal_dt_) { return Vecd(0); }; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class FixedFreeRotateShellBoundary - * @brief Soft the constraint of a solid body part - */ - class FixedFreeRotateShellBoundary : - public PartInteractionDynamicsByParticle1Level, - public ShellDataInner - { - public: - FixedFreeRotateShellBoundary(BaseBodyRelationInner* body_inner_relation, - BodyPartByParticle* body_part, Vecd constrained_direction = Vecd(0)); - virtual ~FixedFreeRotateShellBoundary() {}; - protected: - Real W0_; - Matd constrain_matrix_, recover_matrix_; - StdLargeVec& Vol_; - StdLargeVec& vel_n_, & angular_vel_, &vel_n_temp_, &angular_vel_temp_; - - virtual void Initialization(size_t index_i, Real dt = 0.0) override; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class ClampConstrainShellBodyRegion - * @brief The clamped constrain of a shell body part - */ - class ClampConstrainShellBodyRegion : - public PartInteractionDynamicsByParticle1Level, - public ShellDataInner - { - public: - ClampConstrainShellBodyRegion(BaseBodyRelationInner* body_inner_relation, BodyPartByParticle* body_part); - virtual ~ClampConstrainShellBodyRegion() {}; - protected: - StdLargeVec& Vol_; - StdLargeVec& vel_n_, &angular_vel_, & vel_n_temp_, & angular_vel_temp_; - - virtual void Initialization(size_t index_i, Real dt = 0.0) override; - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /**@class ConstrainShellBodyRegionInAxisDirection - * @brief The boundary conditions are denoted by SS1 according to the references. - * The axis_direction must be 0 or 1. - * Note that the average values for FSI are prescirbed also. - */ - class ConstrainShellBodyRegionInAxisDirection : - public PartSimpleDynamicsByParticle, public ShellDataSimple - { - public: - ConstrainShellBodyRegionInAxisDirection(SolidBody* body, BodyPartByParticle* body_part, int axis_direction); - virtual ~ConstrainShellBodyRegionInAxisDirection() {}; - protected: - const int axis_; /**< the axis direction for bounding*/ - StdLargeVec& pos_n_, &pos_0_; - StdLargeVec& vel_n_, &dvel_dt_, &vel_ave_, &dvel_dt_ave_; - StdLargeVec& rotation_, &angular_vel_, &dangular_vel_dt_; - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class DistributingAPointForceToShell - * @brief Distribute a point force to its contact shell bodies. - */ - class DistributingAPointForceToShell : - public PartSimpleDynamicsByParticle, public ShellDataSimple - { - protected: - Vecd point_force_, point_force_time_; - Vecd reference_position_; - Real time_to_smallest_h_ratio_, time_to_full_external_force_; - Real particle_spacing_ref_, h_spacing_ratio_; - Real sum_of_weight_; - StdLargeVec& pos_0_, &dvel_dt_prior_; - StdLargeVec& Vol_, &mass_, &shell_thickness_; - StdLargeVec& weight_; - public: - DistributingAPointForceToShell(SolidBody* body, BodyPartByParticle* body_part, - Vecd point_force, Vecd reference_position, - Real time_to_smallest_h_ratio, Real time_to_full_external_force, - Real particle_spacing_ref, Real h_spacing_ratio = 1.3); - virtual ~DistributingAPointForceToShell() {}; - - void getWeight(); - void getForce(); - virtual void Update(size_t index_i, Real dt = 0.0) override; - }; - } -} -#endif //THIN_STRUCTURE_DYNAMICS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.cpp b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.cpp deleted file mode 100644 index 4381f9037e..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/** - * @file thin_structure_math.cpp - * @author Dong Wu, Chi ZHang and Xiangyu Hu - * @version 0.1 - */ - -#include "thin_structure_math.h" - -using namespace SimTK; - -namespace SPH -{ - namespace thin_structure_dynamics - { - //=================================================================================================// - Vec2d getVectorAfterThinStructureRotation(const Vec2d &initial_vector, const Vec2d &rotation_angles) - { - /**The rotation matrix. */ - Mat2d rotation_matrix(0.0); - rotation_matrix[0][0] = cos(rotation_angles[0]); - rotation_matrix[0][1] = sin(rotation_angles[0]); - rotation_matrix[1][0] = -rotation_matrix[0][1]; - rotation_matrix[1][1] = rotation_matrix[0][0]; - - return rotation_matrix * initial_vector; - } - //=================================================================================================// - Vec3d getVectorAfterThinStructureRotation(const Vec3d &initial_vector, const Vec3d &rotation_angles) - { - /**The rotation matrix about the X-axis. */ - Mat3d rotation_matrix_x(0.0); - rotation_matrix_x[0][0] = 1.0; - rotation_matrix_x[1][1] = cos(rotation_angles[0]); - rotation_matrix_x[1][2] = -sin(rotation_angles[0]); - rotation_matrix_x[2][1] = -rotation_matrix_x[1][2]; - rotation_matrix_x[2][2] = rotation_matrix_x[1][1]; - /**The rotation matrix about the Y-axis. */ - Mat3d rotation_matrix_y(0.0); - rotation_matrix_y[0][0] = cos(rotation_angles[1]); - rotation_matrix_y[0][2] = sin(rotation_angles[1]); - rotation_matrix_y[1][1] = 1.0; - rotation_matrix_y[2][0] = -rotation_matrix_y[0][2]; - rotation_matrix_y[2][2] = rotation_matrix_y[0][0]; - - return rotation_matrix_y * rotation_matrix_x * initial_vector; - } - //=================================================================================================// - Vec2d getVectorChangeRateAfterThinStructureRotation(const Vec2d &initial_vector, const Vec2d &rotation_angles, const Vec2d &angular_vel) - { - /**The derivative of the rotation matrix. */ - Mat2d drotation_matrix_dt(0.0); - drotation_matrix_dt[0][0] = -sin(rotation_angles[0]) * angular_vel[0]; - drotation_matrix_dt[0][1] = cos(rotation_angles[0]) * angular_vel[0]; - drotation_matrix_dt[1][0] = -drotation_matrix_dt[0][1]; - drotation_matrix_dt[1][1] = drotation_matrix_dt[0][0]; - - return drotation_matrix_dt * initial_vector; - } - //=================================================================================================// - Vec3d getVectorChangeRateAfterThinStructureRotation(const Vec3d& initial_vector, const Vec3d& rotation_angles, const Vec3d& angular_vel) - { - /**The rotation matrix about the X-axis. */ - Mat3d rotation_matrix_x(0.0); - rotation_matrix_x[0][0] = 1.0; - rotation_matrix_x[1][1] = cos(rotation_angles[0]); - rotation_matrix_x[1][2] = -sin(rotation_angles[0]); - rotation_matrix_x[2][1] = -rotation_matrix_x[1][2]; - rotation_matrix_x[2][2] = rotation_matrix_x[1][1]; - /**The rotation matrix about the Y-axis. */ - Mat3d rotation_matrix_y(0.0); - rotation_matrix_y[0][0] = cos(rotation_angles[1]); - rotation_matrix_y[0][2] = sin(rotation_angles[1]); - rotation_matrix_y[1][1] = 1.0; - rotation_matrix_y[2][0] = -rotation_matrix_y[0][2]; - rotation_matrix_y[2][2] = rotation_matrix_y[0][0]; - - /**The derivative of the rotation matrix of the X-axis. */ - Mat3d drotation_matrix_x_dt(0.0); - drotation_matrix_x_dt[1][1] = -sin(rotation_angles[0]) * angular_vel[0]; - drotation_matrix_x_dt[1][2] = -cos(rotation_angles[0]) * angular_vel[0]; - drotation_matrix_x_dt[2][1] = -drotation_matrix_x_dt[1][2]; - drotation_matrix_x_dt[2][2] = drotation_matrix_x_dt[1][1]; - /**The derivative of the rotation matrix of the Y-axis. */ - Mat3d drotation_matrix_y_dt(0.0); - drotation_matrix_y_dt[0][0] = -sin(rotation_angles[1]) * angular_vel[1]; - drotation_matrix_y_dt[0][2] = cos(rotation_angles[1]) * angular_vel[1]; - drotation_matrix_y_dt[2][0] = -drotation_matrix_y_dt[0][2]; - drotation_matrix_y_dt[2][2] = drotation_matrix_y_dt[0][0]; - - return (drotation_matrix_y_dt * rotation_matrix_x + rotation_matrix_y * drotation_matrix_x_dt)* initial_vector; - } - //=================================================================================================// - Vec2d getRotationFromPseudoNormalForFiniteDeformation(const Vec2d& dpseudo_n_d2t, const Vec2d& rotation, const Vec2d& angular_vel, Real dt) - { - Vec2d dangular_vel_dt(0.0); - dangular_vel_dt[0] = -(dpseudo_n_d2t[0] + sin(rotation[0]) * powerN(angular_vel[0], 2)) - / (2 * sin(rotation[0]) * angular_vel[0] * dt - cos(rotation[0])); - return dangular_vel_dt; - } - //=================================================================================================// - Vec3d getRotationFromPseudoNormalForFiniteDeformation(const Vec3d& dpseudo_n_d2t, const Vec3d& rotation, const Vec3d& angular_vel, Real dt) - { - Vec3d dangular_vel_dt(0.0); - dangular_vel_dt[0] = (dpseudo_n_d2t[1] - sin(rotation[0]) * powerN(angular_vel[0], 2)) - / (2 * sin(rotation[0]) * angular_vel[0] * dt - cos(rotation[0])); - dangular_vel_dt[1] = (dpseudo_n_d2t[0] + cos(rotation[0]) * sin(rotation[1]) - * (powerN(angular_vel[0], 2) + powerN(angular_vel[1], 2)) - + 2 * sin(rotation[0]) * cos(rotation[1]) * angular_vel[0] * angular_vel[1] - + (2 * cos(rotation[0]) * sin(rotation[1]) * angular_vel[0] * dt - + 2 * sin(rotation[0]) * cos(rotation[1]) * angular_vel[1] * dt - + sin(rotation[0]) * cos(rotation[1])) * dangular_vel_dt[0]) - / (-2 * sin(rotation[0]) * cos(rotation[1]) * angular_vel[0] * dt - - 2 * cos(rotation[0]) * sin(rotation[1]) * angular_vel[1] * dt - + cos(rotation[0]) * cos(rotation[1])); - return dangular_vel_dt; - } - //=================================================================================================// - Vec2d getRotationFromPseudoNormalForSmallDeformation(const Vec2d& dpseudo_n_d2t, const Vec2d& rotation, const Vec2d& angular_vel, Real dt) - { - Vec2d dangular_vel_dt(0.0); - dangular_vel_dt[0] = dpseudo_n_d2t[0]; - return dangular_vel_dt; - } - //=================================================================================================// - Vec3d getRotationFromPseudoNormalForSmallDeformation(const Vec3d& dpseudo_n_d2t, const Vec3d& rotation, const Vec3d& angular_vel, Real dt) - { - Vec3d dangular_vel_dt(0.0); - dangular_vel_dt[0] = -dpseudo_n_d2t[1]; - dangular_vel_dt[1] = dpseudo_n_d2t[0]; - return dangular_vel_dt; - } - //=================================================================================================// - Vec2d getNormalFromDeformationGradientTensor(const Mat2d& F) - { - Vec2d n = Vec2d(-F.col(0)[1], F.col(0)[0]); - n = n / (n.norm() + Eps); - return n; - } - //=================================================================================================// - Vec3d getNormalFromDeformationGradientTensor(const Mat3d& F) - { - Vec3d n = F.col(0) % F.col(1); - n = n / (n.norm() + Eps); - return n; - } - //=================================================================================================// - } -} diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.h b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.h deleted file mode 100644 index 8af2c07ae6..0000000000 --- a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.h +++ /dev/null @@ -1,69 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file thin_structure_math.h -* @brief Here, we define the math operation for thin structure dynamics. -* @author Dong Wu and Xiangyu Hu -* @version 0.1 -*/ - -#ifndef THIN_STRUCTURE_MATH_H -#define THIN_STRUCTURE_MATH_H - - -#include "all_particle_dynamics.h" -#include "elastic_solid.h" -#include "weakly_compressible_fluid.h" -#include "base_kernel.h" - -namespace SPH -{ - namespace thin_structure_dynamics - { - /** - * @function getVectorAfterThinStructureRotation - * @brief Each of these basic vector rotations appears counterclockwise - * @brief when the axis about which they occur points toward the observer, - * @brief and the coordinate system is right-handed. - */ - Vec2d getVectorAfterThinStructureRotation(const Vec2d &initial_vector, const Vec2d &rotation_angles); - Vec3d getVectorAfterThinStructureRotation(const Vec3d &initial_vector, const Vec3d &rotation_angles); - - /** Vector change rate after rotation. */ - Vec2d getVectorChangeRateAfterThinStructureRotation(const Vec2d &initial_vector, const Vec2d &rotation_angles, const Vec2d &angular_vel); - Vec3d getVectorChangeRateAfterThinStructureRotation(const Vec3d &initial_vector, const Vec3d &rotation_angles, const Vec3d &angular_vel); - - /** get the rotation from pseudo-normal for finite deformation. */ - Vec2d getRotationFromPseudoNormalForFiniteDeformation(const Vec2d& dpseudo_n_d2t, const Vec2d& rotation, const Vec2d& angular_vel, Real dt); - Vec3d getRotationFromPseudoNormalForFiniteDeformation(const Vec3d& dpseudo_n_d2t, const Vec3d& rotation, const Vec3d& angular_vel, Real dt); - - /** get the rotation from pseudo-normal for small deformation. */ - Vec2d getRotationFromPseudoNormalForSmallDeformation(const Vec2d& dpseudo_n_d2t, const Vec2d& rotation, const Vec2d& angular_vel, Real dt); - Vec3d getRotationFromPseudoNormalForSmallDeformation(const Vec3d& dpseudo_n_d2t, const Vec3d& rotation, const Vec3d& angular_vel, Real dt); - - /** get the current normal direction from deformation gradient tensor. */ - Vec2d getNormalFromDeformationGradientTensor(const Mat2d& F); - Vec3d getNormalFromDeformationGradientTensor(const Mat3d& F); - } -} -#endif //THIN_STRUCTURE_MATH_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_generator/CMakeLists.txt b/SPHINXsys/src/shared/particle_generator/CMakeLists.txt deleted file mode 100644 index 6e136f3c33..0000000000 --- a/SPHINXsys/src/shared/particle_generator/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) diff --git a/SPHINXsys/src/shared/particle_generator/base_particle_generator.cpp b/SPHINXsys/src/shared/particle_generator/base_particle_generator.cpp deleted file mode 100644 index 017e4e76ad..0000000000 --- a/SPHINXsys/src/shared/particle_generator/base_particle_generator.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @file base_particle_generator.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#include "base_particle_generator.h" - -#include "base_body.h" -#include "base_particles.h" -#include "in_output.h" - - -namespace SPH { - //=================================================================================================// - void ParticleGenerator::initialize(SPHBody* sph_body) - { - sph_body_ = sph_body; - } - //=================================================================================================// - void ParticleGeneratorDirect - ::createBaseParticles(BaseParticles* base_particles) - { - size_t total_real_particles = 0; - auto& body_input_points_volumes = sph_body_->body_input_points_volumes_; - for (size_t i = 0; i < body_input_points_volumes.size(); ++i) - { - base_particles->initializeABaseParticle(body_input_points_volumes[i].first, - body_input_points_volumes[i].second); - total_real_particles++; - } - - base_particles->total_real_particles_ = total_real_particles; - } - //=================================================================================================// - ParticleGeneratorReload:: - ParticleGeneratorReload(In_Output* in_output, std::string reload_body_name) - : ParticleGenerator() - { - if (!fs::exists(in_output->reload_folder_)) - { - std::cout << "\n Error: the particle reload folder:" << in_output->reload_folder_ << " is not exists" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - - file_path_ = in_output->reload_folder_ + "/SPHBody_" + reload_body_name + "_rld.xml"; - } - //=================================================================================================// - void ParticleGeneratorReload::createBaseParticles(BaseParticles* base_particles) - { - size_t total_real_particles = 0; - XmlEngine* reload_xml_engine = base_particles->getReloadXmlEngine(); - reload_xml_engine->loadXmlFile(file_path_); - SimTK::Xml::element_iterator ele_ite_ = reload_xml_engine->root_element_.element_begin(); - for (; ele_ite_ != reload_xml_engine->root_element_.element_end(); ++ele_ite_) - { - Vecd position(0); - reload_xml_engine->getRequiredAttributeValue(ele_ite_, "Position", position); - Real volume(0); - reload_xml_engine->getRequiredAttributeValue(ele_ite_, "Volume", volume); - base_particles->initializeABaseParticle(position, volume); - total_real_particles++; - } - base_particles->total_real_particles_ = total_real_particles; - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/particle_generator/base_particle_generator.h b/SPHINXsys/src/shared/particle_generator/base_particle_generator.h deleted file mode 100644 index d60cd2a27a..0000000000 --- a/SPHINXsys/src/shared/particle_generator/base_particle_generator.h +++ /dev/null @@ -1,86 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file base_particle_generator.h - * @brief This is the base class of particle generator, which generates particles - * with given positions and volumes. The direct generator simply generate - * particle with given position and volume. - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ - -#ifndef BASE_PARTICLE_GENERATOR_H -#define BASE_PARTICLE_GENERATOR_H - - - -#include "base_data_package.h" -#include "sph_data_conainers.h" - -namespace SPH { - - class SPHBody; - class BaseParticles; - class In_Output; - - /** - * @class ParticleGenerator. - * @brief Base abstract class for particle generation. - */ - class ParticleGenerator - { - public: - ParticleGenerator() : sph_body_(nullptr) {}; - virtual ~ParticleGenerator() {}; - - virtual void initialize(SPHBody* sph_body); - virtual void createBaseParticles(BaseParticles* base_particles) = 0; - protected: - SPHBody* sph_body_; - }; - - /** - * @class ParticleGeneratorDirect - * @brief Generate particle directly from position-and-volume data. - */ - class ParticleGeneratorDirect : public ParticleGenerator - { - public: - ParticleGeneratorDirect() : ParticleGenerator() {}; - virtual ~ParticleGeneratorDirect() {}; - virtual void createBaseParticles(BaseParticles* base_particles) override; - }; - - /** - * @class ParticleGeneratorReload - * @brief Generate particle by reloading particle position and volume. - */ - class ParticleGeneratorReload : public ParticleGenerator - { - std::string file_path_; - public: - ParticleGeneratorReload(In_Output* in_output, std::string reload_body_name); - virtual ~ParticleGeneratorReload() {}; - virtual void createBaseParticles(BaseParticles* base_particles) override; - }; -} -#endif //BASE_PARTICLE_GENERATOR_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_generator/particle_generator_lattice.cpp b/SPHINXsys/src/shared/particle_generator/particle_generator_lattice.cpp deleted file mode 100644 index c571e962cd..0000000000 --- a/SPHINXsys/src/shared/particle_generator/particle_generator_lattice.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/** - * @file particle_generator_lattice.cpp - * @author Luhui Han, Chi ZHang and Xiangyu Hu - */ -#include "particle_generator_lattice.h" - -#include "geometry.h" -#include "base_body.h" -#include "base_particles.h" -#include "particle_adaptation.h" - -namespace SPH { - //=================================================================================================// - ParticleGeneratorLattice::ParticleGeneratorLattice() - : ParticleGenerator(), lattice_spacing_(0), - domain_bounds_(0, 0), body_shape_(nullptr) - { - } - //=================================================================================================// - void ParticleGeneratorLattice::initialize(SPHBody* sph_body) - { - sph_body_ = sph_body; - lattice_spacing_ = sph_body_->particle_adaptation_->ReferenceSpacing(); - domain_bounds_ = sph_body_->getSPHSystemBounds(); - body_shape_ = sph_body_->body_shape_; - } - //=================================================================================================// - void ParticleGeneratorLattice:: - createABaseParticle(BaseParticles* base_particles, - Vecd& particle_position, Real particle_volume, size_t& total_real_particles) - { - base_particles->initializeABaseParticle(particle_position, particle_volume); - total_real_particles++; - } - //=================================================================================================// - ParticleGeneratorMultiResolution::ParticleGeneratorMultiResolution() - : ParticleGeneratorLattice(), particle_adapation_(nullptr) {} - //=================================================================================================// - void ParticleGeneratorMultiResolution::initialize(SPHBody* sph_body) - { - sph_body_ = sph_body; - particle_adapation_ = - dynamic_cast(sph_body->particle_adaptation_); - lattice_spacing_ = particle_adapation_->MinimumSpacing(); - domain_bounds_ = sph_body_->getSPHSystemBounds(); - body_shape_ = sph_body_->body_shape_; - } - //=================================================================================================// - void ParticleGeneratorMultiResolution:: - createABaseParticle(BaseParticles* base_particles, - Vecd& particle_position, Real particle_volume, size_t& total_real_particles) - { - Real local_particle_spacing - = particle_adapation_->getLocalSpacing(*body_shape_, particle_position); - Real local_particle_volume_ratio = powerN(lattice_spacing_ / local_particle_spacing, Dimensions); - if ((double)rand() / (RAND_MAX) < local_particle_volume_ratio) - { - base_particles->initializeABaseParticle(particle_position, particle_volume / local_particle_volume_ratio); - total_real_particles++; - } - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/particle_generator/particle_generator_lattice.h b/SPHINXsys/src/shared/particle_generator/particle_generator_lattice.h deleted file mode 100644 index 7cc7502ae5..0000000000 --- a/SPHINXsys/src/shared/particle_generator/particle_generator_lattice.h +++ /dev/null @@ -1,82 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file particle_generator_lattice.h - * @brief This is the base class of particle generator, which generates particles - * with given positions and volumes. The direct generator simply generate - * particle with given position and volume. The lattice generator generate - * at lattice position by check whether the poision is contained by a SPH body. - * @author Xiangyu Hu, Chi Zhang, Yongchuan Yu - */ - -#ifndef PARTICLE_GENERATOR_LATTICE_H -#define PARTICLE_GENERATOR_LATTICE_H - - - -#include "base_particle_generator.h" - -namespace SPH { - - class ComplexShape; - class ParticleSpacingByBodyShape; - - /** - * @class ParticleGeneratorLattice - * @brief generate particles from lattice positions for a body. - */ - class ParticleGeneratorLattice : public ParticleGenerator - { - public: - ParticleGeneratorLattice(); - virtual ~ParticleGeneratorLattice() {}; - - virtual void initialize(SPHBody* sph_body) override; - virtual void createBaseParticles(BaseParticles* base_particles) override; - protected: - Real lattice_spacing_; - BoundingBox domain_bounds_; - ComplexShape* body_shape_; - - virtual void createABaseParticle(BaseParticles* base_particles, - Vecd& particle_position, Real particle_volume, size_t& total_real_particles); - }; - - /** - * @class ParticleGeneratorMultiResolution - * @brief generate multi-resolution particles from lattice positions for a body. - */ - class ParticleGeneratorMultiResolution : public ParticleGeneratorLattice - { - public: - ParticleGeneratorMultiResolution(); - virtual ~ParticleGeneratorMultiResolution() {}; - virtual void initialize(SPHBody* sph_body) override; - protected: - ParticleSpacingByBodyShape* particle_adapation_; - - virtual void createABaseParticle(BaseParticles* base_particles, - Vecd& particle_position, Real particle_volume, size_t& total_real_particles) override; - }; -} -#endif //PARTICLE_GENERATOR_LATTICE_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/CMakeLists.txt b/SPHINXsys/src/shared/particles/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/particles/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/all_particles.h b/SPHINXsys/src/shared/particles/all_particles.h deleted file mode 100644 index a4b5a5fe58..0000000000 --- a/SPHINXsys/src/shared/particles/all_particles.h +++ /dev/null @@ -1,11 +0,0 @@ - -#ifndef ALL_PARTICLES_H -#define ALL_PARTICLES_H - - - -#include "fluid_particles.h" -#include "solid_particles.h" -#include "diffusion_reaction_particles.h" - -#endif //ALL_PARTICLES_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/base_particles.cpp b/SPHINXsys/src/shared/particles/base_particles.cpp deleted file mode 100644 index f2b8c91fa0..0000000000 --- a/SPHINXsys/src/shared/particles/base_particles.cpp +++ /dev/null @@ -1,329 +0,0 @@ -/** - * @file base_particles.cpp - * @brief Definition of functions declared in base_particles.h - * @author Xiangyu Hu and Chi Zhang - */ -#include "base_particles.h" - -#include "base_body.h" -#include "base_material.h" -#include "base_particle_generator.h" -#include "xml_engine.h" - -namespace SPH -{ - //=================================================================================================// - BaseParticles::BaseParticles(SPHBody* body, BaseMaterial* base_material) : - base_material_(base_material), speed_max_(0.0), signal_speed_max_(0.0), - total_real_particles_(0), real_particles_bound_(0), total_ghost_particles_(0), - body_(body), body_name_(body->getBodyName()), - restart_xml_engine_("xml_restart", "particles"), - reload_xml_engine_("xml_particle_reload", "particles") - { - body->assignBaseParticles(this); - rho0_ = base_material->ReferenceDensity(); - sigma0_ = body->particle_adaptation_->ReferenceNumberDensity(); - //---------------------------------------------------------------------- - // register particle data - //---------------------------------------------------------------------- - registerAVariable(pos_n_, "Position"); - registerAVariable(vel_n_, "Velocity"); - registerAVariable(dvel_dt_, "Acceleration"); - registerAVariable(dvel_dt_prior_, "OtherAcceleration"); - registerAVariable(Vol_, "Volume"); - registerAVariable(rho_n_, "Density"); - registerAVariable(mass_, "Mass"); - //---------------------------------------------------------------------- - // add basic output particle data - //---------------------------------------------------------------------- - addAVariableToWrite("Velocity"); - addAVariableToWrite("Density"); - //---------------------------------------------------------------------- - // add restart output particle data - //---------------------------------------------------------------------- - addAVariableNameToList(variables_to_restart_, "Position"); - addAVariableNameToList(variables_to_restart_, "Velocity"); - addAVariableNameToList(variables_to_restart_, "Volume"); - addAVariableNameToList(variables_to_restart_, "Density"); - - ParticleGenerator* particle_generator = body_->particle_generator_; - particle_generator->initialize(body_); - particle_generator->createBaseParticles(this); - real_particles_bound_ = total_real_particles_; - - body->particle_adaptation_->assignBaseParticles(this); - base_material->assignBaseParticles(this); - } - //=================================================================================================// - BaseParticles::BaseParticles(SPHBody* body) - : BaseParticles(body, new BaseMaterial()) {} - //=================================================================================================// - void BaseParticles::initializeABaseParticle(Vecd pnt, Real Vol_0) - { - sequence_.push_back(0); - sorted_id_.push_back(pos_n_.size()); - unsorted_id_.push_back(pos_n_.size()); - - pos_n_.push_back(pnt); - vel_n_.push_back(Vecd(0)); - dvel_dt_.push_back(Vecd(0)); - dvel_dt_prior_.push_back(Vecd(0)); - - Vol_.push_back(Vol_0); - rho_n_.push_back(rho0_); - mass_.push_back(rho0_ * Vol_0); - } - //=================================================================================================// - void BaseParticles::addABufferParticle() - { - sequence_.push_back(0); - sorted_id_.push_back(pos_n_.size()); - unsorted_id_.push_back(pos_n_.size()); - - loopParticleData(all_particle_data_); - } - //=================================================================================================// - void BaseParticles::copyFromAnotherParticle(size_t this_index, size_t another_index) - { - updateFromAnotherParticle(this_index, another_index); - } - //=================================================================================================// - void BaseParticles::updateFromAnotherParticle(size_t this_index, size_t another_index) - { - loopParticleData(all_particle_data_, this_index, another_index); - } - //=================================================================================================// - size_t BaseParticles::insertAGhostParticle(size_t index_i) - { - total_ghost_particles_ += 1; - size_t expected_size = real_particles_bound_ + total_ghost_particles_; - size_t expected_particle_index = expected_size - 1; - if (expected_size <= pos_n_.size()) { - copyFromAnotherParticle(expected_particle_index, index_i); - /** For a ghost particle, its sorted id is that of corresponding real particle. */ - sorted_id_[expected_particle_index] = index_i; - - } - else { - addABufferParticle(); - copyFromAnotherParticle(expected_particle_index, index_i); - /** For a ghost particle, its sorted id is that of corresponding real particle. */ - sorted_id_[expected_particle_index] = index_i; - } - return expected_particle_index; - } - //=================================================================================================// - void BaseParticles::switchToBufferParticle(size_t index_i) - { - size_t last_real_particle_index = total_real_particles_ - 1; - updateFromAnotherParticle(index_i, last_real_particle_index); - unsorted_id_[index_i] = unsorted_id_[last_real_particle_index]; - total_real_particles_ -= 1; - } - //=================================================================================================// - void BaseParticles::writeParticlesToVtuFile(std::ofstream& output_file) - { - size_t total_real_particles = total_real_particles_; - - //write particle positions first - output_file << " \n"; - output_file << " \n"; - output_file << " "; - for (size_t i = 0; i != total_real_particles; ++i) { - Vec3d particle_position = upgradeToVector3D(pos_n_[i]); - output_file << particle_position[0] << " " << particle_position[1] << " " << particle_position[2] << " "; - } - output_file << std::endl; - output_file << " \n"; - output_file << " \n"; - - //write header of particles data - output_file << " \n"; - - //write sorted particles ID - output_file << " \n"; - output_file << " "; - for (size_t i = 0; i != total_real_particles; ++i) { - output_file << i << " "; - } - output_file << std::endl; - output_file << " \n"; - - //write unsorted particles ID - output_file << " \n"; - output_file << " "; - for (size_t i = 0; i != total_real_particles; ++i) { - output_file << unsorted_id_[i] << " "; - } - output_file << std::endl; - output_file << " \n"; - - //write matrices - for (std::pair& name_index : variables_to_write_[indexMatrix]) - { - std::string variable_name = name_index.first; - StdLargeVec& variable = *(std::get(all_particle_data_)[name_index.second]); - output_file << " \n"; - output_file << " "; - for (size_t i = 0; i != total_real_particles; ++i) { - Mat3d matrix_value = upgradeToMatrix3D(variable[i]); - for (int k = 0; k != 3; ++k) { - Vec3d col_vector = matrix_value.col(k); - output_file << std::fixed << std::setprecision(9) << col_vector[0] << " " << col_vector[1] << " " << col_vector[2] << " "; - } - } - output_file << std::endl; - output_file << " \n"; - } - - //write vectors - for (std::pair& name_index : variables_to_write_[indexVector]) - { - std::string variable_name = name_index.first; - StdLargeVec& variable = *(std::get(all_particle_data_)[name_index.second]); - output_file << " \n"; - output_file << " "; - for (size_t i = 0; i != total_real_particles; ++i) { - Vec3d vector_value = upgradeToVector3D(variable[i]); - output_file << std::fixed << std::setprecision(9) << vector_value[0] << " " << vector_value[1] << " " << vector_value[2] << " "; - } - output_file << std::endl; - output_file << " \n"; - } - - //write scalars - for (std::pair& name_index : variables_to_write_[indexScalar]) - { - std::string variable_name = name_index.first; - StdLargeVec& variable = *(std::get(all_particle_data_)[name_index.second]); - output_file << " \n"; - output_file << " "; - for (size_t i = 0; i != total_real_particles; ++i) { - output_file << std::fixed << std::setprecision(9) << variable[i] << " "; - } - output_file << std::endl; - output_file << " \n"; - } - - //write integers - for (std::pair& name_index : variables_to_write_[indexInteger]) - { - std::string variable_name = name_index.first; - StdLargeVec& variable = *(std::get(all_particle_data_)[name_index.second]); - output_file << " \n"; - output_file << " "; - for (size_t i = 0; i != total_real_particles; ++i) { - output_file << std::fixed << std::setprecision(9) << variable[i] << " "; - } - output_file << std::endl; - output_file << " \n"; - } - } - //=================================================================================================// - void BaseParticles::writePltFileHeader(std::ofstream& output_file) - { - output_file << " VARIABLES = \"x\",\"y\",\"z\",\"ID\""; - for (size_t l = 0; l != variables_to_write_[indexVector].size(); ++l) { - std::string variable_name = variables_to_write_[indexVector][l].first; - output_file << ",\"" << variable_name << "_x\"" << ",\"" << variable_name << "_y\"" << ",\"" << variable_name << "_z\""; - }; - for (size_t l = 0; l != variables_to_write_[indexScalar].size(); ++l) { - std::string variable_name = variables_to_write_[indexScalar][l].first; - output_file << ",\"" << variable_name << "\""; - }; - } - //=================================================================================================// - void BaseParticles::writePltFileParticleData(std::ofstream& output_file, size_t index_i) - { - //write particle positions and index first - Vec3d particle_position = upgradeToVector3D(pos_n_[index_i]); - output_file << particle_position[0] << " " << particle_position[1] << " " << particle_position[2] << " " - << index_i << " "; - - for (std::pair& name_index : variables_to_write_[indexVector]) - { - std::string variable_name = name_index.first; - StdLargeVec& variable = *(std::get(all_particle_data_)[name_index.second]); - Vec3d vector_value = upgradeToVector3D(variable[index_i]); - output_file << vector_value[0] << " " << vector_value[1] << " " << vector_value[2] << " "; - }; - - for (std::pair& name_index : variables_to_write_[indexScalar]) - { - std::string variable_name = name_index.first; - StdLargeVec& variable = *(std::get(all_particle_data_)[name_index.second]); - output_file << variable[index_i] << " "; - }; - } - //=================================================================================================// - void BaseParticles::writeParticlesToPltFile(std::ofstream& output_file) - { - writePltFileHeader(output_file); - output_file << "\n"; - - size_t total_real_particles = total_real_particles_; - for (size_t i = 0; i != total_real_particles; ++i) { - writePltFileParticleData(output_file, i); - output_file << "\n"; - }; - } - //=================================================================================================// - void BaseParticles::resizeXmlDocForParticles(XmlEngine& xml_engine) - { - size_t total_elements = xml_engine.SizeOfXmlDoc(); - - if (total_elements <= total_real_particles_) - { - for (size_t i = total_elements; i != total_real_particles_; ++i) - xml_engine.addElementToXmlDoc("particle"); - } - } - //=================================================================================================// - void BaseParticles::writeParticlesToXmlForRestart(std::string& filefullpath) - { - resizeXmlDocForParticles(restart_xml_engine_); - WriteAParticleVariableToXml write_variable_to_xml(restart_xml_engine_, total_real_particles_); - loopParticleData(all_particle_data_, variables_to_restart_, write_variable_to_xml); - restart_xml_engine_.writeToXmlFile(filefullpath); - } - //=================================================================================================// - void BaseParticles::readParticleFromXmlForRestart(std::string& filefullpath) - { - restart_xml_engine_.loadXmlFile(filefullpath); - ReadAParticleVariableFromXml read_variable_from_xml(restart_xml_engine_, total_real_particles_); - loopParticleData(all_particle_data_, variables_to_restart_, read_variable_from_xml); - } - //=================================================================================================// - void BaseParticles::writeToXmlForReloadParticle(std::string& filefullpath) - { - resizeXmlDocForParticles(reload_xml_engine_); - SimTK::Xml::element_iterator ele_ite = reload_xml_engine_.root_element_.element_begin(); - for (size_t i = 0; i != total_real_particles_; ++i) - { - reload_xml_engine_.setAttributeToElement(ele_ite, "Position", pos_n_[i]); - reload_xml_engine_.setAttributeToElement(ele_ite, "Volume", Vol_[i]); - ele_ite++; - } - reload_xml_engine_.writeToXmlFile(filefullpath); - } - //=================================================================================================// - void BaseParticles::readFromXmlForReloadParticle(std::string& filefullpath) - { - reload_xml_engine_.loadXmlFile(filefullpath); - SimTK::Xml::element_iterator ele_ite = reload_xml_engine_.root_element_.element_begin(); - for (size_t i = 0; i != total_real_particles_; ++i) - { - reload_xml_engine_.getRequiredAttributeValue(ele_ite, "Position", pos_n_[i]); - reload_xml_engine_.getRequiredAttributeValue(ele_ite, "Volume", Vol_[i]); - ele_ite++; - } - - if (reload_xml_engine_.SizeOfXmlDoc() != total_real_particles_) - { - std::cout << "\n Error: reload particle number does not match!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/particles/base_particles.h b/SPHINXsys/src/shared/particles/base_particles.h deleted file mode 100644 index 75ee9ea9ee..0000000000 --- a/SPHINXsys/src/shared/particles/base_particles.h +++ /dev/null @@ -1,350 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file base_particles.h - * @brief This is the base class of SPH particles. The basic data of the particles - * is saved in separated large vectors. Each derived class will introduce several extra - * vectors for the new data. Note that there is no class of single particle. - * @author Xiangyu Hu and Chi Zhang - */ - -#ifndef BASE_PARTICLES_H -#define BASE_PARTICLES_H - - - -#include "base_data_package.h" -#include "sph_data_conainers.h" -#include "xml_engine.h" - -#include - -namespace SPH -{ - - class SPHBody; - class BaseMaterial; - - /** - * @class BaseParticles - * @brief Particles with essential (geometric and kinematic) data. - * There are three types of particles, all particles of a same type are saved with continuous memory segments. - * The first type is real particles whose states are updated by particle dynamics. - * One is buffer particles whose state are not updated by particle dynamics. - * Buffer particles are saved behind real particles. - * The global value of total_real_particles_ separate the real and buffer particles. - * They may be switched from real particles or switch to real particles. - * As the memory for both particles are continuous, such switch is achieved at the memory boundary sequentially. - * The basic idea is swap the data of the last real particle with the one will be switched particle, - * and then switch this swapped last particle as buffer particle by decrease the total_real_particles_ by one. - * Switch from buffer particle to real particle is easy. One just need to assign expect state to - * the first buffer particle and increase total_real_particles_ by one. - * The other is ghost particles whose states are updated according to - * boundary condition if their indices are included in the neighbor particle list. - * The ghost particles are saved behind the buffer particles. - * The global value of real_particles_bound_ separate the sum of real and buffer particles with ghost particles. - * The global value of total_ghost_particles_ indicates the total number of ghost particles in use. - * It will be initialized to zero before a time step. - */ - class BaseParticles - { - public: - BaseParticles(SPHBody* body, BaseMaterial* base_material); - BaseParticles(SPHBody* body); - virtual ~BaseParticles() {}; - - BaseMaterial* base_material_; /**< for dynamic cast in particle data delegation */ - - StdLargeVec pos_n_; /**< current position */ - StdLargeVec vel_n_; /**< current particle velocity */ - StdLargeVec dvel_dt_; /**< inner pressure- or stress-induced acceleration */ - StdLargeVec dvel_dt_prior_; /**< other, such as gravity and viscous, accelerations */ - - StdLargeVec Vol_; /**< particle volume */ - StdLargeVec rho_n_; /**< current particle density */ - StdLargeVec mass_; /**< particle mass */ - //---------------------------------------------------------------------- - //Global information for all particles - //---------------------------------------------------------------------- - Real rho0_; /**< reference density*/ - Real sigma0_; /**< reference number density. */ - Real speed_max_; /**< Maximum particle speed. */ - Real signal_speed_max_; /**< Maximum signal speed.*/ - //---------------------------------------------------------------------- - //Global information for defining particle groups - //---------------------------------------------------------------------- - size_t total_real_particles_; - size_t real_particles_bound_; /**< Maximum possible number of real particles. Also the start index of ghost particles. */ - size_t total_ghost_particles_; - //---------------------------------------------------------------------- - // Generalized particle data for parameterized management - //---------------------------------------------------------------------- - ParticleData all_particle_data_; - ParticleDataMap all_variable_maps_; - - /** register a variable defined in a class (can be non-particle class) to base particles */ - template - void registerAVariable(StdLargeVec& variable_addrs, - std::string variable_name, VariableType initial_value = VariableType(0)) - { - if (all_variable_maps_[DataTypeIndex].find(variable_name) == all_variable_maps_[DataTypeIndex].end()) - { - variable_addrs.resize(real_particles_bound_, initial_value); - std::get(all_particle_data_).push_back(&variable_addrs); - all_variable_maps_[DataTypeIndex].insert(make_pair(variable_name, std::get(all_particle_data_).size() - 1)); - } - else - { - std::cout << "\n Error: the variable '" << variable_name << "' has already been registered!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - }; - - /** Create and register a new variable which has not been defined yet in particles. - * If the variable is registered already, the registered variable will be returned. */ - template - StdLargeVec* createAVariable(std::string new_variable_name, VariableType initial_value = VariableType(0)) - { - if (all_variable_maps_[DataTypeIndex].find(new_variable_name) == all_variable_maps_[DataTypeIndex].end()) { - StdLargeVec* new_variable = new StdLargeVec; - registerAVariable(*new_variable, new_variable_name, initial_value); - return new_variable; - } - std::cout << "\n Warning: the variable '" << new_variable_name << "' has already registered!"; - std::cout << "\n So, the previously registered variable is assigned!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - return getVariableByName(new_variable_name); - }; - - /** create and register a new variable, which has not been defined yet in particles, - * by copying data from an exist variable */ - template - StdLargeVec* createAVariable(std::string new_variable_name, std::string old_variable_name) - { - if (all_variable_maps_[DataTypeIndex].find(old_variable_name) != all_variable_maps_[DataTypeIndex].end()) - { - if (all_variable_maps_[DataTypeIndex].find(new_variable_name) == all_variable_maps_[DataTypeIndex].end()) - { - StdLargeVec* new_variable = new StdLargeVec; - registerAVariable(*new_variable, new_variable_name); - StdLargeVec* old_variable = - std::get(all_particle_data_)[all_variable_maps_[DataTypeIndex][old_variable_name]]; - for (size_t i = 0; i != real_particles_bound_; ++i) (*new_variable)[i] = (*old_variable)[i]; - return new_variable; - } - std::cout << "\n Error: the new variable '" << new_variable_name << "' has already been registered!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - std::cout << "\n Error: the old variable '" << old_variable_name << "' is not registered!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - return nullptr; - }; - - /** get a registered variable from particles by its name */ - template - StdLargeVec* getVariableByName(std::string variable_name) - { - if (all_variable_maps_[DataTypeIndex].find(variable_name) != all_variable_maps_[DataTypeIndex].end()) - return std::get(all_particle_data_)[all_variable_maps_[DataTypeIndex][variable_name]]; - - std::cout << "\n Error: the variable '" << variable_name << "' is not registered!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - return nullptr; - }; - - /** add a variable into a particle vairable name list */ - template - void addAVariableNameToList(ParticleVariableList& variable_name_list, std::string variable_name) - { - if (all_variable_maps_[DataTypeIndex].find(variable_name) != all_variable_maps_[DataTypeIndex].end()) - { - bool is_to_add = true; - for (size_t i = 0; i != variable_name_list[DataTypeIndex].size(); ++i) { - if (variable_name_list[DataTypeIndex][i].first == variable_name) is_to_add = false; - } - if (is_to_add) { - size_t variable_index = all_variable_maps_[DataTypeIndex][variable_name]; - variable_name_list[DataTypeIndex].push_back(make_pair(variable_name, variable_index)); - } - } - else - { - std::cout << "\n Error: the variable '" << variable_name << "' you are going to write is not particle data!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - }; - - /** add a variable into the list for state output */ - template - void addAVariableToWrite(std::string variable_name) - { - addAVariableNameToList(variables_to_write_, variable_name); - }; - - /** add a variable into the list for restart */ - template - void addAVariableToRestart(std::string variable_name) - { - addAVariableNameToList(variables_to_restart_, variable_name); - }; - - //---------------------------------------------------------------------- - // Particle data for sorting - //---------------------------------------------------------------------- - StdLargeVec sequence_; - StdLargeVec sorted_id_; - StdLargeVec unsorted_id_; - ParticleData sortable_data_; - ParticleDataMap sortable_variable_maps_; - - /** register an already defined variable as sortable */ - template - void registerASortableVariable(std::string variable_name) - { - if (sortable_variable_maps_[DataTypeIndex].find(variable_name) == sortable_variable_maps_[DataTypeIndex].end()) - { - if (all_variable_maps_[DataTypeIndex].find(variable_name) != all_variable_maps_[DataTypeIndex].end()) - { - StdLargeVec* variable = - std::get(all_particle_data_)[all_variable_maps_[DataTypeIndex][variable_name]]; - std::get(sortable_data_).push_back(variable); - sortable_variable_maps_[DataTypeIndex].insert(make_pair(variable_name, std::get(sortable_data_).size() - 1)); - } - else - { - std::cout << "\n Error: the variable '" << variable_name << "' is not registered!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - } - else - { - std::cout << "\n Warning: the variable '" << variable_name << "' has already registered as a sortabele variable!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - } - }; - - SPHBody* getSPHBody() { return body_; }; - void initializeABaseParticle(Vecd pnt, Real Vol_0); - void addABufferParticle(); - void copyFromAnotherParticle(size_t this_index, size_t another_index); - void updateFromAnotherParticle(size_t this_index, size_t another_index); - size_t insertAGhostParticle(size_t index_i); - void switchToBufferParticle(size_t index_i); - - /** Write particle data in VTU format for Paraview. */ - virtual void writeParticlesToVtuFile(std::ofstream& output_file); - /** Write particle data in PLT format for Tecplot. */ - void writeParticlesToPltFile(std::ofstream& output_file); - - void resizeXmlDocForParticles(XmlEngine& xml_engine); - void writeParticlesToXmlForRestart(std::string& filefullpath); - void readParticleFromXmlForRestart(std::string& filefullpath); - XmlEngine* getReloadXmlEngine() { return &reload_xml_engine_; }; - void writeToXmlForReloadParticle(std::string& filefullpath); - void readFromXmlForReloadParticle(std::string& filefullpath); - - virtual BaseParticles* ThisObjectPtr() { return this; }; - - /** Normalize the kernel gradient. */ - virtual Vecd normalizeKernelGradient(size_t particle_index_i, Vecd& kernel_gradient) { return kernel_gradient; }; - /** Get the kernel gradient in weak form. */ - virtual Vecd getKernelGradient(size_t particle_index_i, size_t particle_index_j, - Real dW_ij, Vecd& e_ij) { - return dW_ij * e_ij; - }; - protected: - SPHBody* body_; /**< The body in which the particles belongs to. */ - std::string body_name_; - XmlEngine restart_xml_engine_; - XmlEngine reload_xml_engine_; - ParticleVariableList variables_to_write_; - ParticleVariableList variables_to_restart_; - - virtual void writePltFileHeader(std::ofstream& output_file); - virtual void writePltFileParticleData(std::ofstream& output_file, size_t index_i); - - template - struct addAParticleDataValue - { - void operator () (ParticleData& particle_data) const - { - for (size_t i = 0; i != std::get(particle_data).size(); ++i) - std::get(particle_data)[i]->push_back(VariableType(0)); - }; - }; - - template - struct copyAParticleDataValue - { - void operator () (ParticleData& particle_data, size_t this_index, size_t another_index) const - { - for (size_t i = 0; i != std::get(particle_data).size(); ++i) - (*std::get(particle_data)[i])[this_index] = - (*std::get(particle_data)[i])[another_index]; - }; - }; - }; - - struct WriteAParticleVariableToXml - { - XmlEngine& xml_engine_; - size_t& total_real_particles_; - WriteAParticleVariableToXml(XmlEngine& xml_engine, size_t& total_real_particles) : - xml_engine_(xml_engine), total_real_particles_(total_real_particles) {}; - template - void operator () (std::string& variable_name, StdLargeVec& variable) const - { - SimTK::Xml::element_iterator ele_ite = xml_engine_.root_element_.element_begin(); - for (size_t i = 0; i != total_real_particles_; ++i) - { - xml_engine_.setAttributeToElement(ele_ite, variable_name, variable[i]); - ele_ite++; - } - } - }; - - struct ReadAParticleVariableFromXml - { - XmlEngine& xml_engine_; - size_t& total_real_particles_; - ReadAParticleVariableFromXml(XmlEngine& xml_engine, size_t& total_real_particles) : - xml_engine_(xml_engine), total_real_particles_(total_real_particles) {}; - template - void operator () (std::string& variable_name, StdLargeVec& variable) const - { - SimTK::Xml::element_iterator ele_ite = xml_engine_.root_element_.element_begin(); - for (size_t i = 0; i != total_real_particles_; ++i) - { - xml_engine_.getRequiredAttributeValue(ele_ite, variable_name, variable[i]); - ele_ite++; - } - } - }; -} -#endif //BASE_PARTICLES_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/diffusion_reaction_particles.cpp b/SPHINXsys/src/shared/particles/diffusion_reaction_particles.cpp deleted file mode 100644 index 41b6c824d6..0000000000 --- a/SPHINXsys/src/shared/particles/diffusion_reaction_particles.cpp +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @file diffusion_reaction_particles.cpp - * @author Chi Zhang and Xiangyu Hu - */ -#include "diffusion_reaction_particles.h" - - //=================================================================================================// -namespace SPH -{ - //=================================================================================================// - ElectroPhysiologyParticles - ::ElectroPhysiologyParticles(SPHBody* body, - DiffusionReactionMaterial* diffusion_reaction_material) - : DiffusionReactionParticles(body, diffusion_reaction_material) - { - } - //=================================================================================================// - ElectroPhysiologyReducedParticles::ElectroPhysiologyReducedParticles(SPHBody* body, - DiffusionReactionMaterial* diffusion_reaction_material) - : DiffusionReactionParticles(body, diffusion_reaction_material) - { - } - //=================================================================================================// -} \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/diffusion_reaction_particles.h b/SPHINXsys/src/shared/particles/diffusion_reaction_particles.h deleted file mode 100644 index 211511264a..0000000000 --- a/SPHINXsys/src/shared/particles/diffusion_reaction_particles.h +++ /dev/null @@ -1,133 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file diffusion_reaction_particles.h -* @brief This is the derived class of diffusion reaction particles. -* @author Xiangyu Huand Chi Zhang -*/ - - -#ifndef DIFFUSION_REACTION_PARTICLES_H -#define DIFFUSION_REACTION_PARTICLES_H - - - -#include "base_particles.h" -#include "base_body.h" -#include "base_material.h" -#include "diffusion_reaction.h" -#include "xml_engine.h" -#include "solid_particles.h" - -namespace SPH { - - /** - * @class DiffusionReactionParticles - * @brief A group of particles with diffusion or/and reactions particle data. - */ - template - class DiffusionReactionParticles : public BaseParticlesType - { - protected: - size_t number_of_species_; /**< Total number of diffusion and reaction species . */ - size_t number_of_diffusion_species_; /**< Total number of diffusion species . */ - std::map species_indexes_map_; - public: - StdVec> species_n_; /**< array of diffusion/reaction scalars */ - StdVec> diffusion_dt_;/**< array of the time derivative of diffusion species */ - - DiffusionReactionParticles(SPHBody* body, - DiffusionReactionMaterial* diffusion_reaction_material) - : BaseParticlesType(body, diffusion_reaction_material) - { - diffusion_reaction_material->assignDiffusionReactionParticles(this); - species_indexes_map_ = diffusion_reaction_material->SpeciesIndexMap(); - number_of_species_ = diffusion_reaction_material->NumberOfSpecies(); - species_n_.resize(number_of_species_); - - std::map::iterator itr; - for (itr = species_indexes_map_.begin(); itr != species_indexes_map_.end(); ++itr) - { - //Register a species. Note that we call a template function from a template class - this->template registerAVariable(species_n_[itr->second], itr->first); - //the scalars will be sorted if particle sorting is called - this->template registerASortableVariable(itr->first); - // add species to basic output particle data - this->template addAVariableToWrite(itr->first); - } - - number_of_diffusion_species_ = diffusion_reaction_material->NumberOfSpeciesDiffusion(); - diffusion_dt_.resize(number_of_diffusion_species_); - for (size_t m = 0; m < number_of_diffusion_species_; ++m) - { - //---------------------------------------------------------------------- - // register reactive change rate terms without giving variable name - //---------------------------------------------------------------------- - std::get(this->all_particle_data_).push_back(&diffusion_dt_[m]); - diffusion_dt_[m].resize(this->real_particles_bound_, Real(0)); - } - }; - virtual ~DiffusionReactionParticles() {}; - - std::map SpeciesIndexMap() { return species_indexes_map_; }; - - virtual DiffusionReactionParticles* - ThisObjectPtr() override { return this; }; - }; - - /** - * @class ElectroPhysiologyParticles - * @brief A group of particles with electrophysiology particle data. - */ - class ElectroPhysiologyParticles - : public DiffusionReactionParticles - { - public: - ElectroPhysiologyParticles(SPHBody* body, - DiffusionReactionMaterial* diffusion_reaction_material); - virtual ~ElectroPhysiologyParticles() {}; - virtual ElectroPhysiologyParticles* ThisObjectPtr() override { return this; }; - - }; - /** - * @class ElectroPhysiologyReducedParticles - * @brief A group of reduced particles with electrophysiology particle data. - */ - class ElectroPhysiologyReducedParticles - : public DiffusionReactionParticles - { - public: - /** Constructor. */ - ElectroPhysiologyReducedParticles(SPHBody* body, - DiffusionReactionMaterial* diffusion_reaction_material); - /** Destructor. */ - virtual ~ElectroPhysiologyReducedParticles() {}; - virtual ElectroPhysiologyReducedParticles* ThisObjectPtr() override { return this; }; - - virtual Vecd getKernelGradient(size_t particle_index_i, size_t particle_index_j, Real dW_ij, Vecd& e_ij) override - { - return dW_ij * e_ij; - }; - }; -} -#endif //DIFFUSION_REACTION_PARTICLES_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/fluid_particles.cpp b/SPHINXsys/src/shared/particles/fluid_particles.cpp deleted file mode 100644 index cfabeaefb2..0000000000 --- a/SPHINXsys/src/shared/particles/fluid_particles.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/** - * @file fluid_particles.cpp - * @author Xiangyu Hu and Chi Zhang - */ - -#include "fluid_particles.h" - -#include "base_body.h" -#include "weakly_compressible_fluid.h" -#include "compressible_fluid.h" -#include "xml_engine.h" - -namespace SPH -{ - //=================================================================================================// - FluidParticles::FluidParticles(SPHBody *body, Fluid* fluid) - : BaseParticles(body, fluid) - { - fluid->assignFluidParticles(this); - //---------------------------------------------------------------------- - // register particle data - //---------------------------------------------------------------------- - registerAVariable(p_, "Pressure"); - registerAVariable(drho_dt_, "DensityChangeRate"); - registerAVariable(rho_sum_, "DensitySummation"); - registerAVariable(surface_indicator_, "SurfaceIndicator"); - //---------------------------------------------------------------------- - // register sortable particle data - //---------------------------------------------------------------------- - registerASortableVariable("Position"); - registerASortableVariable("Velocity"); - registerASortableVariable("Mass"); - registerASortableVariable("Density"); - registerASortableVariable("Pressure"); - } - //=================================================================================================// - ViscoelasticFluidParticles - ::ViscoelasticFluidParticles(SPHBody *body, Oldroyd_B_Fluid* oldroyd_b_fluid) - : FluidParticles(body, oldroyd_b_fluid) - { - oldroyd_b_fluid->assignViscoelasticFluidParticles(this); - //---------------------------------------------------------------------- - // register particle data - //---------------------------------------------------------------------- - registerAVariable(tau_, "ElasticStress"); - registerAVariable(dtau_dt_, "ElasticStressChangeRate"); - //---------------------------------------------------------------------- - // register sortable particle data - //---------------------------------------------------------------------- - registerASortableVariable("ElasticStress"); - //---------------------------------------------------------------------- - // add restart output particle data - //---------------------------------------------------------------------- - addAVariableNameToList(variables_to_restart_, "ElasticStress"); - } - //=================================================================================================// - CompressibleFluidParticles - ::CompressibleFluidParticles(SPHBody *body, CompressibleFluid* compressiblefluid) - : FluidParticles(body, compressiblefluid) - { - compressiblefluid->assignCompressibleFluidParticles(this); - //---------------------------------------------------------------------- - // register particle data - //---------------------------------------------------------------------- - registerAVariable(mom_, "Momentum"); - registerAVariable(dmom_dt_, "MomentumChangeRate"); - registerAVariable(dmom_dt_prior_, "OtherMomentumChangeRate"); - registerAVariable(E_, "TotalEnergy"); - registerAVariable(dE_dt_, "TotalEnergyChangeRate"); - registerAVariable(dE_dt_prior_, "OtherEnergyChangeRate"); - //---------------------------------------------------------------------- - // add output particle data - //---------------------------------------------------------------------- - addAVariableToWrite("Pressure"); - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/particles/fluid_particles.h b/SPHINXsys/src/shared/particles/fluid_particles.h deleted file mode 100644 index deb37648c0..0000000000 --- a/SPHINXsys/src/shared/particles/fluid_particles.h +++ /dev/null @@ -1,96 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file fluid_particles.h - * @brief This is the derived class of base particle. - * @author Xiangyu Hu and Chi Zhang - */ - -#ifndef FLUID_PARTICLES_H -#define FLUID_PARTICLES_H - - - -#include "base_particles.h" - -namespace SPH { - - class Fluid; - class Oldroyd_B_Fluid; - class CompressibleFluid; - - /** - * @class FluidParticles - * @brief newtonian fluid particles. - */ - class FluidParticles : public BaseParticles - { - public: - explicit FluidParticles(SPHBody *body, Fluid *fluid); - virtual ~FluidParticles() {}; - - StdLargeVec p_; /**< pressure */ - StdLargeVec drho_dt_; /**< density change rate */ - StdLargeVec rho_sum_; /**< number density */ - StdLargeVec surface_indicator_; /**< free surface indicator */ - - virtual FluidParticles* ThisObjectPtr() override {return this;}; - }; - - /** - * @class ViscoelasticFluidParticles - * @brief Viscoelastic fluid particles. - */ - class ViscoelasticFluidParticles : public FluidParticles - { - public: - explicit ViscoelasticFluidParticles(SPHBody *body, Oldroyd_B_Fluid* oldroyd_b_fluid); - virtual ~ViscoelasticFluidParticles() {}; - - StdLargeVec tau_; /**< elastic stress */ - StdLargeVec dtau_dt_; /**< change rate of elastic stress */ - - virtual ViscoelasticFluidParticles* ThisObjectPtr() override {return this;}; - }; - - /** - * @class CompressibleFluidParticles - * @brief Compressible fluid particles. - */ - class CompressibleFluidParticles : public FluidParticles - { - public: - explicit CompressibleFluidParticles(SPHBody *body, CompressibleFluid* compressiblefluid); - virtual ~CompressibleFluidParticles() {}; - - StdLargeVec mom_; /**< momentum */ - StdLargeVec dmom_dt_; /**< change rate of momentum */ - StdLargeVec dmom_dt_prior_; - StdLargeVec E_; /**< total energy per unit volume */ - StdLargeVec dE_dt_; /**< change rate of total energy */ - StdLargeVec dE_dt_prior_; - - virtual CompressibleFluidParticles* ThisObjectPtr() override { return this; }; - }; -} -#endif //FLUID_PARTICLES_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/neighbor_relation.cpp b/SPHINXsys/src/shared/particles/neighbor_relation.cpp deleted file mode 100644 index 3c62e17c24..0000000000 --- a/SPHINXsys/src/shared/particles/neighbor_relation.cpp +++ /dev/null @@ -1,175 +0,0 @@ -/** - * @file neighboring_particles.cpp - * @author Xiangyu Hu and Chi Zhang - */ - -#include "neighbor_relation.h" - -#include "base_body.h" -#include "base_particles.h" - - -namespace SPH -{ - //=================================================================================================// - void Neighborhood::removeANeighbor(size_t neighbor_n) - { - current_size_ --; - j_[neighbor_n] = j_[current_size_]; - W_ij_[neighbor_n] = W_ij_[current_size_]; - dW_ij_[neighbor_n] = dW_ij_[current_size_]; - r_ij_[neighbor_n] = r_ij_[current_size_]; - e_ij_[neighbor_n] = e_ij_[current_size_]; - } - //=================================================================================================// - void NeighborRelation::createRelation(Neighborhood& neighborhood, - Real& distance, Vecd& displacement, size_t j_index) const - { - neighborhood.j_.push_back(j_index); - neighborhood.W_ij_.push_back(kernel_->W(distance, displacement)); - neighborhood.dW_ij_.push_back(kernel_->dW(distance, displacement)); - neighborhood.r_ij_.push_back(distance); - neighborhood.e_ij_.push_back(displacement / (distance + TinyReal)); - neighborhood.allocated_size_++; - } - //=================================================================================================// - void NeighborRelation::initializeRelation(Neighborhood& neighborhood, - Real& distance, Vecd& displacement, size_t j_index) const - { - size_t current_size = neighborhood.current_size_; - neighborhood.j_[current_size] = j_index; - neighborhood.W_ij_[current_size] = kernel_->W(distance, displacement); - neighborhood.dW_ij_[current_size] = kernel_->dW(distance, displacement); - neighborhood.r_ij_[current_size] = distance; - neighborhood.e_ij_[current_size] = displacement / (distance + TinyReal); - } - //=================================================================================================// - void NeighborRelation::createRelation(Neighborhood& neighborhood, Real& distance, - Vecd& displacement, size_t j_index, Real i_h_ratio, Real h_ratio_min) const - { - neighborhood.j_.push_back(j_index); - Real weight = distance < kernel_->CutOffRadius(i_h_ratio) ? - kernel_->W(i_h_ratio, distance, displacement) : 0.0; - neighborhood.W_ij_.push_back(weight); - neighborhood.dW_ij_.push_back(kernel_->dW(h_ratio_min, distance, displacement)); - neighborhood.r_ij_.push_back(distance); - neighborhood.e_ij_.push_back(displacement / (distance + TinyReal)); - neighborhood.allocated_size_++; - } - //=================================================================================================// - void NeighborRelation::initializeRelation(Neighborhood& neighborhood,Real& distance, - Vecd& displacement, size_t j_index, Real i_h_ratio, Real h_ratio_min) const - { - size_t current_size = neighborhood.current_size_; - neighborhood.j_[current_size] = j_index; - neighborhood.W_ij_[current_size] = distance < kernel_->CutOffRadius(i_h_ratio) ? - kernel_->W(i_h_ratio, distance, displacement) : 0.0; - neighborhood.dW_ij_[current_size] = kernel_->dW(h_ratio_min, distance, displacement); - neighborhood.r_ij_[current_size] = distance; - neighborhood.e_ij_[current_size] = displacement / (distance + TinyReal); - } - //=================================================================================================// - NeighborRelationInner::NeighborRelationInner(SPHBody* body) : NeighborRelation() - { - kernel_ = body->particle_adaptation_->getKernel(); - } - //=================================================================================================// - void NeighborRelationInner::operator () (Neighborhood& neighborhood, - Vecd& displacement, size_t i_index, size_t j_index) const - { - Real distance = displacement.norm(); - if (distance < kernel_->CutOffRadius() && i_index != j_index) - { - neighborhood.current_size_ >= neighborhood.allocated_size_ ? - createRelation(neighborhood, distance, displacement, j_index) - : initializeRelation(neighborhood, distance, displacement, j_index); - neighborhood.current_size_++; - } - }; - //=================================================================================================// - NeighborRelationInnerVariableSmoothingLength:: - NeighborRelationInnerVariableSmoothingLength(SPHBody* body) : NeighborRelation(), - h_ratio_(*body->base_particles_->getVariableByName("SmoothingLengthRatio")) - { - kernel_ = body->particle_adaptation_->getKernel(); - } - //=================================================================================================// - void NeighborRelationInnerVariableSmoothingLength::operator () (Neighborhood& neighborhood, - Vecd& displacement, size_t i_index, size_t j_index) const - { - Real i_h_ratio = h_ratio_[i_index]; - Real h_ratio_min = SMIN(i_h_ratio, h_ratio_[j_index]); - Real cutoff_radius = kernel_->CutOffRadius(h_ratio_min); - Real distance = displacement.norm(); - if (distance < cutoff_radius && i_index != j_index) - { - neighborhood.current_size_ >= neighborhood.allocated_size_ ? - createRelation(neighborhood, distance, displacement, j_index, i_h_ratio, h_ratio_min) - : initializeRelation(neighborhood, distance, displacement, j_index, i_h_ratio, h_ratio_min); - neighborhood.current_size_++; - } - }; - //=================================================================================================// - NeighborRelationContact:: - NeighborRelationContact(SPHBody* body, SPHBody* contact_body) : NeighborRelation() - { - Kernel* source_kernel = body->particle_adaptation_->getKernel(); - Kernel* target_kernel = contact_body->particle_adaptation_->getKernel(); - kernel_ = source_kernel->SmoothingLength() > target_kernel->SmoothingLength() ? source_kernel : target_kernel; - } - //=================================================================================================// - void NeighborRelationContact::operator () (Neighborhood& neighborhood, - Vecd& displacement, size_t i_index, size_t j_index) const - { - Real distance = displacement.norm(); - if (distance < kernel_->CutOffRadius()) - { - neighborhood.current_size_ >= neighborhood.allocated_size_ ? - createRelation(neighborhood, distance, displacement, j_index) - : initializeRelation(neighborhood, distance, displacement, j_index); - neighborhood.current_size_++; - } - }; - //=================================================================================================// - NeighborRelationSolidContact::NeighborRelationSolidContact(SPHBody* body, SPHBody* contact_body) - : NeighborRelationContact(body, contact_body) - { - Real source_smoothing_length = body->particle_adaptation_->ReferenceSmoothingLength(); - Real target_smoothing_length = contact_body->particle_adaptation_->ReferenceSmoothingLength(); - kernel_ = new KernelWendlandC2(); - kernel_->initialize(0.5 * (source_smoothing_length + target_smoothing_length)); - } - //=================================================================================================// - NeighborRelationContactBodyPart:: - NeighborRelationContactBodyPart(SPHBody* body, BodyPart* contact_body_part) - : NeighborRelation(), - part_indicator_(*contact_body_part->getBody()->base_particles_->createAVariable("BodyPartByParticleIndicator")) - { - Kernel* source_kernel = body->particle_adaptation_->getKernel(); - Kernel* target_kernel = contact_body_part->getBody()->particle_adaptation_->getKernel(); - kernel_ = source_kernel->SmoothingLength() > target_kernel->SmoothingLength() ? source_kernel : target_kernel; - - BodyPartByParticle* contact_body_part_by_particle = dynamic_cast(contact_body_part); - IndexVector part_particles = contact_body_part_by_particle->body_part_particles_; - - for (size_t i = 0; i != part_particles.size(); ++i) - { - part_indicator_[part_particles[i]] = 1; - } - } - //=================================================================================================// - void NeighborRelationContactBodyPart::operator () (Neighborhood& neighborhood, - Vecd& displacement, size_t i_index, size_t j_index) const - { - Real distance = displacement.norm(); - if (distance < kernel_->CutOffRadius() && part_indicator_[j_index] == 1) - { - neighborhood.current_size_ >= neighborhood.allocated_size_ ? - createRelation(neighborhood, distance, displacement, j_index) - : initializeRelation(neighborhood, distance, displacement, j_index); - neighborhood.current_size_++; - } - } - //=================================================================================================// -} -//=================================================================================================// diff --git a/SPHINXsys/src/shared/particles/neighbor_relation.h b/SPHINXsys/src/shared/particles/neighbor_relation.h deleted file mode 100644 index cbd6c989d6..0000000000 --- a/SPHINXsys/src/shared/particles/neighbor_relation.h +++ /dev/null @@ -1,162 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file neighbor_relation.h - * @brief There are the classes for neighboring particles. - * It saves the information for carring out pair - * interaction, and also considered as the topology of the particles. - * @author Xiangyu Hu and Chi Zhang - */ - -#ifndef NEIGHBOR_RELATION_H -#define NEIGHBOR_RELATION_H - - -#include "base_data_package.h" -#include "all_kernels.h" - -namespace SPH { - - class SPHBody; - class BodyPart; - - /** - * @class Neighborhood - * @brief A neighborhood around particle i. - */ - class Neighborhood - { - public: - size_t current_size_; /**< the current number of neighors */ - size_t allocated_size_; /**< the limit of neighors does not require memory allocation */ - - StdLargeVec j_; /**< index of the neighbor particle. */ - StdLargeVec W_ij_; /**< kernel value or particle volume contribution */ - StdLargeVec dW_ij_; /**< derivative of kernel function or inter-particle surface contribution */ - StdLargeVec r_ij_; /**< distance between j and i. */ - StdLargeVec e_ij_; /**< unit vector pointing from j to i or inter-particle surface direction */ - - Neighborhood() : current_size_(0), allocated_size_(0) {}; - ~Neighborhood() {}; - - void removeANeighbor(size_t neighbor_n); - }; - - /** Inner neighborhoods for all particles in a body for a inner body relation. */ - using ParticleConfiguration = StdLargeVec; - /** All contact neighborhoods for all particles in a body for a contact body relation. */ - using ContatcParticleConfiguration = StdVec; - - /** - * @class NeighborRelation - * @brief Base neighbor relation between particles i and j. - */ - class NeighborRelation - { - protected: - Kernel* kernel_; - //---------------------------------------------------------------------- - // Below are for constant smoothing length. - //---------------------------------------------------------------------- - void createRelation(Neighborhood& neighborhood, Real& distance, - Vecd& displacement, size_t j_index) const; - void initializeRelation(Neighborhood& neighborhood, Real& distance, - Vecd& displacement, size_t j_index) const; - //---------------------------------------------------------------------- - // Below are for variable smoothing length. - //---------------------------------------------------------------------- - void createRelation(Neighborhood& neighborhood, Real& distance, - Vecd& displacement, size_t j_index, Real i_h_ratio, Real h_ratio_min) const; - void initializeRelation(Neighborhood& neighborhood, Real& distance, - Vecd& displacement, size_t j_index, Real i_h_ratio, Real h_ratio_min) const; - public: - NeighborRelation() : kernel_(nullptr) {}; - virtual ~NeighborRelation() {}; - }; - - /** - * @class NeighborRelationInner - * @brief A inner neighbor relation functor between particles i and j. - */ - class NeighborRelationInner : public NeighborRelation - { - public: - NeighborRelationInner(SPHBody* body); - void operator () (Neighborhood& neighborhood, - Vecd& displacement, size_t i_index, size_t j_index) const; - }; - - /** - * @class NeighborRelationInnerVariableSmoothingLength - * @brief A inner neighbor relation functor between particles i and j. - */ - class NeighborRelationInnerVariableSmoothingLength : public NeighborRelation - { - public: - NeighborRelationInnerVariableSmoothingLength(SPHBody* body); - void operator () (Neighborhood& neighborhood, - Vecd& displacement, size_t i_index, size_t j_index) const; - protected: - StdLargeVec& h_ratio_; - }; - - /** - * @class NeighborRelationContact - * @brief A contact neighbor relation functor between particles i and j. - */ - class NeighborRelationContact : public NeighborRelation - { - public: - NeighborRelationContact(SPHBody* body, SPHBody* contact_body); - virtual ~NeighborRelationContact() {}; - void operator () (Neighborhood& neighborhood, - Vecd& displacement, size_t i_index, size_t j_index) const; - }; - - /** - * @class NeighborRelationSolidContact - * @brief A solid contact neighbor relation functor between particles i and j. - */ - class NeighborRelationSolidContact : public NeighborRelationContact - { - public: - NeighborRelationSolidContact(SPHBody* body, SPHBody* contact_body); - virtual ~NeighborRelationSolidContact() {}; - }; - - /** - * @class NeighborRelationContactBodyPart - * @brief A contact neighbor relation functor between particles i and j. - */ - class NeighborRelationContactBodyPart : public NeighborRelation - { - public: - NeighborRelationContactBodyPart(SPHBody* body, BodyPart* contact_body_part); - virtual ~NeighborRelationContactBodyPart() {}; - void operator () (Neighborhood& neighborhood, - Vecd& displacement, size_t i_index, size_t j_index) const; - protected: - StdLargeVec& part_indicator_; /**< indicator of the body part */ - }; -} -#endif //NEIGHBOR_RELATION_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/particle_adaptation.cpp b/SPHINXsys/src/shared/particles/particle_adaptation.cpp deleted file mode 100644 index cda0744ce7..0000000000 --- a/SPHINXsys/src/shared/particles/particle_adaptation.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/** - * @file particle_adaptation.cpp - * @brief Definition of functions declared in particle_adaptation.h - * @author Xiangyu Hu and Chi Zhang - */ - -#include "particle_adaptation.h" - -#include "sph_system.h" -#include "all_kernels.h" -#include "base_body.h" -#include "base_particles.h" -#include "mesh_cell_linked_list.h" -#include "level_set.h" - -namespace SPH -{ - //=================================================================================================// - ParticleAdaptation:: - ParticleAdaptation(Real h_spacing_ratio, int global_refinement_level) : - h_spacing_ratio_(h_spacing_ratio), - global_refinement_level_(global_refinement_level), - system_resolution_ratio_(1.0), - local_refinement_level_(0), - local_coarse_level_(local_refinement_level_ / 2), - spacing_ref_(0), Vol_ref_(0), h_ref_(0), - spacing_min_(0), spacing_ratio_min_(1.0), - spacing_ratio_max_(1.0), h_ratio_min_(1.0), h_ratio_max_(1.0), - number_density_min_(1.0), number_density_max_(1.0), - kernel_(new KernelWendlandC2()), - sph_body_(nullptr), system_domain_bounds_(), - base_particles_(nullptr) {}; - //=================================================================================================// - void ParticleAdaptation::initialize(SPHBody* sph_body) - { - sph_body_ = sph_body; - system_domain_bounds_ = sph_body_->getSPHSystem().system_domain_bounds_; - Real body_resololution = system_resolution_ratio_ * sph_body_->getSPHSystem().resolution_ref_; - spacing_ref_ = RefinedSpacing(body_resololution, global_refinement_level_); - Vol_ref_ = powerN(spacing_ref_, Dimensions); - h_ref_ = h_spacing_ratio_ * spacing_ref_; - kernel_->initialize(h_ref_); - spacing_min_ = RefinedSpacing(spacing_ref_, local_refinement_level_); - spacing_ratio_min_ = powerN(0.5, local_refinement_level_); - spacing_ratio_max_ = powerN(2.0, local_coarse_level_); - h_ratio_min_ = powerN(0.5, local_coarse_level_); - h_ratio_max_ = powerN(2.0, local_refinement_level_); - number_density_min_ = computeReferenceNumberDensity(Vecd(0), h_ratio_max_); - number_density_max_ = computeReferenceNumberDensity(Vecd(0), h_ratio_min_); - } - //=================================================================================================// - void ParticleAdaptation::replaceKernel(Kernel* another_kernel) - { - delete kernel_; - kernel_ = another_kernel; - } - //=================================================================================================// - Real ParticleAdaptation:: - RefinedSpacing(Real coarse_particle_spacing, int refinement_level) - { - return coarse_particle_spacing / powerN(2.0, refinement_level); - } - //=================================================================================================// - Real ParticleAdaptation::computeReferenceNumberDensity(Vec2d zero, Real h_ratio) - { - Real sigma(0); - Real cutoff_radius = kernel_->CutOffRadius(h_ratio); - Real particle_spacing = ReferenceSpacing() / h_ratio; - int search_range = int(cutoff_radius / particle_spacing) + 1; - for (int j = -search_range; j <= search_range; ++j) - for (int i = -search_range; i <= search_range; ++i) - { - Vec2d particle_location(Real(i) * particle_spacing, Real(j) * particle_spacing); - Real distance = particle_location.norm(); - if (distance < cutoff_radius) sigma += kernel_->W(h_ratio, distance, particle_location); - } - return sigma; - } - //=================================================================================================// - Real ParticleAdaptation::computeReferenceNumberDensity(Vec3d zero, Real h_ratio) - { - Real sigma(0); - Real cutoff_radius = kernel_->CutOffRadius(h_ratio); - Real particle_spacing = ReferenceSpacing() / h_ratio; - int search_range = int(cutoff_radius / particle_spacing) + 1; - for (int k = -search_range; k <= search_range; ++k) - for (int j = -search_range; j <= search_range; ++j) - for (int i = -search_range; i <= search_range; ++i) - { - Vec3d particle_location(Real(i) * particle_spacing, - Real(j) * particle_spacing, Real(k) * particle_spacing); - Real distance = particle_location.norm(); - if (distance < cutoff_radius) sigma += kernel_->W(h_ratio, distance, particle_location); - } - return sigma; - } - //=================================================================================================// - Real ParticleAdaptation::ReferenceNumberDensity() - { - return computeReferenceNumberDensity(Vecd(0), 1.0); - } - //=================================================================================================// - Real ParticleAdaptation::probeNumberDensity(Vecd zero, Real h_ratio) - { - Real alpha = (h_ratio_max_ - h_ratio) / (h_ratio_max_ - h_ratio_min_ + TinyReal); - - return alpha * number_density_max_ + (1.0 - alpha) * number_density_min_; - } - //=================================================================================================// - void ParticleAdaptation::assignBaseParticles(BaseParticles* base_particles) - { - base_particles_ = base_particles; - } - //=================================================================================================// - BaseMeshCellLinkedList* ParticleAdaptation::createMeshCellLinkedList() - { - return new MeshCellLinkedList(system_domain_bounds_, kernel_->CutOffRadius(), *sph_body_, *this); - } - //=================================================================================================// - BaseLevelSet* ParticleAdaptation::createLevelSet(ComplexShape& complex_shape) - { - return new LevelSet(complex_shape.findBounds(), ReferenceSpacing(), complex_shape, *this); - } - //=================================================================================================// - ParticleWithLocalRefinement::ParticleWithLocalRefinement(Real h_spacing_ratio, - int global_refinement_level, int local_refinement_level) : - ParticleAdaptation(h_spacing_ratio, global_refinement_level) - { - local_refinement_level_ = local_refinement_level; - local_coarse_level_ = local_refinement_level_ / 2; - } - //=================================================================================================// - size_t ParticleWithLocalRefinement::getMeshCellLinkedListTotalLevel() - { - return size_t(local_coarse_level_ + local_refinement_level_); - } - //=================================================================================================// - size_t ParticleWithLocalRefinement::getLevelSetTotalLevel() - { - return getMeshCellLinkedListTotalLevel() + 1; - } - //=================================================================================================// - void ParticleWithLocalRefinement::assignBaseParticles(BaseParticles* base_particles) - { - ParticleAdaptation::assignBaseParticles(base_particles); - base_particles->registerAVariable(h_ratio_, "SmoothingLengthRatio", 1.0); - } - //=================================================================================================// - BaseMeshCellLinkedList* ParticleWithLocalRefinement::createMeshCellLinkedList() - { - return new MultilevelMeshCellLinkedList(system_domain_bounds_, kernel_->CutOffRadius(), - getMeshCellLinkedListTotalLevel(), MaximumSpacingRatio(), *sph_body_, *this); - } - //=================================================================================================// - BaseLevelSet* ParticleWithLocalRefinement::createLevelSet(ComplexShape& complex_shape) - { - return new MultilevelLevelSet(complex_shape.findBounds(), - ReferenceSpacing(), getLevelSetTotalLevel(), MaximumSpacingRatio(), complex_shape, *this); - } - //=================================================================================================// - ParticleSpacingByBodyShape:: - ParticleSpacingByBodyShape(Real smoothing_length_ratio, - int global_refinement_level, int local_refinement_level) : - ParticleWithLocalRefinement(smoothing_length_ratio, - global_refinement_level, local_refinement_level) {}; - //=================================================================================================// - Real ParticleSpacingByBodyShape::getLocalSpacing(ComplexShape& complex_shape, Vecd& position) - { - Real phi = abs(complex_shape.findSignedDistance(position)); - Real ratio_ref = phi / (2.0 * spacing_ref_ * spacing_ratio_max_); - Real target_ratio = spacing_ratio_max_; - if (ratio_ref < kernel_->KernelSize()) { - Real weight = kernel_->W_1D(ratio_ref); - target_ratio = weight * spacing_ratio_min_ + (1.0 - weight) * spacing_ratio_max_; - } - return target_ratio * spacing_ref_; - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/particles/particle_adaptation.h b/SPHINXsys/src/shared/particles/particle_adaptation.h deleted file mode 100644 index f86b6aa93f..0000000000 --- a/SPHINXsys/src/shared/particles/particle_adaptation.h +++ /dev/null @@ -1,147 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file particle_adaptation.h - * @brief Particle adaptation is for adaptivity of SPH particles. - * The particle adaptation is defined before SPH body. - * @author Xiangyu Hu and Chi Zhang - */ - - -#ifndef PARTICLE_ADAPTATION_H -#define PARTICLE_ADAPTATION_H - - - -#include "base_data_package.h" -#include "sph_data_conainers.h" - - -namespace SPH { - - class SPHBody; - class ComplexShape; - class Kernel; - class BaseMeshCellLinkedList; - class BaseLevelSet; - class BaseParticles; - - /** - * @class ParticleAdaptation - * @brief Base class for particle adaptation - * The base class defined essential global parameteres. - * It is also used for single resolution SPH method. - * The derived class will be used if further adaptation is introduced. - */ - class ParticleAdaptation - { - protected: - Real h_spacing_ratio_; - int global_refinement_level_; - Real system_resolution_ratio_; // ratio of body resolution to system resolution, set to 1.0 by default - int local_refinement_level_; - int local_coarse_level_; - Real spacing_ref_; - Real Vol_ref_; - Real h_ref_; - Real spacing_min_; - Real spacing_ratio_min_; - Real spacing_ratio_max_; - Real h_ratio_min_; - Real h_ratio_max_; - Real number_density_min_; - Real number_density_max_; - - - Kernel* kernel_; - SPHBody* sph_body_; - BoundingBox system_domain_bounds_; - BaseParticles* base_particles_; - public: - ParticleAdaptation(Real h_spacing_ratio = 1.3, int global_refinement_level = 0); - virtual ~ParticleAdaptation() {}; - /** Note: called after construction all derived classes. */ - virtual void initialize(SPHBody* sph_body); - - int GlobalRefinementLevel() { return global_refinement_level_; }; - int LocalRefinementLevel() { return local_refinement_level_; }; - Real ReferenceSpacing() { return spacing_ref_; }; - Real ReferenceVolume() { return Vol_ref_; }; - Real ReferenceSmoothingLength() { return h_ref_; }; - Kernel* getKernel() { return kernel_; }; - /**replace a kernel should be done before kernel initialization */ - void replaceKernel(Kernel* another_kernel); - Real MinimumSpacing() { return spacing_min_; }; - Real MinimumSpacingRatio() { return spacing_ratio_min_; }; - Real MaximumSpacingRatio() { return spacing_ratio_max_; }; - Real computeReferenceNumberDensity(Vec2d zero, Real h_ratio); - Real computeReferenceNumberDensity(Vec3d zero, Real h_ratio); - Real ReferenceNumberDensity(); - Real probeNumberDensity(Vecd zero, Real h_ratio); - virtual Real SmoothingLengthRatio(size_t particle_index_i) {return 1.0; }; - - virtual void assignBaseParticles(BaseParticles* base_particles); - virtual BaseMeshCellLinkedList* createMeshCellLinkedList(); - virtual BaseLevelSet* createLevelSet(ComplexShape& complex_shape); - - void SetSystemResolutionRatio(Real system_resolution_ratio) { system_resolution_ratio_ = system_resolution_ratio; }; - protected: - Real RefinedSpacing(Real coarse_particle_spacing, int refinement_level); - }; - - /** - * @class ParticleWithLocalRefinement - * @brief Base class for particle with refinement. - */ - class ParticleWithLocalRefinement : public ParticleAdaptation - { - public: - StdLargeVec h_ratio_; /**< the ratio between reference smoothing length to variable smoothing length */ - - ParticleWithLocalRefinement(Real h_spacing_ratio_, - int global_refinement_level, int local_refinement_level); - virtual ~ParticleWithLocalRefinement() {}; - - size_t getMeshCellLinkedListTotalLevel(); - size_t getLevelSetTotalLevel(); - virtual Real SmoothingLengthRatio(size_t particle_index_i) override {return h_ratio_[particle_index_i]; }; - - virtual void assignBaseParticles(BaseParticles* base_particles) override; - virtual BaseMeshCellLinkedList* createMeshCellLinkedList() override; - virtual BaseLevelSet* createLevelSet(ComplexShape& complex_shape) override; - }; - /** - * @class ParticleSpacingByBodyShape - * @brief Adaptive resolutions within a SPH body according to the distance to the body surface. - */ - class ParticleSpacingByBodyShape : public ParticleWithLocalRefinement - { - public: - ParticleSpacingByBodyShape(Real smoothing_length_ratio, - int global_refinement_level, int local_refinement_level); - virtual ~ParticleSpacingByBodyShape() {}; - - Real getLocalSpacing(ComplexShape& complex_shape, Vecd& position); - }; -} -#endif //PARTICLE_ADAPTATION_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/particle_sorting.cpp b/SPHINXsys/src/shared/particles/particle_sorting.cpp deleted file mode 100644 index bd56391df3..0000000000 --- a/SPHINXsys/src/shared/particles/particle_sorting.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/** - * @file particle_sorting.cpp - * @author Xiangyu Hu - */ - -#include "particle_sorting.h" - -#include "base_body.h" -#include "base_particles.h" -#include "mesh_cell_linked_list.h" - -namespace SPH { - //=================================================================================================// - SwapParticleData::SwapParticleData(BaseParticles* base_particles) : - sequence_(base_particles->sequence_), - unsorted_id_(base_particles->unsorted_id_), - sortable_data_(base_particles->sortable_data_) {} - //=================================================================================================// - void SwapParticleData::operator () (size_t* a, size_t* b) - { - std::swap(*a, *b); - - size_t index_a = a - sequence_.data(); - size_t index_b = b - sequence_.data(); - std::swap(unsorted_id_[index_a], unsorted_id_[index_b]); - loopParticleData(sortable_data_, index_a, index_b); - } - //=================================================================================================// - ParticleSorting::ParticleSorting(RealBody* real_body) : - base_particles_(nullptr), swap_particle_data_(nullptr), compare_(), - quick_sort_particle_range_(nullptr), quick_sort_particle_body_() {} - //=================================================================================================// - void ParticleSorting::sortingParticleData(size_t* begin, size_t size) - { - quick_sort_particle_range_->begin_ = begin; - quick_sort_particle_range_->size_ = size; - parallel_for(*quick_sort_particle_range_, quick_sort_particle_body_, ap); - updateSortedId(); - } - //=================================================================================================// - void ParticleSorting::updateSortedId() - { - StdLargeVec& unsorted_id = base_particles_->unsorted_id_; - StdLargeVec& sorted_id = base_particles_->sorted_id_; - size_t total_real_particles = base_particles_->total_real_particles_; - parallel_for(blocked_range(0, total_real_particles), - [&](const blocked_range& r) { - for (size_t i = r.begin(); i != r.end(); ++i) { - sorted_id[unsorted_id[i]] = i; - } - }, ap); - } - //=================================================================================================// - void ParticleSorting::assignBaseParticles(BaseParticles* base_particles) - { - base_particles_ = base_particles; - swap_particle_data_ = new SwapParticleData(base_particles); - size_t* begin = base_particles_->sequence_.data(); - quick_sort_particle_range_ = new tbb::interafce9::internal::QuickSortParticleRange(begin, 0, compare_, *swap_particle_data_); - }; - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/particles/particle_sorting.h b/SPHINXsys/src/shared/particles/particle_sorting.h deleted file mode 100644 index befbf0c5be..0000000000 --- a/SPHINXsys/src/shared/particles/particle_sorting.h +++ /dev/null @@ -1,265 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ - -/** -* @file particle_sorting.h -* @brief Here gives the classes for particle sorting. -* @author Xiangyu Hu -*/ - - -#ifndef PARTICLE_SORTING_H -#define PARTICLE_SORTING_H - - - -#include "base_data_package.h" -#include "sph_data_conainers.h" - -/** this is a reformulation of tbb parallel_sort for particle data */ -namespace tbb { - namespace interafce9 { - namespace internal { - - #ifdef TBB_2021_2_0 - using tbb::detail::no_assign; - #else - using tbb::internal::no_assign; - #endif - - /** sorting particle */ - template - class QuickSortParticleRange : private no_assign { - - inline size_t median_of_three(const RandomAccessIterator& array, size_t l, size_t m, size_t r) const { - return comp_(array[l], array[m]) ? (comp_(array[m], array[r]) ? m : (comp_(array[l], array[r]) ? r : l)) - : (comp_(array[r], array[m]) ? m : (comp_(array[r], array[l]) ? r : l)); - } - - inline size_t PseudoMedianOfNine(const RandomAccessIterator& array, const QuickSortParticleRange& range) const { - size_t offset = range.size_ / 8u; - return median_of_three(array, - median_of_three(array, 0, offset, offset * 2), - median_of_three(array, offset * 3, offset * 4, offset * 5), - median_of_three(array, offset * 6, offset * 7, range.size_ - 1)); - - } - - size_t splitRange(QuickSortParticleRange& range) { - RandomAccessIterator array = range.begin_; - RandomAccessIterator key0 = range.begin_; - size_t m = PseudoMedianOfNine(array, range); - if (m) swap_particle_data_(array, array + m); - - size_t i = 0; - size_t j = range.size_; - // Partition interval [i+1,j-1] with key *key0. - for (;;) { - __TBB_ASSERT(i < j, nullptr); - // Loop must terminate since array[l]==*key0. - do { - --j; - __TBB_ASSERT(i <= j, "bad ordering relation?"); - } while (comp_(*key0, array[j])); - do { - __TBB_ASSERT(i <= j, nullptr); - if (i == j) goto quick_sort_particle_partition; - ++i; - } while (comp_(array[i], *key0)); - if (i == j) goto quick_sort_particle_partition; - swap_particle_data_(array + i, array + j); - } - quick_sort_particle_partition: - // Put the partition key were it belongs - swap_particle_data_(array + j, key0); - // array[l..j) is less or equal to key. - // array(j..r) is greater or equal to key. - // array[j] is equal to key - i = j + 1; - size_t new_range_size = range.size_ - i; - range.size_ = j; - return new_range_size; - } - - public: - - static const size_t grainsize_ = 500; - const Compare& comp_; - SwapType& swap_particle_data_; - size_t size_; - RandomAccessIterator begin_; - - QuickSortParticleRange(RandomAccessIterator begin, - size_t size, const Compare& compare, SwapType& swap_particle_data) : - comp_(compare), swap_particle_data_(swap_particle_data), - size_(size), begin_(begin) {} - - bool empty() const { return size_ == 0; } - bool is_divisible() const { return size_ >= grainsize_; } - - QuickSortParticleRange(QuickSortParticleRange& range, split) - : comp_(range.comp_), swap_particle_data_(range.swap_particle_data_) - , size_(splitRange(range)) - // +1 accounts for the pivot element, which is at its correct place - // already and, therefore, is not included into subranges. - , begin_(range.begin_ + range.size_ + 1) {} - }; - - /* - Description : QuickSort in Iterator format - Link : https://stackoverflow.com/a/54976413/3547485 - Ref : http://www.cs.fsu.edu/~lacher/courses/COP4531/lectures/sorts/slide09.html - */ - template - RandomAccessIterator Partition(RandomAccessIterator first, RandomAccessIterator last, Compare& compare, SwapType& swap_particle_data) - { - auto pivot = std::prev(last, 1); - auto i = first; - for (auto j = first; j != pivot; ++j) { - // bool format - if (compare(*j, *pivot)) { - swap_particle_data(i++, j); - } - } - swap_particle_data(i, pivot); - return i; - } - - template - void SerialQuickSort(RandomAccessIterator first, RandomAccessIterator last, Compare& compare, SwapType& swap_particle_data) - { - if (std::distance(first, last) > 1) { - RandomAccessIterator bound = Partition(first, last, compare, swap_particle_data); - SerialQuickSort(first, bound, compare, swap_particle_data); - SerialQuickSort(bound + 1, last, compare, swap_particle_data); - } - } - - /* - Description : Insertsort in Iterator format - Link : http://www.codecodex.com/wiki/Insertion_sort - */ - - template< typename RandomAccessIterator, typename Compare, typename SwapType> - void InsertionSort(RandomAccessIterator First, RandomAccessIterator Last, Compare& compare, SwapType& swap_particle_data) - { - RandomAccessIterator min = First; - for (RandomAccessIterator i = First + 1; i < Last; ++i) - if (compare(*i, *min)) min = i; - - swap_particle_data(First, min); - while (++First < Last) - for (RandomAccessIterator j = First; compare(*j, *(j - 1)); --j) - swap_particle_data((j - 1), j); - } - - /** Body class used to sort elements in a range that is smaller than the grainsize. */ - template - struct QuickSortParticleBody { - void operator()(const QuickSortParticleRange& range) const { - SerialQuickSort(range.begin_, range.begin_ + range.size_, range.comp_, range.swap_particle_data_); - } - }; - - } - } -} - -namespace SPH { - - class RealBody; - class BaseParticles; - class BaseMeshCellLinkedList; - - /** - * @class CompareParticleSequence - * @brief compare the sequence of two particles - */ - struct CompareParticleSequence - { - bool operator () (const size_t& x, const size_t& y) const - { return x < y; }; - }; - - /** - * @class SwapParticleData - * @brief swap sortable particle data according to a sequence - */ - class SwapParticleData - { - protected: - StdLargeVec& sequence_; - StdLargeVec& unsorted_id_; - ParticleData& sortable_data_; - - template - struct swapParticleDataValue - { - void operator () (ParticleData& particle_data, size_t index_a, size_t index_b) const - { - StdVec*> variables = std::get(particle_data); - for (size_t i = 0; i != variables.size(); ++i) - { - StdLargeVec& variable= *variables[i]; - std::swap(variable[index_a], variable[index_b]); - } - }; - }; - - public: - SwapParticleData(BaseParticles* base_particles); - ~SwapParticleData() {}; - - /** the operater overload for swapping particle data. - * the arguments are the same with std::iter_swap - */ - void operator () (size_t* a, size_t* b); - }; - - /** - * @class ParticleSorting - * @brief The class for sorting particle according a given sequence. - */ - class ParticleSorting - { - protected: - BaseParticles* base_particles_; - - SwapParticleData* swap_particle_data_; - CompareParticleSequence compare_; - tbb::interafce9::internal::QuickSortParticleRange* quick_sort_particle_range_; - tbb::interafce9::internal::QuickSortParticleBody quick_sort_particle_body_; - public: - ParticleSorting(RealBody* real_body); - virtual ~ParticleSorting() {}; - - void assignBaseParticles(BaseParticles* base_particles); - /** sorting particle data according to the cell location of particles */ - virtual void sortingParticleData(size_t* begin, size_t size); - /** update the reference of sorted data from unsorted data */ - virtual void updateSortedId(); - }; -} -#endif //PARTICLE_SORTING_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/solid_particles.cpp b/SPHINXsys/src/shared/particles/solid_particles.cpp deleted file mode 100644 index e59c11deed..0000000000 --- a/SPHINXsys/src/shared/particles/solid_particles.cpp +++ /dev/null @@ -1,173 +0,0 @@ -/** - * @file solid_particles.cpp - * @brief Definition of functions declared in solid_particles.h - * @author Xiangyu Hu and Chi Zhang - */ -#include "solid_particles.h" - -#include "geometry.h" -#include "base_body.h" -#include "elastic_solid.h" -#include "inelastic_solid.h" -#include "xml_engine.h" - -namespace SPH { - //=============================================================================================// - SolidParticles::SolidParticles(SPHBody* body) - : SolidParticles(body, new Solid()){} - //=============================================================================================// - SolidParticles::SolidParticles(SPHBody* body, Solid* solid) - : BaseParticles(body, solid) - { - solid->assignSolidParticles(this); - //---------------------------------------------------------------------- - // register particle data - //---------------------------------------------------------------------- - registerAVariable(pos_0_, "InitialPosition"); - registerAVariable(n_, "NormalDirection"); - registerAVariable(n_0_, "InitialNormalDirection"); - registerAVariable(B_, "CorrectionMatrix", Matd(1.0)); - //---------------------------------------------------------------------- - // for FSI - //---------------------------------------------------------------------- - registerAVariable(vel_ave_, "AverageVelocity"); - registerAVariable(dvel_dt_ave_, "AverageAcceleration"); - registerAVariable(force_from_fluid_, "ForceFromFluid"); - //---------------------------------------------------------------------- - // For solid-solid contact - //---------------------------------------------------------------------- - registerAVariable(contact_density_, "ContactDensity"); - registerAVariable(contact_force_, "ContactForce"); - //----------------------------------------------------------------------------------------- - // register sortable particle data before building up particle configuration - //----------------------------------------------------------------------------------------- - registerASortableVariable("Position"); - registerASortableVariable("InitialPosition"); - registerASortableVariable("Volume"); - //set the initial value for initial particle position - for (size_t i = 0; i != pos_n_.size(); ++i) pos_0_[i] = pos_n_[i]; - //sorting particle once - //dynamic_cast(body)->sortParticleWithMeshCellLinkedList(); - } - //=============================================================================================// - void SolidParticles::offsetInitialParticlePosition(Vecd offset) - { - for (size_t i = 0; i != total_real_particles_; ++i) - { - pos_n_[i] += offset; - pos_0_[i] += offset; - } - } - //=================================================================================================// - void SolidParticles::initializeNormalDirectionFromGeometry() - { - ComplexShape* body_shape = body_->body_shape_; - for (size_t i = 0; i != total_real_particles_; ++i) - { - Vecd normal_direction = body_shape->findNormalDirection(pos_n_[i]); - n_[i] = normal_direction; - n_0_[i] = normal_direction; - } - } - //=================================================================================================// - Vecd SolidParticles::normalizeKernelGradient(size_t particle_index_i, Vecd& kernel_gradient) - { - return B_[particle_index_i] * kernel_gradient; - } - //=================================================================================================// - Vecd SolidParticles::getKernelGradient(size_t particle_index_i, size_t particle_index_j, Real dW_ij, Vecd& e_ij) - { - return 0.5 * dW_ij * (B_[particle_index_i] + B_[particle_index_j]) * e_ij; - } - //=============================================================================================// - ElasticSolidParticles::ElasticSolidParticles(SPHBody* body, ElasticSolid* elastic_solid) - : SolidParticles(body, elastic_solid) - { - elastic_solid->assignElasticSolidParticles(this); - //---------------------------------------------------------------------- - // register particle data - //---------------------------------------------------------------------- - registerAVariable(F_, "DeformationGradient", Matd(1.0)); - registerAVariable(dF_dt_, "DeformationRate"); - registerAVariable(stress_PK1_, "FirstPiolaKirchhoffStress"); - //---------------------------------------------------------------------- - // add restart output particle data - //---------------------------------------------------------------------- - addAVariableNameToList(variables_to_restart_, "DeformationGradient"); - } - //=================================================================================================// - void ElasticSolidParticles::writeParticlesToVtuFile(std::ofstream& output_file) - { - SolidParticles::writeParticlesToVtuFile(output_file); - - size_t total_real_particles = total_real_particles_; - - output_file << " \n"; - output_file << " "; - for (size_t i = 0; i != total_real_particles; ++i) { - output_file << std::fixed << std::setprecision(9) << von_Mises_stress(i) << " "; - } - output_file << std::endl; - output_file << " \n"; - } - //=================================================================================================// - void ElasticSolidParticles::writePltFileHeader(std::ofstream& output_file) - { - SolidParticles::writePltFileHeader(output_file); - - output_file << ",\" von Mises stress \""; - } - //=================================================================================================// - void ElasticSolidParticles::writePltFileParticleData(std::ofstream& output_file, size_t index_i) - { - SolidParticles::writePltFileParticleData(output_file, index_i); - - output_file << von_Mises_stress(index_i) << " "; - } - //=============================================================================================// - void ActiveMuscleParticles::initializeActiveMuscleParticleData() - { - //---------------------------------------------------------------------- - // register particle data - //---------------------------------------------------------------------- - registerAVariable(active_stress_, "ActiveStress"); - registerAVariable(active_contraction_stress_, "ActiveContractionStress"); - //---------------------------------------------------------------------- - // add restart output particle data - //---------------------------------------------------------------------- - addAVariableNameToList(variables_to_restart_, "ActiveContractionStress"); - } - //=============================================================================================// - ShellParticles::ShellParticles(SPHBody* body, ElasticSolid* elastic_solid, Real thickness) - : ElasticSolidParticles(body, elastic_solid) - { - elastic_solid->assignElasticSolidParticles(this); - //---------------------------------------------------------------------- - // register particle data - //---------------------------------------------------------------------- - registerAVariable(transformation_matrix_, "TransformationMatrix", Matd(1.0)); - registerAVariable(shell_thickness_, "Thickness", thickness); - registerAVariable(pseudo_n_, "PseudoNormal"); - registerAVariable(dpseudo_n_dt_, "PseudoNormalChangeRate"); - registerAVariable(dpseudo_n_d2t_, "PseudoNormal2ndOrderTimeDerivative"); - registerAVariable(rotation_, "Rotation"); - registerAVariable(angular_vel_, "AngularVelocity"); - registerAVariable(dangular_vel_dt_, "AngularAcceleration"); - registerAVariable(F_bending_, "BendingDeformationGradient"); - registerAVariable(dF_bending_dt_, "BendingDeformationGradientChangeRate"); - registerAVariable(global_shear_stress_, "GlobalShearStress"); - registerAVariable(global_stress_, "GlobalStress"); - registerAVariable(global_moment_, "GlobalMoment"); - //---------------------------------------------------------------------- - // add basic output particle data - //---------------------------------------------------------------------- - addAVariableToWrite("Rotation"); - //---------------------------------------------------------------------- - // add restart output particle data - //---------------------------------------------------------------------- - addAVariableNameToList(variables_to_restart_, "PseudoNormal"); - addAVariableNameToList(variables_to_restart_, "Rotation"); - addAVariableNameToList(variables_to_restart_, "AngularVelocity"); - } - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/particles/solid_particles.h b/SPHINXsys/src/shared/particles/solid_particles.h deleted file mode 100644 index a87f0e9b08..0000000000 --- a/SPHINXsys/src/shared/particles/solid_particles.h +++ /dev/null @@ -1,173 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file solid_particles.h - * @brief This is the derived class of base particles. - * @author Xiangyu Hu and Chi Zhang - */ - -#ifndef SOLID_PARTICLES_H -#define SOLID_PARTICLES_H - - -#include "base_particles.h" - -namespace SPH { - - //---------------------------------------------------------------------- - // preclaimed classes - //---------------------------------------------------------------------- - class Solid; - class ElasticSolid; - class PlasticSolid; - template class ActiveMuscle; - - /** - * @class SolidParticles - * @brief A group of particles with solid body particle data. - */ - class SolidParticles : public BaseParticles - { - public: - SolidParticles(SPHBody* body); - SolidParticles(SPHBody* body, Solid* solid); - virtual ~SolidParticles() {}; - - StdLargeVec pos_0_; /**< initial position */ - StdLargeVec n_; /**< current normal direction */ - StdLargeVec n_0_; /**< inital normal direction */ - StdLargeVec B_; /**< configuration correction for linear reproducing */ - //---------------------------------------------------------------------- - // for fluid-structure interaction (FSI) - //---------------------------------------------------------------------- - StdLargeVec vel_ave_; /**< fluid time-step averaged particle velocity */ - StdLargeVec dvel_dt_ave_; /**< fluid time-step averaged particle acceleration */ - StdLargeVec force_from_fluid_; /**< forces (including pressure and viscous) from fluid */ - //---------------------------------------------------------------------- - // for solid-solid contact dynamics - //---------------------------------------------------------------------- - StdLargeVec contact_density_; /**< density due to contact of solid-solid. */ - StdLargeVec contact_force_; /**< contact force from other solid body or bodies */ - - void offsetInitialParticlePosition(Vecd offset); - void initializeNormalDirectionFromGeometry(); - void ParticleTranslationAndRotation(Transformd& transform); - - /** Normalize a gradient. */ - virtual Vecd normalizeKernelGradient(size_t particle_index_i, Vecd& gradient) override; - /** Get the kernel gradient in weak form. */ - virtual Vecd getKernelGradient(size_t particle_index_i, size_t particle_index_j, Real dW_ij, Vecd& e_ij) override; - - virtual SolidParticles* ThisObjectPtr() override {return this;}; - }; - - /** - * @class ElasticSolidParticles - * @brief A group of particles with elastic body particle data. - */ - class ElasticSolidParticles : public SolidParticles - { - protected: - /**< Computing von_Mises_stress. */ - Real von_Mises_stress(size_t particle_i); - virtual void writePltFileHeader(std::ofstream& output_file); - virtual void writePltFileParticleData(std::ofstream& output_file, size_t index_i); - - public: - ElasticSolidParticles(SPHBody* body, ElasticSolid* elastic_solid); - virtual ~ElasticSolidParticles() {}; - - StdLargeVec F_; /**< deformation tensor */ - StdLargeVec dF_dt_; /**< deformation tensor change rate */ - StdLargeVec stress_PK1_; /**< first Piola-Kirchhoff stress tensor */ - - virtual void writeParticlesToVtuFile(std::ofstream &output_file) override; - - virtual ElasticSolidParticles* ThisObjectPtr() override {return this;}; - }; - - /** - * @class ActiveMuscleParticles - * @brief A group of particles with active muscle particle data. - */ - class ActiveMuscleParticles : public ElasticSolidParticles - { - public: - - StdLargeVec active_contraction_stress_; /**< active contraction stress */ - StdLargeVec active_stress_; /**< active stress */ //seems to be moved to method class - - template - ActiveMuscleParticles(SPHBody* body, ActiveMuscle* active_muscle) : - ElasticSolidParticles(body, active_muscle) - { - active_muscle->assignActiveMuscleParticles(this); - initializeActiveMuscleParticleData(); - }; - virtual ~ActiveMuscleParticles() {}; - - virtual ActiveMuscleParticles* ThisObjectPtr() override { return this; }; - private: - void initializeActiveMuscleParticleData(); - }; - - /** - * @class ShellParticles - * @brief A group of particles with shell particle data. - */ - class ShellParticles : public ElasticSolidParticles - { - public: - ShellParticles(SPHBody* body, ElasticSolid* elastic_solid, Real thickness); - virtual ~ShellParticles() {}; - - StdLargeVec transformation_matrix_; /**< initial transformation matrix from global to local coordinates */ - StdLargeVec shell_thickness_; /**< shell thickness */ - //---------------------------------------------------------------------- - // extra generalized coordinates in global coordinate - //---------------------------------------------------------------------- - StdLargeVec pseudo_n_; /**< current pseudo-normal vector */ - StdLargeVec dpseudo_n_dt_; /**< pseudo-normal vector change rate */ - StdLargeVec dpseudo_n_d2t_; /**< pseudo-normal vector second order time derivation */ - //---------------------------------------------------------------------- - // extra generalized coordinate and velocity in local coordinate - //---------------------------------------------------------------------- - StdLargeVec rotation_; /**< rotation angle of the initial normal respective to each axis */ - StdLargeVec angular_vel_; /**< angular velocity respective to each axis */ - StdLargeVec dangular_vel_dt_; /**< angular accelration of respective to each axis*/ - //---------------------------------------------------------------------- - // extra deformation and deformation rate in local coordinate - //---------------------------------------------------------------------- - StdLargeVec F_bending_; /**< bending deformation gradient */ - StdLargeVec dF_bending_dt_; /**< bending deformation gradient change rate */ - //---------------------------------------------------------------------- - // extra stress for pair interaction in global coordinate - //---------------------------------------------------------------------- - StdLargeVec global_shear_stress_; /**< global shear stress */ - StdLargeVec global_stress_; /**< global stress for pair interaction */ - StdLargeVec global_moment_; /**< global bending moment for pair interaction */ - - virtual ShellParticles* ThisObjectPtr() override {return this;}; - }; -} -#endif //SOLID_PARTICLES_H diff --git a/SPHINXsys/src/shared/regression_testing/CMakeLists.txt b/SPHINXsys/src/shared/regression_testing/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/regression_testing/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/regression_testing/meanvalue_variance_method.h b/SPHINXsys/src/shared/regression_testing/meanvalue_variance_method.h deleted file mode 100644 index 06212e3df2..0000000000 --- a/SPHINXsys/src/shared/regression_testing/meanvalue_variance_method.h +++ /dev/null @@ -1,203 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file regression_testing.h - * @brief Classes for the comparison between validated and tested results. - * @author Bo Zhang, Xiangyu Hu - */ - -#pragma once -#include "in_output.h" -#include "xml_engine.h" -#include "all_physical_dynamics.h" - -namespace SPH -{ - /** - * @class meanvalue_variance_method.h - * @brief the regression testing is based on the converged meanvalue and variance. - */ - - template - class RegressionTesting : public ObserveMethodType - { - /* identify the the variable type from the parent class. */ - using VariableType = decltype(ObserveMethodType::type_indicator_); - - protected: - std::string result_filefullpath_; /* the path for result. */ - std::string meanvalue_filefullpath_; /* the path for meanvalue. */ - std::string variance_filefullpath_; /* the path for variance. */ - std::string runtimes_filefullpath_; /* the path for runtimes. */ - std::string converged; /* the tag for result converged. */ - - XmlEngine result_xml_engine_in_; /* xml engine for result input. */ - XmlEngine meanvalue_xml_engine_in_; /* xml engine for meanvalue input. */ - XmlEngine variance_xml_engine_in_; /* xml engine for variance input. */ - XmlEngine result_xml_engine_out_; /* xml engine for result output. */ - XmlEngine meanvalue_xml_engine_out_; /* xml engine for meanvalue output. */ - XmlEngine variance_xml_engine_out_; /* xml engine for variance output. */ - - VariableType threshold_mean_, threshold_variance_; /* the threshold container for mean and variance. */ - StdVec> result_; /* the container of results in all runs. */ - DataVec meanvalue_, meanvalue_new_; /* the container of meanvalue. */ - DataVec variance_, variance_new_; /* the container of variance. */ - - size_t i_, j_; /* the size of each layer of the result vector. */ - size_t number_of_snapshot_old_; /* the snapshot size of existed result. */ - size_t difference_; /* the difference of snapshot between old and new result. */ - size_t number_of_run_; /* the times of run. */ - size_t label_for_repeat_; /* the int label for stable convergence. */ - - /* initialize the threshold of meanvalue and variance. */ - void InitializeThreshold(Real& threshold_mean, Real& threshold_variance); - void InitializeThreshold(Vecd& threshold_mean, Vecd& threshold_variance); - void InitializeThreshold(Matd& threshold_mean, Matd& threshold_variance); - - /* the method for calculating the meanvalue. */ - void GetNewMeanValue(DataVec& current_result, DataVec& meanvalue, DataVec& meanvalue_new); - void GetNewMeanValue(DataVec& current_result, DataVec& meanvalue, DataVec& meanvalue_new); - void GetNewMeanValue(DataVec& current_result, DataVec& meanvalue, DataVec& meanvalue_new); - - /* the method for calculating the variance. */ - void GetNewVariance(StdVec>& result, DataVec& meanvalue_new, DataVec& variance, DataVec& variance_new); - void GetNewVariance(StdVec>& result, DataVec& meanvalue_new, DataVec& variance, DataVec& variance_new); - void GetNewVariance(StdVec>& result, DataVec& meanvalue_new, DataVec& variance, DataVec& variance_new); - - /* the method for writing data to xml memory. */ - void WriteDataToXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, const DataVec& quantity); - void WriteDataToXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, const DataVec& quantity); - void WriteDataToXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, const DataVec& quantity); - - /* the method for comparing the meanvalue and variance. */ - size_t CompareParameter(string par_name, DataVec& parameter, DataVec& parameter_new, Real& threshold); - size_t CompareParameter(string par_name, DataVec& parameter, DataVec& parameter_new, Vecd& threshold); - size_t CompareParameter(string par_name, DataVec& parameter, DataVec& parameter_new, Matd& threshold); - - /** the method for testing the new result. */ - size_t TestingNewResult(size_t diff, DataVec& current_result, DataVec& meanvalue, DataVec& variance); - size_t TestingNewResult(size_t diff, DataVec& current_result, DataVec& meanvalue, DataVec& variance); - size_t TestingNewResult(size_t diff, DataVec& current_result, DataVec& meanvalue, DataVec& variance); - - /** setup (load .xml file) and correct the number of old and new result. */ - void SettingupAndCorrection(); - - /** read the result, meanvalue, variance from the .xml files. */ - void ReadResultFromXml(); - void ReadMeanAndVarianceToXml(); - - /** update the meanvalue, variance to a new value. */ - void UpdateMeanValueAndVariance(); - - /** write the result, meanvalue, variance to the .xml files. */ - void WriteResultToXml(); - void WriteMeanAndVarianceToXml(); - - /** compare the meanvalue, variance if converged. */ - bool CompareMeanValueAndVariance(); - - /** testing the new result if the converged within the range. */ - void ResultTesting(); - - public: - template - explicit RegressionTesting(ConstructorArgs... constructor_args) : - ObserveMethodType(constructor_args...), - result_xml_engine_in_("result_xml_engine_in", "result"), - meanvalue_xml_engine_in_("meanvalue_xml_engine_in", "meanvalue"), - variance_xml_engine_in_("variance_xml_engine_in", "variance"), - result_xml_engine_out_("result_xml_engine_out", "result"), - meanvalue_xml_engine_out_("meanvalue_xml_engine_out", "meanvalue"), - variance_xml_engine_out_("variance_xml_engine_out", "variance") - { - result_filefullpath_ = this->in_output_.input_folder_ + "/" + this->body_name_ - + "_" + this->quantity_name_ + "_" + this->in_output_.restart_step_ + "_result.xml"; - meanvalue_filefullpath_ = this->in_output_.input_folder_ + "/" +this-> body_name_ - + "_" + this->quantity_name_ + "_" + this->in_output_.restart_step_ + "._meanvalue.xml"; - variance_filefullpath_ = this->in_output_.input_folder_ + "/" + this->body_name_ - + "_" + this->quantity_name_ + "_" + this->in_output_.restart_step_ + "._variance.xml"; - runtimes_filefullpath_ = this->in_output_.input_folder_ + "/" + this->body_name_ - + "_" + this->quantity_name_ + "_" + this->in_output_.restart_step_ + "_runtimes.dat"; - - if (!fs::exists(runtimes_filefullpath_)) - { - number_of_run_ = 1; - converged = "false"; - label_for_repeat_ = 0; - } - else - { - std::ifstream in_file(runtimes_filefullpath_.c_str()); - in_file >> converged; - in_file >> number_of_run_; - in_file >> label_for_repeat_; - in_file.close(); - }; - }; - virtual ~RegressionTesting(); - - /* the interface to write date into xml memory. */ - void writeToFile(Real iteration = 0) - { - ObserveMethodType::writeToFile(); - this->WriteToXml(iteration); - } - - /* the interface to write XML memory into XML file. */ - void WriteXmlToXmlFile() { this->observe_xml_engine_.writeToXmlFile(this->filefullpath_input_); }; - - /* read local data from XML file. */ - void ReadXmlFromXmlFile() { this->ReadFromXml(); }; - - /** the interface for generating the priori converged result. */ - void generateDataBase(VariableType threshold_mean, VariableType threshold_variance) - { - WriteXmlToXmlFile(); - ReadXmlFromXmlFile(); - InitializeThreshold(threshold_mean, threshold_variance); - if (converged == "false") - { - SettingupAndCorrection(); - ReadResultFromXml(); - ReadMeanAndVarianceToXml(); - UpdateMeanValueAndVariance(); - WriteResultToXml(); - WriteMeanAndVarianceToXml(); - CompareMeanValueAndVariance(); - } - }; - - /* the interface for testing the new result. */ - void newResultTesting() - { - WriteXmlToXmlFile(); - ReadXmlFromXmlFile(); - SettingupAndCorrection(); - ReadMeanAndVarianceToXml(); - ResultTesting(); - }; - - /* calculate the meanvalue of a time series result. */ - void GetMeanvalueInTime(size_t iteration = 0); - }; -}; \ No newline at end of file diff --git a/SPHINXsys/src/shared/regression_testing/meanvalue_variance_method.hpp b/SPHINXsys/src/shared/regression_testing/meanvalue_variance_method.hpp deleted file mode 100644 index 99d234b234..0000000000 --- a/SPHINXsys/src/shared/regression_testing/meanvalue_variance_method.hpp +++ /dev/null @@ -1,663 +0,0 @@ -/** - * @file regression_testing.cpp - * @author Bo Zhang, Xiangyu Hu - */ - -#include "meanvalue_variance_method.h" - - //=================================================================================================// -namespace SPH -{ - //=================================================================================================// - template - void RegressionTesting::InitializeThreshold( - Real& threshold_mean, Real& threshold_variance) - { - threshold_mean_ = threshold_mean; - threshold_variance_ = threshold_variance; - }; - //=================================================================================================// - template - void RegressionTesting::InitializeThreshold( - Vecd& threshold_mean, Vecd& threshold_variance) - { - for (size_t index_i = 0; index_i != threshold_mean_.size(); ++index_i) - { - threshold_mean_[index_i] = threshold_mean[index_i]; - threshold_variance_[index_i] = threshold_variance[index_i]; - } - }; - //=================================================================================================// - template - void RegressionTesting::InitializeThreshold( - Matd& threshold_mean, Matd& threshold_variance) - { - for (size_t index_i = 0; index_i != threshold_mean_.size(); ++index_i) - { - for (size_t index_j = 0; index_j != threshold_mean_.size(); ++index_j) - { - threshold_mean_[index_i][index_j] = threshold_mean[index_i][index_j]; - threshold_variance_[index_i][index_j] = threshold_variance[index_i][index_j]; - } - } - }; - //=================================================================================================// - template - void RegressionTesting::GetNewMeanValue(DataVec& current_result, - DataVec& meanvalue, DataVec& meanvalue_new) - { - for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) - { - for (size_t j = 0; j != j_; ++j) - { - meanvalue_new[i][j] = (meanvalue[i][j] * (number_of_run_ - 1) - + current_result[i][j]) / number_of_run_; - } - } - }; - //=================================================================================================// - template - void RegressionTesting::GetNewMeanValue(DataVec& current_result, - DataVec& meanvalue, DataVec& meanvalue_new) - { - for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) - { - for (size_t j = 0; j != j_; ++j) - { - for (size_t index_i = 0; index_i != meanvalue[0][0].size(); ++index_i) - { - meanvalue_new[i][j][index_i] = (meanvalue[i][j][index_i] * (number_of_run_ - 1) - + current_result[i][j][index_i]) / number_of_run_; - } - } - } - }; - //=================================================================================================// - template - void RegressionTesting::GetNewMeanValue(DataVec& current_result, - DataVec& meanvalue, DataVec& meanvalue_new) - { - for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) - { - for (size_t j = 0; j != j_; ++j) - { - for (size_t index_i = 0; index_i != meanvalue[0][0].size(); ++index_i) - { - for (size_t index_j = 0; index_j != meanvalue[0][0].size(); +index_j) - { - meanvalue_new[i][j][index_i][index_j] = (meanvalue[i][j][index_i][index_j] * - (number_of_run_ - 1) + current_result[i][j][index_i][index_j]) / number_of_run_; - } - } - } - } - }; - //=================================================================================================// - template - void RegressionTesting::GetNewVariance(StdVec>& result, - DataVec& meanvalue_new, DataVec& variance, DataVec& variance_new) - { - for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) - { - for (size_t j = 0; j != j_; ++j) - { - for (size_t n = 0; n != number_of_run_; ++n) - { - variance_new[i][j] = SMAX(variance[i][j], variance_new[i][j], - std::pow((result[n][i][j] - meanvalue_new[i][j]), 2)); - variance_new[i][j] = variance_new[i][j] < std::pow(meanvalue_new[i][j] * 1.0e-6, 2) ? - std::pow(meanvalue_new[i][j] * 1.0e-6, 2) : variance_new[i][j]; - } - } - } - }; - //=================================================================================================// - template - void RegressionTesting::GetNewVariance(StdVec>& result, - DataVec& meanvalue_new, DataVec& variance, DataVec& variance_new) - { - for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) - { - for (size_t j = 0; j != j_; ++j) - { - for (size_t n = 0; n != number_of_run_; ++n) - { - for (size_t index_i = 0; index_i != variance[0][0].size(); ++index_i) - { - variance_new[i][j][index_i] = SMAX(variance[i][j][index_i], variance_new[i][j][index_i], - std::pow((result[n][i][j][index_i] - meanvalue_new[i][j][index_i]), 2)); - variance_new[i][j][index_i] = variance_new[i][j][index_i] < - std::pow(meanvalue_new[i][j][index_i] * 1.0e-6, 2) ? - std::pow(meanvalue_new[i][j][index_i] * 1.0e-6, 2) : variance_new[i][j][index_i]; - } - } - } - } - }; - //=================================================================================================// - template - void RegressionTesting::GetNewVariance(StdVec>& result, - DataVec& meanvalue_new, DataVec& variance, DataVec& variance_new) - { - for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) - { - for (size_t j = 0; j != j_; ++j) - { - for (size_t n = 0; n != number_of_run_; ++n) - { - for (size_t index_i = 0; index_i != variance[0][0].size(); ++index_i) - { - for (size_t index_j = 0; index_j != variance[0][0].size(); ++index_j) - { - variance_new[i][j][index_i][index_j] = SMAX(variance[i][j][index_i][index_j], - variance_new[i][j][index_i][index_j], std::pow((result[n][i][j][index_i][index_j] - - meanvalue_new[i][j][index_i][index_j]), 2)); - variance_new[i][j][index_i][index_j] = variance_new[i][j][index_i][index_j] < - std::pow(meanvalue_new[i][j][index_i][index_j] * 1.0e-6, 2) ? - std::pow(meanvalue_new[i][j][index_i][index_j] * 1.0e-6, 2) : - variance_new[i][j][index_i][index_j]; - } - } - } - } - } - }; - //=================================================================================================// - template - void RegressionTesting::WriteDataToXmlMemory(XmlEngine xmlengine, - SimTK::Xml::Element element, const DataVec& quantity) - { - for (size_t snapshot_n_ = 0; snapshot_n_ != SMIN(i_, number_of_snapshot_old_); ++snapshot_n_) - { - std::string element_name_ = this->element_tag_[snapshot_n_]; - xmlengine.addChildToElement(element, element_name_); - for (size_t particle_n_ = 0; particle_n_ != j_; ++particle_n_) - { - SimTK::Xml::element_iterator ele_ite = element.element_begin(element_name_); - std::string attribute_name_ = this->quantity_name_ + "_" + std::to_string(particle_n_); - xmlengine.setAttributeToElement(ele_ite, attribute_name_, quantity[snapshot_n_][particle_n_]); - } - } - }; - //=================================================================================================// - template - void RegressionTesting::WriteDataToXmlMemory(XmlEngine xmlengine, - SimTK::Xml::Element element, const DataVec& quantity) - { - for (size_t snapshot_n_ = 0; snapshot_n_ != SMIN(i_, number_of_snapshot_old_); ++snapshot_n_) - { - std::string element_name_ = this->element_tag_[snapshot_n_]; - xmlengine.addChildToElement(element, element_name_); - for (size_t particle_n_ = 0; particle_n_ != j_; ++particle_n_) - { - SimTK::Xml::element_iterator ele_ite = element.element_begin(element_name_); - std::string attribute_name_ = this->quantity_name_ + "_" + std::to_string(particle_n_); - xmlengine.setAttributeToElement(ele_ite, attribute_name_, quantity[snapshot_n_][particle_n_]); - } - } - }; - //=================================================================================================// - template - void RegressionTesting::WriteDataToXmlMemory(XmlEngine xmlengine, - SimTK::Xml::Element element, const DataVec& quantity) - { - for (size_t snapshot_n_ = 0; snapshot_n_ != SMIN(i_, number_of_snapshot_old_); ++snapshot_n_) - { - std::string element_name_ = this->element_tag_[snapshot_n_]; - xmlengine.addChildToElement(element, element_name_); - for (size_t particle_n_ = 0; particle_n_ != j_; ++particle_n_) - { - SimTK::Xml::element_iterator ele_ite = element.element_begin(element_name_); - std::string attribute_name_ = this->quantity_name_ + "_" + std::to_string(particle_n_); - xmlengine.setAttributeToElement(ele_ite, attribute_name_, quantity[snapshot_n_][particle_n_]); - } - } - }; - //=================================================================================================// - template - size_t RegressionTesting::CompareParameter(string par_name, - DataVec& parameter, DataVec& parameter_new, Real& threshold) - { - size_t count = 0; - for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) - { - for (size_t j = 0; j != j_; ++j) - { - if ((ABS(parameter[i][j] - parameter_new[i][j]) / (parameter_new[i][j] + TinyReal)) > threshold) - { - std::cout << par_name << ": " << this->quantity_name_ << "[" << j << "] in " - << this->element_tag_[i] << "is not converged, and difference is " - << (ABS(parameter[i][j] - parameter_new[i][j]) / (parameter_new[i][j] + TinyReal)) << endl; - count++; - } - } - } - return count; - }; - //=================================================================================================// - template - size_t RegressionTesting::CompareParameter(string par_name, - DataVec& parameter, DataVec& parameter_new, Vecd& threshold) - { - size_t count = 0; - for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) - { - for (size_t j = 0; j != j_; ++j) - { - for (size_t index_i = 0; index_i != parameter[0][0].size(); ++index_i) - { - if ((ABS(parameter[i][j][index_i] - parameter_new[i][j][index_i]) - / (parameter_new[i][j][index_i] + TinyReal)) > threshold[index_i]) - { - std::cout << par_name << ": " << this->quantity_name_ << "[" << j << "][" << - index_i << "] in " << this->element_tag_[i] << "is not converged, and difference is " - << (ABS(parameter[i][j][index_i] - parameter_new[i][j][index_i]) / - (parameter_new[i][j][index_i] + TinyReal)) << endl; - count++; - } - } - } - } - return count; - }; - //=================================================================================================// - template - size_t RegressionTesting::CompareParameter(string par_name, - DataVec& parameter, DataVec& parameter_new, Matd& threshold) - { - size_t count = 0; - for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) - { - for (size_t j = 0; j != j_; ++j) - { - for (size_t index_i = 0; index_i != parameter[0][0].size(); ++index_i) - { - for (size_t index_j = 0; index_j != parameter[0][0].size(); ++index_j) - { - if ((ABS(parameter[i][j][index_i][index_j] - parameter_new[i][j][index_i][index_j]) / - (parameter_new[i][j][index_i][index_j] + TinyReal)) > threshold[index_i][index_j]) - { - std::cout << par_name << ": " << this->quantity_name_ << "[" << j << "][" << - index_i << "][" << index_j << " ] in " << this->element_tag_[i] << - "is not converged, and difference is " << (ABS(parameter[i][j][index_i][index_j] - - parameter_new[i][j][index_i][index_j]) / - (parameter_new[i][j][index_i][index_j] + TinyReal)) << endl; - count++; - } - } - } - } - } - return count; - }; - //=================================================================================================// - template - size_t RegressionTesting::TestingNewResult(size_t diff, - DataVec& current_result, DataVec& meanvalue, DataVec& variance) - { - size_t count = 0; - for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) - { - for (size_t j = 0; j != j_; ++j) - { - if (std::pow(current_result[i][j] - meanvalue[i + diff][j], 2) > variance[i + diff][j]) - { - std::cout << this->quantity_name_ << "[" << j << "] in " << this->element_tag_[i] << - " is beyond the expection, and difference is " << - (ABS((std::pow(current_result[i][j] - meanvalue[i + diff][j], 2) - variance[i + diff][j])) - / variance[i + diff][j]) << endl; - count++; - } - } - } - return count; - }; - //=================================================================================================// - template - size_t RegressionTesting::TestingNewResult(size_t diff, - DataVec& current_result, DataVec& meanvalue, DataVec& variance) - { - size_t count = 0; - for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) - { - for (size_t j = 0; j != j_; ++j) - { - for (size_t index_i = 0; index_i != meanvalue[0][0].size(); ++index_i) - { - if (std::pow(current_result[i][j][index_i] - meanvalue[i + diff][j][index_i], 2) - > variance[i + diff][j][index_i]) - { - std::cout << this->quantity_name_ << "[" << j << "][" << index_i << "] in " - << this->element_tag_[i] << " is beyond the expection, and difference is " - << (ABS((std::pow(current_result[i][j][index_i] - meanvalue[i + diff][j][index_i], 2) - - variance[i + diff][j][index_i])) / variance[i + diff][j][index_i]) << endl; - count++; - } - } - } - } - return count; - }; - //=================================================================================================// - template - size_t RegressionTesting::TestingNewResult(size_t diff, - DataVec& current_result, DataVec& meanvalue, DataVec& variance) - { - size_t count = 0; - for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) - { - for (size_t j = 0; j != j_; ++j) - { - for (size_t index_i = 0; index_i != meanvalue[0][0].size(); ++index_i) - { - for (size_t index_j = 0; index_j != meanvalue[0][0].size(); ++index_j) - { - if (std::pow(current_result[i][j][index_i][index_j] - - meanvalue[i + diff][j][index_i][index_j], 2) > - variance[i + diff][j][index_i][index_j]) - { - std::cout << this->quantity_name_ << "[" << j << "][" << index_i << "] in " - << this->element_tag_[i] << " is beyond the expection, and difference is " - << (ABS((std::pow(current_result[i][j][index_i][index_j] - - meanvalue[i + diff][j][index_i][index_j], 2) - - variance[i + diff][j][index_i][index_j])) / - variance[i + diff][j][index_i][index_j]) << endl; - count++; - } - } - - } - } - } - return count; - }; - //=================================================================================================// - template - void RegressionTesting::SettingupAndCorrection() - { - /* obtain the size of result. */ - i_ = this->current_result_.size(); - j_ = this->current_result_[0].size(); - - if (number_of_run_ > 1) - { - if (!fs::exists(result_filefullpath_)) - { - std::cout << "\n Error: the input file:" << result_filefullpath_ << " is not exists" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - else if (!fs::exists(meanvalue_filefullpath_)) - { - std::cout << "\n Error: the input file:" << meanvalue_filefullpath_ << " is not exists" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - else if (!fs::exists(variance_filefullpath_)) - { - std::cout << "\n Error: the input file:" << variance_filefullpath_ << " is not exists" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - else - { - /** load the result from .xml file. */ - result_xml_engine_in_.loadXmlFile(result_filefullpath_); - /** load the meanvalue from .xml file. */ - meanvalue_xml_engine_in_.loadXmlFile(meanvalue_filefullpath_); - /** load the variance from .xml file. */ - variance_xml_engine_in_.loadXmlFile(variance_filefullpath_); - - number_of_snapshot_old_ = std::distance(meanvalue_xml_engine_in_.root_element_.element_begin(), - meanvalue_xml_engine_in_.root_element_.element_end()); - - DataVec meanvalue_temp_(SMAX(i_, number_of_snapshot_old_), StdVec(j_)); - DataVec variance_temp_(SMAX(i_, number_of_snapshot_old_), StdVec(j_)); - meanvalue_ = meanvalue_temp_; - variance_ = variance_temp_; - - if (number_of_snapshot_old_ < i_) - { - difference_ = i_ - number_of_snapshot_old_; - for (size_t delete_ = 0; delete_ != difference_; ++delete_) - { - this->current_result_.pop_back(); - } - } - else if (number_of_snapshot_old_ > i_) - { - difference_ = number_of_snapshot_old_ - i_; - } - else - { - difference_ = 0; - } - } - } - else if (number_of_run_ == 1) - { - number_of_snapshot_old_ = i_; - DataVec meanvalue_temp_(i_, StdVec(j_)); - DataVec variance_temp_(i_, StdVec(j_)); - result_.push_back(this->current_result_); - meanvalue_ = meanvalue_temp_; - variance_ = variance_temp_; - } - }; - //=================================================================================================// - template - void RegressionTesting::ReadResultFromXml() - { - if (number_of_run_ > 1) - { - /** read result from .xml */ - DataVec result_in_(SMAX(i_, number_of_snapshot_old_), StdVec(j_)); - for (size_t run_n_ = 0; run_n_ != number_of_run_ - 1; ++run_n_) - { - std::string node_name_ = "Round_" + std::to_string(run_n_); - SimTK::Xml::Element father_element_ = result_xml_engine_in_.getChildElement(node_name_); - for (size_t particle_n_ = 0; particle_n_ != j_; ++particle_n_) - { - this->ReadDataFromXmlMemory(result_xml_engine_in_, father_element_, particle_n_, result_in_); - } - DataVec result_temp_ = result_in_; - for (size_t delete_ = 0; delete_ != difference_; ++delete_) - { - result_temp_.pop_back(); - } - result_.push_back(result_temp_); - } - result_.push_back(this->current_result_); - } - }; - //=================================================================================================// - template - void RegressionTesting::ReadMeanAndVarianceToXml() - { - if (number_of_run_ > 1) - { - /** read mean value from .xml file. */ - SimTK::Xml::Element element_name_meanvalue_ = meanvalue_xml_engine_in_.root_element_; - SimTK::Xml::Element element_name_variance_ = variance_xml_engine_in_.root_element_; - for (size_t particle_n_ = 0; particle_n_ != j_; ++particle_n_) - { - this->ReadDataFromXmlMemory(meanvalue_xml_engine_in_, element_name_meanvalue_, particle_n_, meanvalue_); - this->ReadDataFromXmlMemory(variance_xml_engine_in_, element_name_variance_, particle_n_, variance_); - } - } - }; - //=================================================================================================// - template - void RegressionTesting::UpdateMeanValueAndVariance() - { - if (number_of_run_ > 1) - { - for (size_t delete_ = 0; delete_ != difference_; ++delete_) - { - meanvalue_.pop_back(); - variance_.pop_back(); - } - } - meanvalue_new_ = meanvalue_; - variance_new_ = variance_; - /** update meanvalue of result. */ - GetNewMeanValue(this->current_result_, meanvalue_, meanvalue_new_); - /** update variance of result. */ - GetNewVariance(result_, meanvalue_new_, variance_, variance_new_); - }; - //=================================================================================================// - template - bool RegressionTesting::CompareMeanValueAndVariance() - { - size_t count_not_converged_m = 0; - size_t count_not_converged_v = 0; - /** determine if the average value has converged. */ - count_not_converged_m = CompareParameter("meanvalue", meanvalue_, meanvalue_new_, threshold_mean_); - count_not_converged_v = CompareParameter("variance", variance_, variance_new_, threshold_variance_); - if (count_not_converged_m == 0) - { - std::cout << "The meanvalue of " << this->quantity_name_ << " are converged now." << endl; - if (count_not_converged_v == 0) - { - if (label_for_repeat_ == 1) - { - converged = "true"; - std::cout << "The meanvalue and variance of " << this->quantity_name_ << - " are converged enough times, and rum will stop now." << endl; - return true; - } - else - { - converged = "false"; - label_for_repeat_++; - std::cout << "The variance of " << this->quantity_name_ << " are also converged," - "but they should be converged again to be stable." << endl; - return false; - } - } - else if (count_not_converged_v != 0) - { - converged = "false"; - label_for_repeat_ = 0; - std::cout << "The variance of " << this->quantity_name_ << " is not converged "<< - count_not_converged_v << " times." << endl; - return false; - } - } - else if (count_not_converged_m != 0) - { - converged = "false"; - label_for_repeat_ = 0; - std::cout << "The meanvalue of " << this->quantity_name_ << " is not converged " << - count_not_converged_m << " times." << endl; - return false; - - } - }; - //=================================================================================================// - template - void RegressionTesting::WriteResultToXml() - { - /** Write result to .xml file */ - for (size_t run_n_ = 0; run_n_ != number_of_run_; ++run_n_) - { - std::string node_name_ = "Round_" + std::to_string(run_n_); - result_xml_engine_out_.addElementToXmlDoc(node_name_); - SimTK::Xml::Element father_element_ = - result_xml_engine_out_.getChildElement(node_name_); - WriteDataToXmlMemory(result_xml_engine_out_, father_element_, result_[run_n_]); - } - /** write the result in XmlEngine to xml file. */ - result_xml_engine_out_.writeToXmlFile(result_filefullpath_); - }; - //=================================================================================================// - template - void RegressionTesting::WriteMeanAndVarianceToXml() - { - /** Write meanvalue and variance to .xml file */ - SimTK::Xml::Element element_meanvalue_ = meanvalue_xml_engine_out_.root_element_; - SimTK::Xml::Element element_variance_ = variance_xml_engine_out_.root_element_; - WriteDataToXmlMemory(meanvalue_xml_engine_out_, element_meanvalue_, meanvalue_new_); - WriteDataToXmlMemory(variance_xml_engine_out_, element_variance_, variance_new_); - /** write the meanvalue and variance in XmlEngine to xml file. */ - meanvalue_xml_engine_out_.writeToXmlFile(meanvalue_filefullpath_); - variance_xml_engine_out_.writeToXmlFile(variance_filefullpath_); - }; - //=================================================================================================// - template - RegressionTesting::~RegressionTesting() - { - if (converged == "false") - { - number_of_run_ += 1; - } - if (fs::exists(runtimes_filefullpath_)) - { - fs::remove(runtimes_filefullpath_); - } - std::ofstream out_file(runtimes_filefullpath_.c_str()); - out_file << converged; - out_file << "\n"; - out_file << number_of_run_; - out_file << "\n"; - out_file << label_for_repeat_; - out_file.close(); - } - //=================================================================================================// - template - void RegressionTesting::ResultTesting() - { - /* compare the current result to the converged mean value and variance. */ - size_t test_wrong = 0; - if (i_ < number_of_snapshot_old_) - { - test_wrong = TestingNewResult(difference_, this->current_result_, meanvalue_, variance_); - if (test_wrong == 0) - std::cout << "The result of " << this->quantity_name_ << - " is correct based on the regression testing!" << endl; - else - { - std::cout << "There are " << test_wrong << - " snapshots are not within the expected range." << endl; - std::cout << "Please try again. If it still post this sectence, " - "the result is not correct!" << endl; - } - } - else if (i_ >= number_of_snapshot_old_) - { - for (size_t delete_ = 0; delete_ != difference_; ++delete_) - { - meanvalue_.pop_back(); - variance_.pop_back(); - } - test_wrong = TestingNewResult(0, this->current_result_, meanvalue_, variance_); - if (test_wrong == 0) - std::cout << "The result of " << this->quantity_name_ << - " is correct based on the regression testing!" << endl; - else - { - std::cout << "There are " << test_wrong << - " snapshots are not within the expected range." << endl; - std::cout << "Please try again. If it still post this sectence, " - "the result is not correct!" << endl; - } - } - }; - //=================================================================================================// - template - void RegressionTesting::GetMeanvalueInTime(size_t iteration) - { - VariableType average = { 0 }; - for (size_t i = iteration; i != i_; ++i) - { - average += this->current_result_[i][0]; - } - average = average / (i_ - iteration); - - meanvalue_xml_engine_out_.addElementToXmlDoc("meanvalue"); - SimTK::Xml::element_iterator element_iterator = meanvalue_xml_engine_out_.root_element_.element_begin(); - meanvalue_xml_engine_out_.setAttributeToElement(element_iterator, "AverageViscousForce", average); - meanvalue_xml_engine_out_.writeToXmlFile(meanvalue_filefullpath_); - }; - //=================================================================================================// -} \ No newline at end of file diff --git a/SPHINXsys/src/shared/regression_testing/regression_testing.h b/SPHINXsys/src/shared/regression_testing/regression_testing.h deleted file mode 100644 index 762c33adf1..0000000000 --- a/SPHINXsys/src/shared/regression_testing/regression_testing.h +++ /dev/null @@ -1,33 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** -* @file regression_testing.h -* @brief This is the header file that user code should include to pick up all -* the regression testing method used in SPHinXsys. -* @author Bo Zhang -*/ -#pragma once - -#include "meanvalue_variance_method.h" -#include "meanvalue_variance_method.hpp" - diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/CMakeLists.txt b/SPHINXsys/src/shared/simbody_sphinxsys/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/simbody_sphinxsys/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/all_simbody.h b/SPHINXsys/src/shared/simbody_sphinxsys/all_simbody.h deleted file mode 100644 index ebb061d5a4..0000000000 --- a/SPHINXsys/src/shared/simbody_sphinxsys/all_simbody.h +++ /dev/null @@ -1,14 +0,0 @@ - -#ifndef ALL_SIMBODY_H -#define ALL_SIMBODY_H - - - -/** @file -This is the header file that user code should include to pick up all -interface function to Simbody library. **/ - -#include "xml_engine.h" -#include "state_engine.h" - -#endif //ALL_SIMBODY_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/array.h b/SPHINXsys/src/shared/simbody_sphinxsys/array.h deleted file mode 100644 index 4d5bf35c36..0000000000 --- a/SPHINXsys/src/shared/simbody_sphinxsys/array.h +++ /dev/null @@ -1,648 +0,0 @@ -/** - * @file array.h - * @details An Array toolkit for array operator. - * @author Chi Zhang and Xiangyu Hu. - */ - -#ifndef ARRAY_SIMBODY_H -#define ARRAY_SIMBODY_H - - - -#include "base_data_package.h" - -#include -#include -#include -#include - -static const int Array_CAPMIN = 1; - -namespace SPH { - /** - * @class Array - * @ details A class for storing an array of values of type T. The capacity of the class - * grows as needed. To use this template for a class of type T, class T should - * implement the following methods: default constructor, copy constructor, - * assignment operator (=), equality operator (==), and less than - * operator (<). - */ - template class Array - { - protected: - /** Size of the array. Also the index of the first empty array element. */ - int size_; - /** Current capacity of the array. */ - int capacity_; - /** Increment by which the current capacity is increased when the capacity - of this storage instance is reached. If negative, capacity doubles. */ - int capacityIncrement_; - /** Default value of elements. */ - T defaultValue_; - /** Array of values. */ - T *array_; - public: - /** - * @brief Default constructor. - * - * @param aDefaultValue Default value of an array element. This value - * is used to initialize array elements as the size of the array is - * changed. - * @param aSize Initial size of the array. The array elements are - * initialized to aDefaultValue. - * @param aCapacity Initial capacity of the array. The initial capacity - * is guaranteed to be at least as large as aSize + 1. - */ - explicit Array(const T &aDefaultValue=T(),int aSize=0,int aCapacity=Array_CAPMIN) - { - setNull(); - defaultValue_ = aDefaultValue; - int newCapacity; - int min = aSize + 1; - if(min < aCapacity) min = aCapacity; - computeNewCapacity(min,newCapacity); - ensureCapacity(newCapacity); - size_ = aSize; - if(size_<0) size_=0; - } - /** - * @brief Copy constructor. - * - * @param aArray Array to be copied. - */ - Array(const Array &aArray) - { - setNull(); - *this = aArray; - } - /** - * @brief Destructor. - * - * @details When the array is deleted, references to elements of this array become - * invalid. - */ - virtual ~Array() - { - if(array_!=nullptr) { delete[] array_; array_ = nullptr; } - } - private: - /** - * %Set all member variables to their null or default values. - */ - void setNull() - { - size_ = 0; - capacityIncrement_ = -1; - capacity_ = 0; - array_ = nullptr; - } - //============================================================================= - // OPERATORS - //============================================================================= - public: - /** - * @brief A non-operator version of operator == - */ - bool arrayEquals(const Array &aArray) const - { - return *this == aArray; - } - /** - * @brief Get the array element at a specified index. This overloaded operator - * can be used both to set and get element values: - * @code - * Array array(2); - * T value = array[i]; - * array[i] = value; - * @endcode - * - * @details This operator is intended for accessing array elements with as little - * overhead as possible, so no error checking is performed. - * The caller must make sure the specified index is within the bounds of - * the array. If error checking is desired, use Array::get(). - * - * @param aIndex Index of the desired element (0 <= aIndex < size_). - * @return Reference to the array element. - * @see get(). - */ - T& operator[](int aIndex) const - { - return(array_[aIndex]); - } - /** - * @brief Assign this array to a specified array. - * This operator makes a complete copy of the specified array; all member - * variables are copied. So, the result is two identical, independent arrays. - * - * @param aArray Array to be copied. - * @return Reference to this array. - */ - Array& operator=(const Array &aArray) - { - size_ = aArray.size_; - capacity_ = aArray.capacity_; - capacityIncrement_ = aArray.capacityIncrement_; - defaultValue_ = aArray.defaultValue_; - - // ARRAY - if(array_!=nullptr) delete[] array_; - array_ = new T[capacity_]; - for(int i = 0;i < capacity_; i++) array_[i] = aArray.array_[i]; - - return(*this); - } - /** - * @brief Determine if two arrays are equal. - * - * @details Two arrays are equal if their contents are equal. That is, each array - * must be the same length and their corresponding array elements must be - * equal. - * - * @param aArray Array to be tested as equal. - * @return True if equal, false if not equal. - */ - bool operator==(const Array &aArray) const - { - if(size_ != aArray.size_) return(false); - - int i; - for(i=0; i &aArray) - { - int i; - for(i=0; i>(std::istream& in, Array& out) - { - return in; - } - /** - * @brief Compute a new capacity that is at least as large as a specified minimum - * capacity; this method does not change the capacity, it simply computes - * a new recommended capacity. - * - * @details If the capacity increment is negative, the current capacity is - * doubled until the computed capacity is greater than or equal to the - * specified minimum capacity. If the capacity increment is positive, the - * current capacity increment by this amount until the computed capacity is - * greater than or equal to the specified minimum capacity. If the capacity - * increment is zero, the computed capacity is set to the current capacity - * and false is returned. - * - * @param rNewCapacity New computed capacity. - * @param aMinCapacity Minimum new computed capacity. The computed capacity - * is incremented until it is at least as large as aMinCapacity, assuming - * the capacity increment is not zero. - * @return True if the new capacity was increased, false otherwise (i.e., - * if the capacity increment is set to 0). - * @see setCapacityIncrement() - */ - bool computeNewCapacity(int aMinCapacity,int &rNewCapacity) - { - rNewCapacity = capacity_; - if(rNewCapacity < Array_CAPMIN) rNewCapacity = Array_CAPMIN; - - if(capacityIncrement_ == 0) { - std::cout << "Array.computeNewCapacity: WARN- capacity is set"; - std::cout << " not to increase (i.e., capacityIncrement_==0).\n"; - return(false); - } - - while(rNewCapacity < aMinCapacity) { - if(capacityIncrement_ < 0) { - rNewCapacity = 2 * rNewCapacity; - } else { - rNewCapacity = rNewCapacity + capacityIncrement_; - } - } - - return(true); - } - /** - * @brief Ensure that the capacity of this array is at least the specified amount. - * Note that the newly allocated array elements are not initialized. - * - * @param aCapacity Desired capacity. - * @return true if the capacity was successfully obtained, false otherwise. - */ - bool ensureCapacity(int aCapacity) - { - if(aCapacity < Array_CAPMIN) aCapacity = Array_CAPMIN; - if(capacity_ >= aCapacity) return(true); - - int i; - T *newArray = new T[aCapacity]; - if(newArray == nullptr) - { - std::cout << "Array.ensureCapacity: ERR- failed to increase capacity.\n"; - return(false); - } - - if(array_ != nullptr) { - for(i =0; i < size_; i++) newArray[i] = array_[i]; - for(i = size_; i < aCapacity; i++) newArray[i] = defaultValue_; - delete []array_; - array_ = nullptr; - } else { - for(i = 0; i < aCapacity; i++) newArray[i] = defaultValue_; - } - - capacity_ = aCapacity; - array_ = newArray; - - return(true); - } - - /** - * @details Trim the capacity of this array so that it is one larger than the size - * of this array. This is useful for reducing the amount of memory used - * by this array. This capacity is kept at one larger than the size so - * that, for example, an array of characters can be treated as a nullptr - * terminated string. - */ - void trim() - { - int newCapacity = size_ + 1; - if(newCapacity >= capacity_) return; - if(newCapacity < Array_CAPMIN) newCapacity = Array_CAPMIN; - - int i; - T *newArray = new T[newCapacity]; - if(newArray==nullptr) { - std::cout << "Array.trim: ERR- unable to allocate temporary array.\n"; - return; - } - - for(i = 0; i < size_; i++) newArray[i] = array_[i]; - - delete[] array_; - - array_ = newArray; - - capacity_ = newCapacity; - } - /** - * Get the capacity of this storage instance. - */ - int getCapacity() const - { - - return(capacity_); - } - /** - * @brief Set the amount by which the capacity is increased when the capacity of - * of the array in exceeded. - * @details If the specified increment is negative, the capacity is set to double - * whenever the capacity is exceeded. - * - * @param aIncrement Desired capacity increment. - */ - void setCapacityIncrement(int aIncrement) - { - capacityIncrement_ = aIncrement; - } - - /** - * @brief Get the amount by which the capacity is increased. - */ - int getCapacityIncrement() const - { - return(capacityIncrement_); - } - /** - * @details Set the size of the array. This method can be used to either increase - * or decrease the size of the array. If this size of the array is - * increased, the new elements are initialized to the default value - * that was specified at the time of construction. - * - * @details Note that the size of an array is different than its capacity. The size - * indicates how many valid elements are stored in an array. The capacity - * indicates how much the size of the array can be increased without - * allocated more memory. At all times size <= capacity. - * - * @param aSize Desired size of the array. The size must be greater than - * or equal to zero. - */ - bool setSize(int aSize) - { - if(aSize == size_) return(true); - if(aSize < 0) aSize = 0; - bool success = true; - if(aSize < size_) - { - int i; - for(i = (size_ - 1);i >= aSize; i--) array_[i] = defaultValue_; - size_ = aSize; - } else if(aSize <= capacity_) { - size_ = aSize; - } else { - int newCapacity; - success = computeNewCapacity(aSize+1, newCapacity); - if(!success) return(false); - success = ensureCapacity(newCapacity); - if(success) size_ = aSize; - } - - return(success); - } - /** - * @brief Get the size of the array. - * - * @return Size of the array. - */ - int getSize() const - { - return(size_); - } - /** Alternate name for getSize(). **/ - int size() const {return getSize();} - - /** - * @brief Append a value onto the array. - * - * @param aValue Value to be appended. - * @return New size of the array, or, equivalently, the index to the new - * first empty element of the array. - */ - int append(const T &aValue) - { - if((size_ + 1) >= capacity_) { - int newCapacity; - bool success; - success = computeNewCapacity(size_ + 1, newCapacity); - if(!success) return(size_); - success = ensureCapacity(newCapacity); - if(!success) return(size_); - } - - array_[size_] = aValue; - size_++; - - return(size_); - } - /** - * @brief Append an array of values. - * - * @param aArray Array of values to append. - * @return New size of the array, or, equivalently, the index to the new - * first empty element of the array. - */ - int append(const Array &aArray) - { - int i,n = aArray.getSize(); - for(i = 0; i < n; i++) { - append(aArray[i]); - } - - return(size_); - } - - /** - * @brief Append an array of values. - * - * @param aSize Size of the array to append. - * @param aArray Array of values to append. - * @return New size of the array, or, equivalently, the index to the new - * first empty element of the array. - */ - int append(int aSize,const T *aArray) - { - if(aSize < 0) return(size_); - if(aArray == nullptr) return(size_); - - int i; - for(i = 0;i < aSize; i++) { - append(aArray[i]); - } - - return(size_); - } - /** - * @brief Insert a value into the array at a specified index. - * - * @details This method is relatively computationally costly since many of the array - * elements may need to be shifted. - * - * @param aValue Value to be inserted. - * @param aIndex Index at which to insert the new value. All current elements - * from aIndex to the end of the array are shifted one place in the direction - * of the end of the array. If the specified index is greater than the - * current size of the array, the size of the array is increased to aIndex+1 - * and the intervening new elements are initialized to the default value that - * was specified at the time of construction. - * @return Size of the array after the insertion. - */ - int insert(int aIndex,const T &aValue) - { - if(aIndex<0) { - std::cout << "Array.insert: ERR- aIndex was less than 0.\n"; - return(size_); - } - - if(aIndex >= size_) { - setSize(aIndex+1); - array_[aIndex] = aValue; - return(size_); - } - - if((size_ + 1) >= capacity_) { - int newCapacity; - bool success; - success = computeNewCapacity(size_ + 1,newCapacity); - if(!success) return(size_); - success = ensureCapacity(newCapacity); - if(!success) return(size_); - } - - int i; - for(i = size_; i > aIndex; i--) { - array_[i] = array_[i-1]; - } - - array_[aIndex] = aValue; - size_++; - - return(size_); - } - /** - * @brief Remove a value from the array at a specified index. - * - * @details This method is relatively computationally costly since many of the array - * elements may need to be shifted. - * - * @param aIndex Index of the value to remove. All elements from aIndex to - * the end of the array are shifted one place toward the beginning of - * the array. If aIndex is less than 0 or greater than or equal to the - * current size of the array, no element is removed. - * @return Size of the array after the removal. - */ - int remove(int aIndex) - { - if(aIndex < 0) { - std::cout << "Array.remove: ERR- aIndex was less than 0.\n"; - return(size_); - } - if(aIndex >= size_) { - std::cout << "Array.remove: ERR- aIndex was greater than or equal the "; - std::cout << "size of the array.\n"; - return(size_); - } - - int i; - size_--; - for(i = aIndex; i < size_; i++) { - array_[i] = array_[i+1]; - } - array_[size_] = defaultValue_; - - return(size_); - } - /** - * @brief Set the value at a specified index. - * - * @param aIndex Index of the array element to be set. It is permissible - * for aIndex to be past the current end of the array- the capacity will - * be increased if necessary. Values between the current end of the array - * and aIndex are not initialized. - * @param aValue Value. - */ - void set(int aIndex,const T &aValue) - { - if(aIndex < 0) return; - - bool success = false; - if((aIndex+2) >= capacity_) { - int newCapacity; - success = computeNewCapacity(aIndex+2, newCapacity); - if(!success) return; - success = ensureCapacity(newCapacity); - if(!success) return; - } - array_[aIndex] = aValue; - if(aIndex >= size_) size_ = aIndex + 1; - } - /** - * @brief Get a pointer to the low-level array. - * - * @return Vecder to the low-level array. - */ - T* get() - { - return(array_); - } - /** - * @brief Get a pointer to the low-level array. - * - * @return Vecder to the low-level array. - */ - - const T* get() const - { - return(array_); - } - /** - * @brief Get a const reference to the value at a specified array index. - * - * @details If the index is negative or passed the end of the array, an exception - * is thrown. - * - * For faster execution, the array elements can be accessed through the - * overloaded operator[], which does no bounds checking. - * - * @param aIndex Index of the desired array element. - * @return const reference to the array element. - * @throws Exception if (aIndex<0)||(aIndex>=size_). - * @see operator[]. - */ - const T& get(int aIndex) const - { - if((aIndex < 0) || (aIndex >= size_)) { - std::stringstream msg; - msg << "Array index out of bounds. " << "."; - throw (msg.str(),__FILE__,__LINE__); - } - return(array_[aIndex]); - } - /** - * Get the last value in the array. - * - * @return Last value in the array. - * @throws Exception if the array is empty. - */ - const T& getLast() const - { - if(size_ <= 0) { - std::stringstream msg; - msg << "Array is empty. " << "."; - throw (msg.str(),__FILE__,__LINE__); - } - return(array_[size_ - 1]); - } - /** - * Get writable reference to last value in the array. - * - * @return writable reference to Last value in the array. - * @throws Exception if the array is empty. - */ - T& updLast() const - { - if(size_ <= 0) { - std::stringstream msg; - msg << "Array is empty. " << "."; - throw (msg.str(),__FILE__,__LINE__); - } - return(array_[size_ - 1]); - } - /** - * Linear search for an element matching a given value. - * - * @param aValue Value to which the array elements are compared. - * @return Index of the array element matching aValue. If there is more than - * one such elements with the same value the index of the first of these elements - * is returned. If no match is found, -1 is returned. - */ - int findIndex(const T &aValue) const - { - for(int i = 0; i < size_; i++) if(array_[i] == aValue) return i; - return -1; - } - - /** - * Linear search in reverse for an element matching a given value. - * - * @param aValue Value to which the array elements are compared. - * @return Index of the array element matching aValue. If there is more than - * one such elements with the same value the index of the last of these elements - * is returned. If no match is found, -1 is returned. - */ - int rfindIndex(const T &aValue) const - { - for(int i=size_ - 1; i >= 0; i--) if(array_[i]==aValue) return i; - return -1; - } - }; /** ended of class Array. */ - -} -#endif //ARRAY_SIMBODY_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/exception.cpp b/SPHINXsys/src/shared/simbody_sphinxsys/exception.cpp deleted file mode 100644 index 0d564cace5..0000000000 --- a/SPHINXsys/src/shared/simbody_sphinxsys/exception.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/** - * @file exception.cpp - * @author Chi Zhang and Xiangyu Hu - */ -#include "exception.h" - -namespace SPH { - //===============================================================// - Exception::Exception(const std::string &aMsg, const std::string &aFile, int aLine): - exception() - { - setNull(); - - setMessage(aMsg); - file_ = aFile; - line_ = aLine; - } - //===============================================================// - namespace { - std::string findFileName(const std::string& filepath) - { - std::string::size_type pos = filepath.find_last_of("/\\"); - if (pos + 1 >= filepath.size()) pos = 0; - return filepath.substr(pos + 1); - } - } - //===============================================================// - Exception::Exception(const std::string& file, size_t line, - const std::string& func) - { - addMessage("\tThrown at " + findFileName(file) + ":" + - std::to_string(line) + " in " + func + "()."); - } - //===============================================================// - Exception::Exception(const std::string& file, size_t line, - const std::string& func, const std::string& msg) - : Exception{file, line, func} - { - addMessage(msg); - } - //===============================================================// - void Exception::addMessage(const std::string& msg) - { - if(msg_.length() == 0) - msg_ = msg; - else - msg_ = msg + "\n" + msg_; - } - //===============================================================// - const char* Exception::what() const noexcept - { - return getMessage(); - } - //===============================================================// - void Exception::setNull() - { - setMessage(""); - line_ = -1; - } - //===============================================================// - void Exception::setMessage(const std::string &aMsg) - { - msg_ = aMsg; - } - //===============================================================// - const char* Exception::getMessage() const - { - return(msg_.c_str()); - } - //===============================================================// - void Exception::print(std::ostream &aOut) const - { - aOut << "\nException:\n"; - - if(file_.size()>0) { - aOut << " file= " << file_ << '\n'; - } - - if(line_ >= 0) { - aOut << " line= " << line_ << '\n'; - } - aOut << '\n' << std::endl; - } -}/** End of namespcae */ \ No newline at end of file diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/exception.h b/SPHINXsys/src/shared/simbody_sphinxsys/exception.h deleted file mode 100644 index 3a747f2c51..0000000000 --- a/SPHINXsys/src/shared/simbody_sphinxsys/exception.h +++ /dev/null @@ -1,142 +0,0 @@ -/** - * @file exception.h - * @details A toolkit for exception functionality. - * @author Chi Zhang and Xiangyu Hu. - */ - -#ifndef EXCEPTION_SIMBODY_H -#define EXCEPTION_SIMBODY_H - - - -#include "base_data_package.h" - -#include -#include -#include -#include -#include - -namespace SPH { - /** - * @class Exception - * @brief A class for basic exception functionality. - * @details Exception class manages the concatenation of error messages from all the - * derived classes. When creating new exceptions, remember to call addMessage() - * as shown above if the exception class does have any error message. - */ - class Exception : public std::exception { - private: - std::string msg_; /**< A user set message for the exception. */ - std::string file_; /**< File in which the error occurred. */ - int line_; /**< Line number at which the error occurred. */ -public: - /** - * @brief Default Constructor - * @details This constructor is for backward compatibility. Use the constructor - * taking file, line, func. - */ - Exception(const std::string &aMsg="", const std::string &aFile="", int aLine=-1); - - /** - * @brief Call this constructor from derived classes to add file, line and - * function information to the error message. Use this when throwing - * Derived classes. Use OPENSIM_THROW_<> macros at throw sites. - */ - Exception(const std::string& file, size_t line, const std::string& func); - /** - * @brief Use this when you want to throw an Exception and also provide a message. - */ - Exception(const std::string& file, size_t line, - const std::string& func, const std::string& msg); - /** - * @ brief Destructor. - */ - virtual ~Exception() throw() {} - - protected: - /** Add to the error message that will be returned for the exception. */ - void addMessage(const std::string& msg); - - private: - void setNull(); - - public: - void setMessage(const std::string &aMsg); - const char* getMessage() const; - - virtual void print(std::ostream &aOut) const; - - const char* what() const noexcept override; - }; - - class InvalidArgument : public Exception { - public: - InvalidArgument(const std::string& file, - size_t line, - const std::string& func, - const std::string& msg = "") : - Exception(file, line, func) { - std::string mesg = "Invalid Argument. " + msg; - - addMessage(mesg); - } - }; - - class InvalidCall : public Exception { - public: - InvalidCall(const std::string& file, - size_t line, - const std::string& func, - const std::string& msg = "") : - Exception(file, line, func) { - std::string mesg = "Invalid Call. " + msg; - - addMessage(mesg); - } - }; - - class InvalidTemplateArgument : public Exception { - public: - InvalidTemplateArgument(const std::string& file, - size_t line, - const std::string& func, - const std::string& msg) : - Exception(file, line, func) { - std::string mesg = "Invalid Template argument. " + msg; - - addMessage(mesg); - } - }; - - class IndexOutOfRange : public Exception { - public: - IndexOutOfRange(const std::string& file, - size_t line, - const std::string& func, - size_t index, - size_t min, - size_t max) : - Exception(file, line, func) { - std::string msg = "min = " + std::to_string(min); - msg += " max = " + std::to_string(max); - msg += " index = " + std::to_string(index); - - addMessage(msg); - } - }; - - class KeyNotFound : public Exception { - public: - KeyNotFound(const std::string& file, - size_t line, - const std::string& func, - const std::string& key) : - Exception(file, line, func) { - std::string msg = "Key '" + key + "' not found."; - - addMessage(msg); - } - }; -}/** end of namespace */ -#endif //EXCEPTION_SIMBODY_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/simbody_middle.h b/SPHINXsys/src/shared/simbody_sphinxsys/simbody_middle.h deleted file mode 100644 index c08b07f584..0000000000 --- a/SPHINXsys/src/shared/simbody_sphinxsys/simbody_middle.h +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @file simbody_middle.h - * @brief file to include Simbody headers and suppress their warnings - * @author Wen-Yang Chu - */ - -#ifndef SIMBODY_MIDDLE_H -#define SIMBODY_MIDDLE_H - -#ifdef __linux__ -#pragma GCC system_header //for GCC/CLANG -#elif _WIN32 -#pragma warning(push, 0) //for MSVC -#endif - -#include "SimTKcommon.h" -#include "SimTKmath.h" -#include "Simbody.h" - -#ifdef _WIN32 -#pragma warning(pop) //for MSVC -#endif -#endif //SIMBODY_MIDDLE_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/state_engine.cpp b/SPHINXsys/src/shared/simbody_sphinxsys/state_engine.cpp deleted file mode 100644 index 6ba45f2526..0000000000 --- a/SPHINXsys/src/shared/simbody_sphinxsys/state_engine.cpp +++ /dev/null @@ -1,363 +0,0 @@ -/** - * @file statengine.cpp - * @brief engine of state functions are defined here - * @author Chi Zhang and Xiangyu Hu - */ - -#include "state_engine.h" - -namespace SPH { - //===============================================================// - StateEngine:: - StateEngine(SimTK::MultibodySystem& system) : - simbody_xml_engine_("state_xml", "mbsystem") - { - mbsystem_ = system; - restart_folder_ = "./rstfile"; - if (!fs::exists(restart_folder_)) - { - fs::create_directory(restart_folder_); - } - } - //===============================================================// - void StateEngine::InitializeState() - { - /** Clear cached list of all related - StateVariables if any from a previousSystem. - */ - allstatevariables_.clear(); - getMultibodySystem().invalidateSystemTopologyCache(); - getMultibodySystem().realizeTopology(); - /** Set the model's operating state (internal member variable) to the - default state that is stored inside the System. - */ - working_state_ = getMultibodySystem().getDefaultState(); - /** Process the modified modeling option. */ - getMultibodySystem().realizeModel(working_state_); - /** Realize instance variables that may have been set above. This - * means floating point parameters such as mass properties and - * geometry placements are frozen. - */ - getMultibodySystem().realize(working_state_, SimTK::Stage::Instance); - /** Realize the initial configuration in preparation. This - * initial configuration does not necessarily satisfy constraints. - */ - getMultibodySystem().realize(working_state_, SimTK::Stage::Position); - } - //===============================================================// - SimTK::MultibodySystem& StateEngine::getMultibodySystem() - { - return mbsystem_.getRef(); - } - //===============================================================// - void StateEngine::addStateVariable(std::string statevariablename, - SimTK::Stage invalidatestage) - { - if ((invalidatestage < SimTK::Stage::Position) || - (invalidatestage > SimTK::Stage::Dynamics)) - { - std::stringstream msg; - msg << "StateEngine::addStateVariable: invalidatestage " - "must be Position, Velocity or Dynamics. " << __FILE__ << __LINE__; - throw (msg.str()); - } - /** Allocate space for a new state variable. */ - AddedStateVariable* asv = - new AddedStateVariable(statevariablename, *this, invalidatestage); - // Add it to the Component and let it take ownership - addStateVariable(asv); - } - //===============================================================// - void StateEngine::addStateVariable(StateEngine::StateVariable* statevariable) - { - std::string& statevariablename = statevariable->getName(); - /** don't add state if there is another state variable with the same name. */ - std::map::const_iterator it; - it = namedstatevariableinfo_.find(statevariablename); - if (it != namedstatevariableinfo_.end()) - { - std::stringstream msg; - msg << "StateEngine::addStateVariable: State variable " << - statevariablename << " already exists." << __FILE__ << __LINE__; - throw (msg.str()); - } - int order = (int)namedstatevariableinfo_.size(); - /** assign a "slot" for a state variable by name - state variable index will be invalid by default - upon allocation during realizeTopology the index will be set - */ - namedstatevariableinfo_[statevariablename] = StateVariableInfo(statevariable, order); - - } - //===============================================================// - StateEngine::StateVariable* StateEngine:: - traverseToStateVariable(std::string& pathname) - { - auto it = namedstatevariableinfo_.find(pathname); - if (it != namedstatevariableinfo_.end()) - { - return it->second.statevariable_.get(); - } - else { - return nullptr; - } - } - //===============================================================//. - Array StateEngine::getStateVariableNames() - { - std::map::const_iterator it; - it = namedstatevariableinfo_.begin(); - - Array names;//("",(int)namedstatevariableinfo_.size()); - - while (it != namedstatevariableinfo_.end()) - { - names[it->second.order] = it->first; - it++; - } - return names; - } - //===============================================================// - int StateEngine::getNumOfStateVariables() - { - return getNumStateVariablesAddedByEngine(); - } - //===============================================================// - bool StateEngine::isAllStatesVariablesListValid() - { - int nsv = getNumOfStateVariables(); - /** Consider the list of all StateVariables to be valid if all of - the following conditions are true: - 1. a System has been associated with the list of StateVariables - 2. The list of all StateVariables is correctly sized (initialized) - 3. The System associated with the StateVariables is the current System */ - bool valid = - !statesassociatedsystem_.empty() && - (int)allstatevariables_.size() == nsv && - getMultibodySystem().isSameSystem(statesassociatedsystem_.getRef()); - - return valid; - } - //===============================================================// - SimTK::Vector StateEngine::getStateVariableValues() - { - int nsv = getNumOfStateVariables(); - /** if the StateVariables are invalid, rebuild the list. */ - if (!isAllStatesVariablesListValid()) - { - statesassociatedsystem_.reset(&getMultibodySystem()); - allstatevariables_.clear(); - allstatevariables_.resize(nsv); - Array names = getStateVariableNames(); - for (int i = 0; i < nsv; ++i) - allstatevariables_[i].reset(traverseToStateVariable(names[i])); - } - - SimTK::Vector statevariablevalues(nsv, SimTK::NaN); - for (int i = 0; i < nsv; ++i) { - statevariablevalues[i] = allstatevariables_[i]->getValue(); - std::cout << statevariablevalues[i] << std::endl; - } - return statevariablevalues; - } - //-----------------------------------------------------------------------------// - // OTHER REALIZE METHODS - //-----------------------------------------------------------------------------// - /** override virtual methods. */ - Real StateEngine::AddedStateVariable::getValue() - { - SimTK::ZIndex zix(getVarIndex()); - if (getSubsysIndex().isValid() && zix.isValid()) { - const SimTK::Vector& z = getOwner().getDefaultSubsystem().getZ(getOwner().working_state_); - return z[SimTK::ZIndex(zix)]; - } - - std::stringstream msg; - msg << "StateEngine::AddedStateVariable::getValue: ERR- variable '" - << getName() << "' is invalid! " << __FILE__ << __LINE__; - throw (msg.str()); - return SimTK::NaN; - } - //===============================================================// - void StateEngine::AddedStateVariable::setValue(Real value) - { - SimTK::ZIndex zix(getVarIndex()); - if (getSubsysIndex().isValid() && zix.isValid()) { - SimTK::Vector& z = getOwner().getDefaultSubsystem().updZ(getOwner().working_state_); - z[SimTK::ZIndex(zix)] = value; - return; - } - - std::stringstream msg; - msg << "StateEngine::AddedStateVariable::setValue: ERR- variable '" - << getName() << "' is invalid! " << __FILE__ << __LINE__;; - throw (msg.str()); - } - //===============================================================// - double StateEngine::AddedStateVariable:: - getDerivative() - { - //return getCacheVariableValue(state, getName()+"_deriv"); - return 0.0; - } - //===============================================================// - void StateEngine::AddedStateVariable:: - setDerivative(Real deriv) - { - //return setCacheVariableValue(state, getName()+"_deriv", deriv); - } - //===============================================================// - void StateEngine::reporter(SimTK::State& state_) - { - const SimTK::SimbodyMatterSubsystem& matter_ = getMultibodySystem().getMatterSubsystem(); - for (SimTK::MobilizedBodyIndex mbx(0); mbx < matter_.getNumBodies(); ++mbx) - { - - const SimTK::MobilizedBody& mobod = matter_.getMobilizedBody(mbx); - - int num_q_ = mobod.getNumQ(state_); - for (int i = 0; i < num_q_; i++) - { - std::cout << num_q_ << " " << mobod.getOneQ(state_, SimTK::QIndex(i)) << std::endl; - } - int num_u_ = mobod.getNumU(state_); - for (int i = 0; i < num_u_; i++) - { - std::cout << num_u_ << " " << mobod.getOneU(state_, SimTK::UIndex(i)) << std::endl; - } - std::cout << " Body Info : " << std::endl; - std::cout << " Transform : " << mobod.getBodyTransform(state_) << std::endl; - std::cout << " Rotation : " << mobod.getBodyRotation(state_) << std::endl; - std::cout << " Origin : " << mobod.getBodyOriginLocation(state_) << std::endl; - } - } - //===============================================================// - void StateEngine::resizeXmlDocForSimbody(size_t input_size) - { - size_t total_elements = simbody_xml_engine_.SizeOfXmlDoc(); - - if (total_elements <= input_size) - { - for (size_t i = total_elements; i != input_size; ++i) - simbody_xml_engine_.addElementToXmlDoc("mbbody"); - } - } - //===============================================================// - void StateEngine::writeStateInfoToXml(int ite_rst_, const SimTK::State& state_) - { - const SimTK::SimbodyMatterSubsystem& matter_ = getMultibodySystem().getMatterSubsystem(); - resizeXmlDocForSimbody(matter_.getNumBodies()); - SimTK::Xml::element_iterator ele_ite = simbody_xml_engine_.root_element_.element_begin(); - SimTK::MobilizedBodyIndex mbx(0); - for (int k = 0; k != matter_.getNumBodies(); ++k) - { - const SimTK::MobilizedBody& mobod = matter_.getMobilizedBody(mbx); - - int num_q_ = mobod.getNumQ(state_); - for (int i = 0; i < num_q_; i++) - { - Real mobod_q = mobod.getOneQ(state_, SimTK::QIndex(i)); - std::string ele_name = "QIndx_" + std::to_string(i); - simbody_xml_engine_.setAttributeToElement(ele_ite, ele_name, mobod_q); - } - - int num_u_ = mobod.getNumU(state_); - for (int i = 0; i < num_u_; i++) - { - Real mobod_u = mobod.getOneU(state_, SimTK::UIndex(i)); - std::string ele_name = "UIndx_" + std::to_string(i); - simbody_xml_engine_.setAttributeToElement(ele_ite, ele_name, mobod_u); - } - Vec3d transform_ = mobod.getBodyTransform(state_).p(); - simbody_xml_engine_.setAttributeToElement(ele_ite, "Transform", transform_); - - ++ele_ite; - } - std::string filefullpath = restart_folder_ + "/Simbody_Rst_" + std::to_string(ite_rst_) + ".xml"; - simbody_xml_engine_.writeToXmlFile(filefullpath); - } - //===============================================================// - SimTK::State StateEngine::readAndSetStateInfoFromXml(int ite_rst_, SimTK::MultibodySystem& system_) - { - std::string filefullpath = restart_folder_ + "/Simbody_Rst_" + std::to_string(ite_rst_) + ".xml"; - const SimTK::SimbodyMatterSubsystem& matter_ = system_.getMatterSubsystem(); - SimTK::State state_ = system_.getDefaultState(); - if (!fs::exists(filefullpath)) - { - std::cout << "\n Error: the input file:" << filefullpath << " is not valid" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - else { - int num_mobod = 0; - simbody_xml_engine_.loadXmlFile(filefullpath); - SimTK::Xml::element_iterator ele_ite_ = simbody_xml_engine_.root_element_.element_begin(); - for (; ele_ite_ != simbody_xml_engine_.root_element_.element_end(); ++ele_ite_) - { - const SimTK::MobilizedBody& mobod = matter_.getMobilizedBody(SimTK::MobilizedBodyIndex(num_mobod)); - int num_q_ = mobod.getNumQ(state_); - Real q_tmp_ = 0.0; - if (num_q_ != 0) - { - for (int i = 0; i < num_q_; i++) - { - std::string attr_name = "QIndx_" + std::to_string(i); - simbody_xml_engine_.getRequiredAttributeValue(ele_ite_, attr_name, q_tmp_); - mobod.setOneQ(state_, SimTK::QIndex(i), q_tmp_); - } - } - int num_u_ = mobod.getNumU(state_); - Real u_tmp_ = 0.0; - if (num_u_ != 0) - { - for (int i = 0; i < num_u_; i++) - { - std::string attr_name = "UIndx_" + std::to_string(i); - simbody_xml_engine_.getRequiredAttributeValue(ele_ite_, attr_name, u_tmp_); - mobod.setOneU(state_, SimTK::UIndex(i), u_tmp_); - } - } - Vec3d transform_(0); - simbody_xml_engine_.getRequiredAttributeValue(ele_ite_, "Transform", transform_); - mobod.setQToFitTransform(state_, SimTK::Transform(transform_)); - - num_mobod++; - } - } - system_.realizeModel(state_); - return state_; - } - //------------------------------------------------------------------------------ - // REALIZE THE SYSTEM TO THE REQUIRED COMPUTATIONAL STAGE - //------------------------------------------------------------------------------ - void StateEngine::realizeTime() - { - getMultibodySystem().realize(working_state_, SimTK::Stage::Time); - } - //===============================================================// - void StateEngine::realizePosition() - { - getMultibodySystem().realize(working_state_, SimTK::Stage::Position); - } - //===============================================================// - void StateEngine::realizeVelocity() - { - getMultibodySystem().realize(working_state_, SimTK::Stage::Velocity); - } - //===============================================================// - void StateEngine::realizeDynamics() - { - getMultibodySystem().realize(working_state_, SimTK::Stage::Dynamics); - } - //===============================================================// - void StateEngine::realizeAcceleration() - { - getMultibodySystem().realize(working_state_, SimTK::Stage::Acceleration); - } - //===============================================================// - void StateEngine::realizeReport() - { - getMultibodySystem().realize(working_state_, SimTK::Stage::Report); - } - //===============================================================// -} diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/state_engine.h b/SPHINXsys/src/shared/simbody_sphinxsys/state_engine.h deleted file mode 100644 index 8ba6cdcfe4..0000000000 --- a/SPHINXsys/src/shared/simbody_sphinxsys/state_engine.h +++ /dev/null @@ -1,380 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file state_engine.h - * @details The StateEngine class defines the interface used to add computational - * elements to the underlying SimTK::System (MultibodySystem). It specifies - * the interface that simbodystates must satisfy in order to be part of the system - * and provides a series of helper methods for adding variables - * (state, discrete, cache, ...) to the underlying system. As such, SimbodyState - * handles all of the bookkeeping of system indices and provides convenience - * access to variable values (incl. derivatives) via their names as strings. - * @author Chi Zhang and Xiangyu Hu. - */ - -#ifndef STATE_ENGINE_SIMBODY_H -#define STATE_ENGINE_SIMBODY_H - - -#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING - -#include "base_data_package.h" -#include "array.h" -#include "exception.h" -#include "all_simbody.h" - -#include -#include -#include -#include - -#include "Simbody.h" -#include "SimTKmath.h" -#include "SimTKcommon.h" -#include "simbody/internal/MultibodySystem.h" - -namespace SPH { - /** - * @class StateEngine - */ - class StateEngine { - //===============================================================// - // PROTECTED - //===============================================================// - protected: - - XmlEngine simbody_xml_engine_; - - void resizeXmlDocForSimbody(size_t input_size); - - /** - * @class StateVariable - * @details Derived simbodysate must create concrete StateVariables to expose their state - * variables. When exposing state variables allocated by the underlying Simbody - * (MobilizedBody, Constraint, Force, etc...) use its interface to - * implement the virtual methods below. - */ - class StateVariable { - //friend void StateEngine::addStateVariable(StateVariable* sv); - public: - /** Concstructor and destructor. */ - StateVariable() :name_(""), owner_(nullptr), - subsysindex_(SimTK::InvalidIndex), - varindex_(SimTK::InvalidIndex), - sysyindex_(SimTK::InvalidIndex) {} - explicit StateVariable(std::string& name, /**< state var name. */ - StateEngine& owner, /**< owning component. */ - SimTK::SubsystemIndex subsys, /**< subsystem for allocation. */ - int varindex) /**< variable's index in subsystem.*/ - : name_(name), owner_(&owner), subsysindex_(subsys), - varindex_(varindex), sysyindex_(SimTK::InvalidIndex) {} - virtual ~StateVariable() {} - - std::string& getName() { return name_; } - StateEngine& getOwner() { return *owner_; } - /** Get the index of simbody state variable. */ - int& getVarIndex() { return varindex_; } - /** Return the index of the subsystem used to make resource allocations. */ - SimTK::SubsystemIndex& getSubsysIndex() { return subsysindex_; } - /** Return the index in the global list of continuous state variables, Y. */ - SimTK::SystemYIndex& getSystemYIndex() { return sysyindex_; } - /** Set the index of simbody variable. */ - void setVarIndex(int index) { varindex_ = index; } - /** Set the index of the subsystem used to resource allocations. */ - void setSubsystemIndex(SimTK::SubsystemIndex& subsysindx) - { - subsysindex_ = subsysindx; - } - /** Concrete StateEngines implement how the state variable value is evaluated. */ - virtual Real getValue() = 0; - virtual void setValue(Real value) = 0; - virtual Real getDerivative() = 0; - /** The derivative a state should be a cache entry and thus does not change the state. */ - virtual void setDerivative(Real deriv) = 0; - private: - std::string name_; - SimTK::ReferencePtr owner_; - /** - * Identify which subsystem this state variable belongs to, which should - * be determined and set at creation time - */ - SimTK::SubsystemIndex subsysindex_; - /** - * The local variable index in the subsystem also provided at creation - * (e.g. can be QIndex, UIndex, or Zindex type) - */ - int varindex_; - /** - * Once allocated a state in the system will have a global index - * and that can be stored here as well - */ - SimTK::SystemYIndex sysyindex_; - }; - //===============================================================// - // PRIVATE - //===============================================================// - /** - * @class AddedStateVariable - * @brief Class for handling state variable added (allocated) by this StateEngine. - */ - class AddedStateVariable : public StateVariable { - public: - /** Constructors adn destrucutors. */ - AddedStateVariable() : StateVariable(), invalidatestage_(SimTK::Stage::Empty) {} - - /** Convenience constructor for defining a StateEngine added state variable */ - explicit AddedStateVariable(std::string& name, /**< state var name. */ - StateEngine& owner, - SimTK::Stage invalidatestage) : /**< stage this variable invalidates. */ - StateVariable(name, owner, - SimTK::SubsystemIndex(SimTK::InvalidIndex), - SimTK::InvalidIndex), - invalidatestage_(SimTK::Stage::Empty) {} - - /** override virtual methods. */ - Real getValue() override; - void setValue(Real value) override; - Real getDerivative() override; - void setDerivative(Real deriv) override; - - private: - /** Changes in state variables trigger recalculation of appropriate cache - * variables by automatically invalidating the realization stage specified - * upon allocation of the state variable. - */ - SimTK::Stage invalidatestage_; - }; - /** - * @struct StateVariableInfo - * @brief To hold related info about discrete variables. - */ - struct StateVariableInfo { - StateVariableInfo() {} - explicit StateVariableInfo(StateEngine::StateVariable* sv, int order) : - statevariable_(sv), order(order) {} - - /** Need empty copy constructor because default compiler generated - will fail since it cannot copy a unique_ptr. */ - StateVariableInfo(const StateVariableInfo&) {} - /** Now handle assignment by moving ownership of the unique pointer. */ - StateVariableInfo& operator=(const StateVariableInfo& svi) { - if (this != &svi) { - /** assignment has to be const but cannot swap const - want to keep unique pointer to guarantee no multiple reference - so use const_cast to swap under the covers. */ - StateVariableInfo* mutableSvi = const_cast(&svi); - statevariable_.swap(mutableSvi->statevariable_); - } - order = svi.order; - return *this; - } - - // State variable - std::unique_ptr statevariable_; - // order of allocation - int order; - }; - //===============================================================// - // PULIC - //===============================================================// - public: - - /** Add a continuous system state variable belonging to this Engine, - and assign a name by which to refer to it. Changing the value of this state - variable will automatically invalidate everything at and above its - \a invalidatesStage, which is normally Stage::Dynamics meaning that there - are forces that depend on this variable. If you define one or more - of these variables you must also override computeStateVariableDerivatives() - to provide time derivatives for them. Note, all corresponding system - indices are automatically determined using this interface. As an advanced - option you may choose to hide the state variable from being accessed outside - of this component, in which case it is considered to be "hidden". - You may also want to create an Output for this state variable; see - #OpenSim_DECLARE_OUTPUT_FOR_STATE_VARIABLE for more information. Reporters - should use such an Output to get the StateVariable's value (instead of using - getStateVariableValue()). - - @param[in] stateVariableName string value to access variable by name - @param[in] invalidatesStage the system realization stage that is - invalidated when variable value is changed - @param[in] isHidden flag (bool) to optionally hide this state - variable from being accessed outside this - component as an Output - */ - void addStateVariable(std::string statevariablename, - SimTK::Stage invalidatestage); - - /** The above method provides a convenient interface to this method, which - automatically creates an 'AddedStateVariable' and allocates resources in the - SimTK::State for this variable. This interface allows the creator to - add/expose state variables that are allocated by underlying Simbody - components and specify how the state variable value is accessed by - implementing a concrete StateVariable and adding it to the StateEngine using - this method. - You may also want to create an Output for this state variable; see - #OpenSim_DECLARE_OUTPUT_FOR_STATE_VARIABLE for more information. Reporters - should use such an Output to get the StateVariable's value (instead of - using getStateVariableValue()). - */ - void addStateVariable(StateEngine::StateVariable* statevariable); - //===========================================================// - SimTK::DefaultSystemSubsystem& getDefaultSubsystem() - { - return const_cast - (getMultibodySystem().getDefaultSubsystem()); - } - SimTK::DefaultSystemSubsystem& updDefaultSubsystem() - { - return getMultibodySystem().updDefaultSubsystem(); - } - //===========================================================// - /** - * Get a StateVariable anywhere in the state engine, given a - * StateVariable path. - * This returns nullptr if a StateVariable does not exist at the specified - * path or if the path is invalid. - */ - StateVariable* traverseToStateVariable(std::string& pathname); - /** Map names of continuous state variables of the Engine to their - underlying SimTK indices. */ - mutable std::map namedstatevariableinfo_; - /** Check that the list of _allStateVariables is valid. */ - bool isAllStatesVariablesListValid(); - - /** Array of all state variables for fast access during simulation. */ - mutable SimTK::Array_ > - allstatevariables_; - /** A handle the System associated with the above state variables. */ - mutable SimTK::ReferencePtr statesassociatedsystem_; - - /** Default constructor **/ - StateEngine(SimTK::MultibodySystem& system); - - /** Reference pointer to the system that this engine manage. */ - SimTK::ReferencePtr mbsystem_; - /** This is the internal 'writable' state of the engine. - * working_state_ will be set to the system default state when - * initializeState() is called. - */ - SimTK::State working_state_; - - /** Destructor is virtual to allow concrete StateEngine to cleanup. **/ - virtual ~StateEngine() {}; - /** Set up the working state in presetn engine */ - void InitializeState(); - /** - * Get the underlying MultibodySystem that this StateEngine is connected to. - * Make sure you have called Model::initSystem() prior to accessing the System. - * Throws an Exception if the System has not been created or the StateEngine - * has not added itself to the System. - * @see hasSystem(). */ - SimTK::MultibodySystem& getMultibodySystem(); - /** - * Get writable reference to the MultibodySystem that this component is - * connected to. - */ - SimTK::MultibodySystem& updMultibodySystem(); - /** - * Get the number of "continuous" state variables maintained by the - * State Engine. - */ - int getNumOfStateVariables(); - /** Get the number of continuous states that the State Engine added to the - underlying computational system.*/ - int getNumStateVariablesAddedByEngine() - { - return (int)namedstatevariableinfo_.size(); - } - /** - * Get the names of "continuous" state variables maintained by the Engine - */ - Array getStateVariableNames(); - /** - * Get all values of the state variables allocated by this StateEngine. - * - * @param state the State for which to get the value - * @return Vector of state variable values of length getNumStateVariables() - * in the order returned by getStateVariableNames() - * @throws StateEngineHasNoSystem if this object has not been added to a - * System (i.e., if initSystem has not been called) - */ - SimTK::Vector getStateVariableValues(); - /** - * report the state info by requested. - */ - void reporter(SimTK::State& state_); - /** - * Write the state data to xml file. - * For all bodies in the matter system, their generalized coordinates, - * generalized velocities and transformations of the origin points are written in - * the output file - */ - std::string restart_folder_; - void writeStateInfoToXml(int ite_rst_, const SimTK::State& state_); - /** - * read state infor from xml and set it to sate. - * For all bodies in the matter system, their generalized coordinates, - * generalized velocities and transformations of the origin points are read from - * the restart file - */ - SimTK::State readAndSetStateInfoFromXml(int ite_rst_, SimTK::MultibodySystem& system_); - /**@name Realize the Simbody System and State to Computational Stage. - Methods in this section enable advanced and scripting users access to - realize the Simbody MultibodySystem and the provided state to a desired - computational (realization) Stage. - */ - - /** - * Perform computations that depend only on time and earlier stages. - */ - void realizeTime(); - /** - * Perform computations that depend only on position-level state - * variables and computations performed in earlier stages (including time). - */ - void realizePosition(); - /** - * Perform computations that depend only on velocity-level state - * variables and computations performed in earlier stages (including position, - * and time). - */ - void realizeVelocity(); - /** - * Perform computations (typically forces) that may depend on - * dynamics-stage state variables, and on computations performed in earlier - * stages (including velocity, position, and time), but not on other forces, - * accelerations, constraint multipliers, or reaction forces. - */ - void realizeDynamics(); - /** - * Perform computations that may depend on applied forces. - */ - void realizeAcceleration(); - /** - * Perform computations that may depend on anything but are only used - * for reporting and cannot affect subsequent simulation behavior. - */ - void realizeReport(); - }; -} -#endif //STATE_ENGINE_SIMBODY_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.cpp b/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.cpp deleted file mode 100644 index ed33f9835e..0000000000 --- a/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.cpp +++ /dev/null @@ -1,116 +0,0 @@ -/** - * @file xml.cpp - * @brief XML functions are defined here - * @author Chi Zhang and Xiangyu Hu - */ - -#include "xml_engine.h" - -namespace SPH -{ - //=================================================================================================// - XmlEngine::XmlEngine(const std::string& xml_name, const std::string& root_tag) : - xml_name_(xml_name) - { - xmldoc_.setRootTag(root_tag); - root_element_ = xmldoc_.getRootElement(); - } - //=================================================================================================// - void XmlEngine::addElementToXmlDoc(const std::string& element_name) - { - SimTK::Xml::Element* element = new SimTK::Xml::Element(element_name); - root_element_.insertNodeAfter(root_element_.node_end(), *element); - } - //=================================================================================================// - void XmlEngine::addChildToElement(SimTK::Xml::Element& father_element, - const std::string& child_name) - { - SimTK::Xml::Element* child_element = new SimTK::Xml::Element(child_name); - father_element.insertNodeAfter(father_element.node_end(), *child_element); - } - //=================================================================================================// - void XmlEngine::setAttributeToElement(const SimTK::Xml::element_iterator& ele_ite, - const std::string& attrib_name, const Matd& value) - { - int num_dim = value.nrow(); - SimTK::Array_ array_(num_dim * num_dim); - for (int i = 0; i < num_dim; i++) - for (int j = 0; j < num_dim; j++) - array_[i * num_dim + j] = value(i, j); - SimTK::Xml::Attribute attr_(attrib_name, SimTK::String(array_)); - ele_ite->setAttributeValue(attr_.getName(), attr_.getValue()); - } - //=================================================================================================// - void XmlEngine::getRequiredAttributeMatrixValue(SimTK::Xml::element_iterator& ele_ite_, const std::string& attrib_name, Matd& value) - { - std::string value_in_string = ele_ite_->getRequiredAttributeValue(attrib_name); - SimTK::Array_ array_; - array_ = SimTK::convertStringTo>(value_in_string); - int num_dim_2 = array_.size(); - int num_dim; - if (num_dim_2 == 4) { - num_dim = 2; - } - else if (num_dim_2 == 9) { - num_dim = 3; - } - else - { - std::cout << "\n Error: the input dimension of deformation tensor:" << num_dim_2 << " is not valid" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - for (int i = 0; i < num_dim; i++) - for (int j = 0; j < num_dim; j++) - value(i, j) = array_[i * num_dim + j]; - } - //=================================================================================================// - void XmlEngine::writeToXmlFile(const std::string& filefullpath) - { - xmldoc_.writeToFile(filefullpath); - } - //=================================================================================================// - void XmlEngine::loadXmlFile(const std::string& filefullpath) - { - xmldoc_.readFromFile(filefullpath); - root_element_ = xmldoc_.getRootElement(); - } - //=================================================================================================// - std::string XmlEngine::getRootElementTag() - { - return xmldoc_.getRootTag(); - } - //=================================================================================================// - std::string XmlEngine::getElementTag(SimTK::Xml::Element& element) - { - return element.getElementTag(); - } - //=================================================================================================// - void XmlEngine::resizeXmlDocForParticles(size_t input_size) - { - size_t total_elements = std::distance(root_element_.element_begin(), - root_element_.element_end()); - - if (total_elements <= input_size) - { - for (size_t i = total_elements; i != input_size; ++i) addElementToXmlDoc("particle"); - } - else - { - std::cout << "\n Error: XML Engine allows increase date size only!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - exit(1); - } - }; - //=================================================================================================// - size_t XmlEngine::SizeOfXmlDoc() - { - return std::distance(root_element_.element_begin(), root_element_.element_end()); - } - //=================================================================================================// - SimTK::Xml::Element XmlEngine::getChildElement(const std::string& tag) - { - return root_element_.getOptionalElement(tag); - }; - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.h b/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.h deleted file mode 100644 index ffa0dea909..0000000000 --- a/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.h +++ /dev/null @@ -1,114 +0,0 @@ -/* -------------------------------------------------------------------------* -* SPHinXsys * -* --------------------------------------------------------------------------* -* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * -* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * -* physical accurate simulation and aims to model coupled industrial dynamic * -* systems including fluid, solid, multi-body dynamics and beyond with SPH * -* (smoothed particle hydrodynamics), a meshless computational method using * -* particle discretization. * -* * -* SPHinXsys is partially funded by German Research Foundation * -* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * -* and HU1527/12-1. * -* * -* Portions copyright (c) 2017-2020 Technical University of Munich and * -* the authors' affiliations. * -* * -* Licensed under the Apache License, Version 2.0 (the "License"); you may * -* not use this file except in compliance with the License. You may obtain a * -* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * -* * -* --------------------------------------------------------------------------*/ -/** - * @file xml_engine.h - * @brief XML class for xml input and output, this is GUI of simbody xml parser. - * @author Chi Zhang and Xiangyu Hu. - */ - -#ifndef XML_ENGINE_SIMBODY_H -#define XML_ENGINE_SIMBODY_H - - -#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING - -#include "base_data_package.h" -#include "sph_data_conainers.h" - -#include "SimTKcommon.h" -#include "SimTKcommon/internal/Xml.h" -#include "SimTKcommon/internal/String.h" - -#include -#include -#include - -#include -#ifdef __APPLE__ -#include -namespace fs = boost::filesystem; -#else -#include -namespace fs = std::experimental::filesystem; -#endif - -namespace SPH -{ - class XmlEngine - { - protected: - std::string xml_name_; /**< xml name. */ - SimTK::Xml::Document xmldoc_; /**< the xml document. */ - - public: - /** Constructor for XML output. */ - XmlEngine(const std::string& xml_name, const std::string& root_tag); - /** Defaut distructor. */ - virtual ~XmlEngine() {}; - - SimTK::Xml::Element root_element_; /**< Root element of document. */ - - /**Add existing element to root_element of Xml Doc. */ - void addElementToXmlDoc(const std::string& element_name); - - /**Add child element to a given element. */ - void addChildToElement(SimTK::Xml::Element& father_element, const std::string& child_name); - - /** Add an attribute of type string to an xml element. */ - template - void setAttributeToElement(const SimTK::Xml::element_iterator& ele_ite, const std::string& attrib_name, const T& value) - { - SimTK::Xml::Attribute attr_(attrib_name, SimTK::String(value)); - ele_ite->setAttributeValue(attr_.getName(), attr_.getValue()); - }; - /** Adds attribute of type matrix to an xml element. */ - void setAttributeToElement(const SimTK::Xml::element_iterator& ele_ite, const std::string& attrib_name, const Matd& value); - - /** Get the required attribute value of an element */ - template - void getRequiredAttributeValue(SimTK::Xml::element_iterator& ele_ite_, const std::string& attrib_name, T& value) - { - std::string value_in_string = ele_ite_->getRequiredAttributeValue(attrib_name); - value = SimTK::convertStringTo(value_in_string); - }; - /** Get the required int attribute valaue of an element */ - void getRequiredAttributeMatrixValue(SimTK::Xml::element_iterator& ele_ite_, const std::string& attrib_name, Matd& value); - - /** Write to XML file */ - void writeToXmlFile(const std::string& filefullpath); - /** Load XML file using XML parser. */ - void loadXmlFile(const std::string& filefullpath); - /** Get the Tag of root element as a string */ - std::string getRootElementTag(); - /** Get the Tag of a element as a string */ - std::string getElementTag(SimTK::Xml::Element& element); - /** resize of Xml doc */ - void resizeXmlDocForParticles(size_t input_size); - /** Get the size of Xml doc */ - size_t SizeOfXmlDoc(); - /** Get a reference to a child element */ - SimTK::Xml::Element getChildElement(const std::string& tag); - }; -} - -#endif //XML_ENGINE_SIMBODY_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/sphinxsys_system/CMakeLists.txt b/SPHINXsys/src/shared/sphinxsys_system/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/sphinxsys_system/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/sphinxsys_system/sph_system.cpp b/SPHINXsys/src/shared/sphinxsys_system/sph_system.cpp deleted file mode 100644 index 75cf1fe77b..0000000000 --- a/SPHINXsys/src/shared/sphinxsys_system/sph_system.cpp +++ /dev/null @@ -1,139 +0,0 @@ -/** - * @file sph_system.cpp - * @brief Definition of all system level functions - * @author Xiangyu Hu, Luhui Han and Chi Zhang - */ - -#include "sph_system.h" - -#include "base_body.h" -#include "body_relation.h" -#include "solid_dynamics.h" - -namespace SPH -{ - //=================================================================================================// - SPHSystem::SPHSystem(BoundingBox system_domain_bounds, - Real resolution_ref, size_t number_of_threads) : - system_domain_bounds_(system_domain_bounds), - resolution_ref_(resolution_ref), - tbb_global_control_(tbb::global_control::max_allowed_parallelism, number_of_threads), - in_output_(nullptr), restart_step_(0), run_particle_relaxation_(false), - reload_particles_(false) {} - //=================================================================================================// - void SPHSystem::addABody(SPHBody* sph_body) - { - bodies_.push_back(sph_body); - } - //=================================================================================================// - void SPHSystem::addARealBody(RealBody* real_body) - { - real_bodies_.push_back(real_body); - } - //=================================================================================================// - void SPHSystem::addASolidBody(SolidBody* solid_body) - { - solid_bodies_.push_back(solid_body); - } - //=================================================================================================// - void SPHSystem::addAFictitiousBody(FictitiousBody* fictitious_body) - { - fictitious_bodies_.push_back(fictitious_body); - } - //=================================================================================================// - void SPHSystem::initializeSystemCellLinkedLists() - { - for (auto &body : real_bodies_) - { - dynamic_cast(body)->updateCellLinkedList(); - } - } - //=================================================================================================// - void SPHSystem::initializeSystemConfigurations() - { - for (auto& body : bodies_) - { - for (size_t i = 0; i < body->body_relations_.size(); i++) - { - body->body_relations_[i]->updateConfiguration(); - } - - } - } - //=================================================================================================// - Real SPHSystem::getSmallestTimeStepAmongSolidBodies() - { - Real dt = Infinity; - for (size_t i = 0; i < solid_bodies_.size(); i++) - { - solid_dynamics::AcousticTimeStepSize computing_time_step_size(solid_bodies_[i]); - Real dt_temp = computing_time_step_size.parallel_exec(); - if (dt_temp < dt) dt = dt_temp; - } - return dt; - } - //=================================================================================================// - #ifdef BOOST_AVAILABLE - void SPHSystem::handleCommandlineOptions(int ac, char* av[]) - { - try { - - po::options_description desc("Allowed options"); - desc.add_options() - ("help", "produce help message") - ("r", po::value(), "Particle relaxation.") - ("i", po::value(), "Particle reload from input file.") - ("restart_step", po::value(), "Run form a restart file.") - ; - - po::variables_map vm; - po::store(po::parse_command_line(ac, av, desc), vm); - po::notify(vm); - - if (vm.count("help")) { - std::cout << desc << "\n"; - exit(0); - } - - if (vm.count("r")) { - run_particle_relaxation_ = vm["r"].as(); - std::cout << "Particle relaxation was set to " - << vm["r"].as() << ".\n"; - } - else { - std::cout << "Particle relaxation was set to default (" - << run_particle_relaxation_ <<").\n"; - } - - if (vm.count("i")) { - reload_particles_ = vm["i"].as(); - std::cout << "Particle reload from input file was set to " - << vm["i"].as() << ".\n"; - } - else { - std::cout << "Particle reload from input file was set to default (" - << reload_particles_ << ").\n"; - } - - if (vm.count("restart_step")) { - restart_step_ = vm["restart_step"].as(); - std::cout << "Restart step was set to " - << vm["restart_step"].as() << ".\n"; - } - else { - std::cout << "Restart inactivated, i.e. restart_step (" - << restart_step_ << ").\n"; - } - } - catch (std::exception & e) { - std::cerr << "error: " << e.what() << "\n"; - exit(1); - } - catch (...) { - std::cerr << "Exception of unknown type!\n"; - } - - } - #endif - //=================================================================================================// -} diff --git a/SPHINXsys/src/shared/sphinxsys_system/sph_system.h b/SPHINXsys/src/shared/sphinxsys_system/sph_system.h deleted file mode 100644 index 1a90115787..0000000000 --- a/SPHINXsys/src/shared/sphinxsys_system/sph_system.h +++ /dev/null @@ -1,80 +0,0 @@ -/** - * @file sph_system.h - * @brief The SPH_System managing objects in the system level. - * @details Note that the system operation prefer these are application independent. - * @author Xiangyu Hu, Luhui Han and Chi Zhang - */ - -#ifndef SPH_SYSTEM_H -#define SPH_SYSTEM_H - - -#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING - -#define TBB_PREVIEW_GLOBAL_CONTROL 1 -#include -#ifdef BOOST_AVAILABLE -#include "boost/program_options.hpp" -namespace po = boost::program_options; -#endif - -#include "base_data_package.h" -#include "sph_data_conainers.h" - -#include -#include -/** Macro for APPLE compilers*/ -#ifdef __APPLE__ -#include -namespace fs = boost::filesystem; -#else -#include -namespace fs = std::experimental::filesystem; -#endif - -namespace SPH -{ - /** - * @brief Preclaimed classes. - */ - class SPHBody; - class In_Output; - - /** - * @class SPHSystem - * @brief The SPH system managing objects in the system level. - */ - class SPHSystem - { - public: - SPHSystem(BoundingBox system_domain_bounds, Real resolution_ref, - size_t number_of_threads = std::thread::hardware_concurrency()); - virtual ~SPHSystem() {}; - - BoundingBox system_domain_bounds_; /**< Lower and Upper domain bounds. */ - Real resolution_ref_; /**< refernce resolution of the SPH system */ - tbb::global_control tbb_global_control_; /**< global controling on the total number parallel threads */ - - In_Output* in_output_; /**< in_output setup */ - size_t restart_step_; /**< restart step */ - bool run_particle_relaxation_; /**< run particle relaxation for body fitted particle distribution */ - bool reload_particles_; /**< start the simulation with relaxed particles. */ - - SPHBodyVector bodies_; /**< All sph bodies. */ - SPHBodyVector fictitious_bodies_; /**< The bodies without inner particle configuration. */ - SPHBodyVector real_bodies_; /**< The bodies with inner particle configuration. */ - SolidBodyVector solid_bodies_; /**< The bodies with inner particle configuration and acoustic time steps . */ - - void addABody(SPHBody* sph_body); - void addARealBody(RealBody* real_body); - void addASolidBody(SolidBody* solid_body); - void addAFictitiousBody(FictitiousBody* fictitious_body); - void initializeSystemCellLinkedLists(); - void initializeSystemConfigurations(); - Real getSmallestTimeStepAmongSolidBodies(); - #ifdef BOOST_AVAILABLE - void handleCommandlineOptions(int ac, char* av[]); - #endif - }; -} -#endif //SPH_SYSTEM_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/tbb/CMakeLists.txt b/SPHINXsys/src/shared/tbb/CMakeLists.txt deleted file mode 100644 index 4f387fb5d1..0000000000 --- a/SPHINXsys/src/shared/tbb/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir -include(Headersearch) - -file(GLOB BASE_DIR_HEADERS *.h) -DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) -#message("${BASE_DIR_HEADER_NAMES}") -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) -INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/cases_high_level_simulation/test_3d_TAH_path/CMakeLists.txt b/cases_high_level_simulation/test_3d_TAH_path/CMakeLists.txt index a3c916604c..9921482f34 100644 --- a/cases_high_level_simulation/test_3d_TAH_path/CMakeLists.txt +++ b/cases_high_level_simulation/test_3d_TAH_path/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) STRING( REGEX REPLACE ".*/(.*)" "\\1" CURRENT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR} ) @@ -26,7 +26,7 @@ foreach(STL_FILE ${FILES_STL}) endforeach() aux_source_directory(. DIR_SRCS) -ADD_EXECUTABLE(${PROJECT_NAME} ${DIR_SRCS}) +ADD_EXECUTABLE(${PROJECT_NAME} ${EXECUTABLE_OUTPUT_PATH} ${DIR_SRCS}) add_test(NAME ${PROJECT_NAME}_particle_relaxation COMMAND ${PROJECT_NAME} --r=true diff --git a/cases_high_level_simulation/test_3d_TAH_path/sim_total_artificial_heart.h b/cases_high_level_simulation/test_3d_TAH_path/sim_total_artificial_heart.h index d862db3ead..124de77fd0 100644 --- a/cases_high_level_simulation/test_3d_TAH_path/sim_total_artificial_heart.h +++ b/cases_high_level_simulation/test_3d_TAH_path/sim_total_artificial_heart.h @@ -67,10 +67,10 @@ class SimTotalArtificialHeart ~SimTotalArtificialHeart(){}; public: //C++ Backend functions - void runCompleteSimulation(double endTime) { sim->RunSimulation(SPH::Real(endTime)); }; + void runCompleteSimulation(double endTime) { sim->runSimulation(SPH::Real(endTime)); }; public: //WASM functions - void runSimulationFixedDurationJS(int number_of_steps) { sim->RunSimulationFixedDurationJS(number_of_steps); }; + void runSimulationFixedDurationJS(int number_of_steps) { sim->runSimulationFixedDurationJS(number_of_steps); }; private: std::unique_ptr sim; diff --git a/cases_high_level_simulation/test_3d_ball_position_solid_body/CMakeLists.txt b/cases_high_level_simulation/test_3d_ball_position_solid_body/CMakeLists.txt index 936cccb503..b22fde6d8b 100644 --- a/cases_high_level_simulation/test_3d_ball_position_solid_body/CMakeLists.txt +++ b/cases_high_level_simulation/test_3d_ball_position_solid_body/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) STRING( REGEX REPLACE ".*/(.*)" "\\1" CURRENT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR} ) @@ -21,7 +21,7 @@ foreach(STL_FILE ${FILES_STL}) endforeach() aux_source_directory(. DIR_SRCS) -ADD_EXECUTABLE(${PROJECT_NAME} ${DIR_SRCS}) +ADD_EXECUTABLE(${PROJECT_NAME} ${EXECUTABLE_OUTPUT_PATH} ${DIR_SRCS}) add_test(NAME ${PROJECT_NAME}_particle_relaxation COMMAND ${PROJECT_NAME} --r=true diff --git a/cases_high_level_simulation/test_3d_ball_position_solid_body/test_3d_ball_position_solid_body.cpp b/cases_high_level_simulation/test_3d_ball_position_solid_body/test_3d_ball_position_solid_body.cpp index a46044cba3..421dff8142 100644 --- a/cases_high_level_simulation/test_3d_ball_position_solid_body/test_3d_ball_position_solid_body.cpp +++ b/cases_high_level_simulation/test_3d_ball_position_solid_body/test_3d_ball_position_solid_body.cpp @@ -46,7 +46,7 @@ int main() /** SIMULATION MODEL */ StructuralSimulation sim (input); /** START SIMULATION */ - sim.RunSimulation(end_time_simulation); + sim.runSimulation(end_time_simulation); return 0; } diff --git a/cases_high_level_simulation/test_3d_unit/CMakeLists.txt b/cases_high_level_simulation/test_3d_unit_position_based_bc/CMakeLists.txt similarity index 87% rename from cases_high_level_simulation/test_3d_unit/CMakeLists.txt rename to cases_high_level_simulation/test_3d_unit_position_based_bc/CMakeLists.txt index 6e08f61933..456ef0af5a 100644 --- a/cases_high_level_simulation/test_3d_unit/CMakeLists.txt +++ b/cases_high_level_simulation/test_3d_unit_position_based_bc/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) STRING( REGEX REPLACE ".*/(.*)" "\\1" CURRENT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR} ) @@ -20,7 +20,7 @@ foreach(STL_FILE ${FILES_STL}) endforeach() aux_source_directory(. DIR_SRCS) -ADD_EXECUTABLE(${PROJECT_NAME} ${EXECUTABLE_OUTPUT_PATH} ${DIR_SRCS}) +ADD_EXECUTABLE(${PROJECT_NAME} ${EXECUTABLE_OUTPUT_PATH} ${DIR_SRCS}) add_test(NAME ${PROJECT_NAME}_particle_relaxation COMMAND ${PROJECT_NAME} --r=true @@ -28,8 +28,7 @@ add_test(NAME ${PROJECT_NAME}_particle_relaxation if(NOT SPH_ONLY_STATIC_BUILD) # usual dynamic build if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") - set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${EXECUTABLE_OUTPUT_PATH}") - target_link_libraries(${PROJECT_NAME} sphinxsys_3d GTest::gtest GTest::gtest_main) + target_link_libraries(${PROJECT_NAME} sphinxsys_3d) add_dependencies(${PROJECT_NAME} sphinxsys_3d sphinxsys_static_3d) else(${CMAKE_SYSTEM_NAME} MATCHES "Windows") if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") diff --git a/cases_high_level_simulation/test_3d_unit/input/ball_mass.stl b/cases_high_level_simulation/test_3d_unit_position_based_bc/input/ball_mass.stl similarity index 100% rename from cases_high_level_simulation/test_3d_unit/input/ball_mass.stl rename to cases_high_level_simulation/test_3d_unit_position_based_bc/input/ball_mass.stl diff --git a/cases_high_level_simulation/test_3d_unit/input/cylinder.stl b/cases_high_level_simulation/test_3d_unit_position_based_bc/input/cylinder.stl similarity index 100% rename from cases_high_level_simulation/test_3d_unit/input/cylinder.stl rename to cases_high_level_simulation/test_3d_unit_position_based_bc/input/cylinder.stl diff --git a/cases_high_level_simulation/test_3d_unit/test_high_level_structural_class.cpp b/cases_high_level_simulation/test_3d_unit_position_based_bc/position_based_boundary_conditions.cpp similarity index 66% rename from cases_high_level_simulation/test_3d_unit/test_high_level_structural_class.cpp rename to cases_high_level_simulation/test_3d_unit_position_based_bc/position_based_boundary_conditions.cpp index 1ce09b199e..52d0771b72 100644 --- a/cases_high_level_simulation/test_3d_unit/test_high_level_structural_class.cpp +++ b/cases_high_level_simulation/test_3d_unit_position_based_bc/position_based_boundary_conditions.cpp @@ -1,16 +1,14 @@ #include -#include "solid_structural_simulation_class.h" +#include "test_structural_simulation_class.h" -Real tolerance = 1e-6; - -TEST(StructuralSimulation, ExpandBoundingBox) +TEST(StructuralSimulation, expandBoundingBox) { BoundingBox bb(Vec3d(0), Vec3d(0)); BoundingBox bb_2(Vec3d(-0.5, -1.0, -2.0), Vec3d(1.05, 1.1, 2.2)); BoundingBox bb_3(Vec3d(1.0, -10.0, -20.0), Vec3d(10.05, 0.0, 0.0)); - ExpandBoundingBox(&bb, &bb_2); - ExpandBoundingBox(&bb, &bb_3); + expandBoundingBox(&bb, &bb_2); + expandBoundingBox(&bb, &bb_3); BoundingBox bb_ref(Vec3d(-0.5, -10.0, -20.0), Vec3d(10.05, 1.1, 2.2)); @@ -21,51 +19,6 @@ TEST(StructuralSimulation, ExpandBoundingBox) } } -class TestStructuralSimulation : StructuralSimulation -{ -public: - TestStructuralSimulation(StructuralSimulationInput& input) : StructuralSimulation(input){}; - void TestRunSimulation(Real end_time){ RunSimulation(end_time); }; - // input members - string Get_relative_input_path_(){ return relative_input_path_; }; - vector Get_imported_stl_list_(){ return imported_stl_list_; }; - Real Get_scale_stl_(){ return scale_stl_; }; - vector Get_translation_list_(){ return translation_list_; }; - Real Get_system_resolution_(){ return system_resolution_; }; - vector Get_resolution_list_(){ return resolution_list_; }; - vector Get_material_model_list_(){ return material_model_list_; }; - Real Get_physical_viscosity_(){ return physical_viscosity_; }; - // internal members - SPHSystem Get_system_(){ return system_; }; - Real Get_scale_system_boundaries_(){ return scale_system_boundaries_; }; - In_Output Get_in_output_(){ return in_output_; }; - // other - vector Get_body_mesh_list_(){ return body_mesh_list_; }; - vector> Get_solid_body_list_(){ return solid_body_list_; }; - vector> Get_contacting_bodies_list_(){ return contacting_bodies_list_; }; - vector> Get_contact_list_(){ return contact_list_; }; - vector> Get_contact_density_list_(){ return contact_density_list_; }; - vector> Get_contact_force_list_(){ return contact_force_list_; }; - // for InitializeATimeStep - vector> Get_initialize_gravity_(){ return initialize_gravity_; }; - vector Get_non_zero_gravity_(){ return non_zero_gravity_; }; - // for AccelerationForBodyPartInBoundingBox - vector> Get_acceleration_bounding_box_(){ return acceleration_bounding_box_; }; - vector Get_acceleration_bounding_box_tuple_(){ return acceleration_bounding_box_tuple_; }; - // for SpringDamperConstraintParticleWise - vector> Get_spring_damper_constraint_(){ return spring_damper_constraint_; }; - vector Get_spring_damper_tuple_(){ return spring_damper_tuple_; }; - // for ConstrainSolidBodyRegion - vector> Get_fixed_constraint_(){ return fixed_constraint_; }; - vector Get_body_indeces_fixed_constraint_(){ return body_indeces_fixed_constraint_; }; - // for PositionSolidBody - vector> Get_position_solid_body_(){ return position_solid_body_; }; - vector Get_position_solid_body_tuple_(){ return position_solid_body_tuple_; }; - // for PositionScaleSolidBody - vector> Get_position_scale_solid_body_(){ return position_scale_solid_body_; }; - vector Get_position_scale_solid_body_tuple_(){ return position_scale_solid_body_tuple_; }; -}; - TEST(StructuralSimulation, PositionSolidBodyTuple) { /** INPUT PARAMETERS */ @@ -109,7 +62,7 @@ TEST(StructuralSimulation, PositionSolidBodyTuple) //=================================================================================================// //=================================================================================================// - // test ScaleTranslationAndResolution(); + // test scaleTranslationAndResolution(); EXPECT_EQ(sim.Get_translation_list_().size(), sim.Get_resolution_list_().size()); for (size_t i = 0; i < translation_list.size(); i++) { @@ -118,10 +71,10 @@ TEST(StructuralSimulation, PositionSolidBodyTuple) } EXPECT_EQ(sim.Get_system_resolution_(), resolution_mass * scale_stl); //=================================================================================================// - // test CreateBodyMeshList(); + // test createBodyMeshList(); EXPECT_EQ(sim.Get_body_mesh_list_().size(), number_of_bodies); //=================================================================================================// - // test CalculateSystemBoundaries(); + // test calculateSystemBoundaries(); Real ball_radius = 100 * scale_stl * 0.5; BoundingBox test_bounds(Vec3d(-ball_radius * scale_system_bounds), Vec3d(ball_radius * scale_system_bounds)); for (size_t i = 0; i < 3; i++) @@ -134,7 +87,7 @@ TEST(StructuralSimulation, PositionSolidBodyTuple) EXPECT_EQ(sim.Get_solid_body_list_().size(), number_of_bodies); //=================================================================================================// // test InitializeAllContacts(); - EXPECT_EQ(sim.Get_contacting_bodies_list_().size(), 0); + EXPECT_EQ(sim.Get_contacting_body_pairs_list_().size(), 0); EXPECT_EQ(sim.Get_contact_list_().size(), 0); EXPECT_EQ(sim.Get_contact_density_list_().size(), 0); EXPECT_EQ(sim.Get_contact_force_list_().size(), 0); @@ -216,6 +169,53 @@ TEST(StructuralSimulation, PositionScaleSolidBodyTuple) } } +TEST(StructuralSimulation, TranslateSolidBodyTuple) +{ + Real scale_stl = 0.001 / 4; // diameter of 0.025 m + Real resolution_mass = 8.0; + Real poisson = 0.35; + Real Youngs_modulus = 1e4; + Real physical_viscosity = 200; + Real rho_0 = 1000; + Real end_time = 0.1; + + /** STL IMPORT PARAMETERS */ + std::string relative_input_path = "./input/"; //path definition for linux + std::vector imported_stl_list = { "ball_mass.stl" }; + std::vector translation_list = { Vec3d(0) }; + std::vector resolution_list = { resolution_mass}; + LinearElasticSolid material = LinearElasticSolid(rho_0, Youngs_modulus, poisson); + std::vector material_model_list = { material }; + + StructuralSimulationInput input + { + relative_input_path, + imported_stl_list, + scale_stl, + translation_list, + resolution_list, + material_model_list, + physical_viscosity, + {} + }; + Vecd translation_vector = Vec3d(0.0, 0.0, 0.1); //changed to be more like PositionSolidBodyTuple + input.translation_solid_body_tuple_ = { TranslateSolidBodyTuple(0, end_time * 0.32, end_time, translation_vector) }; + + //=================================================================================================// + TestStructuralSimulation sim (input); + sim.TestRunSimulation(end_time); + //=================================================================================================// + + StdLargeVec& pos_0 = sim.Get_translation_solid_body_()[0]->GetParticlePos0(); + StdLargeVec& pos_n = sim.Get_translation_solid_body_()[0]->GetParticlePosN(); + + for (size_t index = 0; index < pos_0.size(); index++) + { + Vec3d end_pos = pos_0[index] + translation_vector; + EXPECT_NEAR(pos_n[index][2], end_pos[2], end_pos.norm() * 1e-2); + } +} + int main(int argc, char* argv[]) { testing::InitGoogleTest(&argc, argv); diff --git a/cases_high_level_simulation/test_3d_unit_time_dep_contact/CMakeLists.txt b/cases_high_level_simulation/test_3d_unit_time_dep_contact/CMakeLists.txt new file mode 100644 index 0000000000..33256fa75b --- /dev/null +++ b/cases_high_level_simulation/test_3d_unit_time_dep_contact/CMakeLists.txt @@ -0,0 +1,70 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir +set(CMAKE_VERBOSE_MAKEFILE on) + +STRING( REGEX REPLACE ".*/(.*)" "\\1" CURRENT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR} ) +PROJECT("${CURRENT_FOLDER}") + +include(ImportSPHINXsysFromSource_for_3D_build) + +SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) +SET(EXECUTABLE_OUTPUT_PATH "${PROJECT_BINARY_DIR}/bin/") +SET(BUILD_INPUT_PATH "${EXECUTABLE_OUTPUT_PATH}/input") +SET(BUILD_RELOAD_PATH "${EXECUTABLE_OUTPUT_PATH}/reload") + +file(MAKE_DIRECTORY ${BUILD_INPUT_PATH}) +execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${BUILD_INPUT_PATH}) + +set(FILES_STL + "plate.stl" + "plate2.stl" + "mock_stent.stl" + "vessel_cylinder.stl" + "plate_stent.stl" + ) +foreach(STL_FILE ${FILES_STL}) + file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/input/${STL_FILE} DESTINATION ${BUILD_INPUT_PATH}) +endforeach() + +aux_source_directory(. DIR_SRCS) +ADD_EXECUTABLE(${PROJECT_NAME} ${EXECUTABLE_OUTPUT_PATH} ${DIR_SRCS}) + +add_test(NAME ${PROJECT_NAME}_particle_relaxation + COMMAND ${PROJECT_NAME} --r=true + WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) + +if(NOT SPH_ONLY_STATIC_BUILD) # usual dynamic build + if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + target_link_libraries(${PROJECT_NAME} sphinxsys_3d) + add_dependencies(${PROJECT_NAME} sphinxsys_3d sphinxsys_static_3d) + else(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + target_link_libraries(${PROJECT_NAME} sphinxsys_3d stdc++) + else(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + target_link_libraries(${PROJECT_NAME} sphinxsys_3d stdc++ stdc++fs gtest gtest_main) + endif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + + if(DEFINED BOOST_AVAILABLE) # link Boost if available (not for Windows) + target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES}) + endif() + endif(${CMAKE_SYSTEM_NAME} MATCHES "Windows") +else() # static build only + if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + target_link_libraries(${PROJECT_NAME} sphinxsys_static_3d) + else(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + target_link_libraries(${PROJECT_NAME} sphinxsys_static_3d stdc++) + else(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + target_link_libraries(${PROJECT_NAME} sphinxsys_static_3d stdc++ stdc++fs) + endif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + + if(DEFINED BOOST_AVAILABLE) # link Boost if available (not for Windows) + target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES}) + endif() + endif(${CMAKE_SYSTEM_NAME} MATCHES "Windows") +endif() +if(NOT BUILD_WITH_SIMBODY) # link Simbody if not built by the project +target_link_libraries(${PROJECT_NAME} ${Simbody_LIBRARIES}) +endif() +if(NOT BUILD_WITH_ONETBB) # link TBB if not built by the project +target_link_libraries(${PROJECT_NAME} ${TBB_LIBRARYS}) +endif() \ No newline at end of file diff --git a/cases_high_level_simulation/test_3d_unit_time_dep_contact/input/mock_stent.stl b/cases_high_level_simulation/test_3d_unit_time_dep_contact/input/mock_stent.stl new file mode 100644 index 0000000000000000000000000000000000000000..0326921ede84c93f555e426bb39852d67b347c13 GIT binary patch literal 23884 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 PsJ=&^CE4-QeLu4=T@9oO literal 0 HcmV?d00001 diff --git a/cases_high_level_simulation/test_3d_unit_time_dep_contact/input/plate2.stl b/cases_high_level_simulation/test_3d_unit_time_dep_contact/input/plate2.stl new file mode 100644 index 0000000000000000000000000000000000000000..2e0bfb2ee7728f2ab472e1676757d51b3167bc09 GIT binary patch literal 684 zcmb_YK@Nj33=2E&5&VSmv;2&3+8#BD)hVuA#BPh!%1Z6nso8()IgImVviJU|XR^6e zyb9hw$7@ADCEkR-fpxxws1vi_xT2s3SgNL93<{(o3#ob%^qG(f-+;h6U((|S7w{Sz z=%P>$;sDPV+$gy7Pc(_(NMgO33Lg9u!37FVg5q~7Zma5445@Tm@f$qM=>vD2dxHZW PsJ=&^CE4-QeLu4=T@9oO literal 0 HcmV?d00001 diff --git a/cases_high_level_simulation/test_3d_unit_time_dep_contact/input/plate_stent.stl b/cases_high_level_simulation/test_3d_unit_time_dep_contact/input/plate_stent.stl new file mode 100644 index 0000000000000000000000000000000000000000..9ee2727a02e6321170b328b3906b86cf5b2cd7b9 GIT binary patch literal 684 zcmb`D!4<+V3`5;J18^&wlx&vGz_;$g5XG`9kZ&BwAx|&Sw=J9f_gq@Jxh=N))oxF- zeS{vO{dKH&yaSmJ+&DtGXwW|rtSf|P>6v%*il1@1v`1>bmp3NcpoG9AjaH)EB+j&` zs|0l=&-x@}cHI?q1)+G3(y5YWs7hipXaUA)X*EZV@8}4=@Ade!a=E^7U-W+kvmF7D~DkG zmwMz06lx-9b^q<}Y4*71ixIKrdu!@TKlOHtgBqZF$P&jC0%#&6SNRGZJSnV*&i4AL zC4yFu{Pk_kx-I8v9K!LIZ$7QNZS7{ecd3Ega-+UsfA)2MTH0sRR^uQ7a*MeU zuXg|0OS|j#c+H$wr!Jq~T)*~2n*;I{k=H-I_Wb5)_gye&Mf5a+R&QH(Mf35)Plp@Z**717yV?-C+t zb?X@yHh(#IW4udFzG_qV(sK{6`Je`J%aN*z2x=w}lB<9ATe@2x*>#TN*JoYPeE92Y zZ4QVo;;27&_}7}Pzuh!vMf5a+R-3oq++6qafv>8zJ@)Q;%Mbp%pjc|a*F%;#?)vZM zdh*safG6Xe{duDo?1AOEd z%{hE8tcWfm?}(t)wta4B_Idmcxm)$y;FVwbWcSA7*4TVd1G(i$C4!m>gyc$}I_?{+ zdX+CZ`bVPMzs3JIgxd%4bGdopoeS!3VMVlC>%a2|=h*F)UGwktPhZxNs_MA=&aU5a z=q@`IQ)&{Q2sej8|M6XCx4{=`<0j3-A)bAJ!J9f_V1rr zuRrEMLr?>`XQr!aiUWM)knH9wbnv~fB0AgaoD)H-Pakz-v;JiVYvh?Dj1jcbC-^m| zJS!p&lW&8oe}9L~AT^NfNgZcXIEbKE69~y}zCsO8IIex;M6XsskmK%cUv7>(>ex8v z$3L{C{_^%=l|c>oddR{tg#eleiTvnwud5HebU%B_-FwKz&FLqdW9&pg9>qb8u6Ne{ zXQQDxQqGj#?bHyV>#lnrJTXR|F(ralFW=*g=IV(Q@)eQS_k4bHbJeD6=B$WL2oXYo zpw%hgd2VywvEPVu{;qfaYvJbIkGDCe27EnaiTt(~?p-hK_eMid1G$)+s+!^eA2}pf z@d`TU@V&4i+5!4+iJ+D4IgekuGTtS7$}V-^Ozx+|0^ZoyN z&cgX$Io)QE8u0ayC64XumlvM6>10Dt1G#6W<#RZ}0X}k!<{Z8kRzw$(cSO)it8IrI zvOUguti#!SPy^YjYH@DFDK zX4yaIW3-N31)V|3;4R`PjV_9upY2ABMEP7U~U1{1f<`BI$oKn>&~ z(*;K$2V`=L<{Z8kRzzFSk|(`_e$G9k`eg8EdwGh^^0Qr@y9@ET8>oS-pYt@5J*g9d znhAu=xqKTe#P5%SL%tbi{(VmL5P=4vb1qNInSTq@is*z;P=TP8eDBVDWnka6Oa6UM z4fym)ow)7Uz7(JBff~p~rVEZh4#?yf%{hE8tcbSAB~N4F0R%Qs45(u6AF+s+T?I%y*{v z|D!pV-QCQ0qqHJ=h*`tdIVXZvvP+%C9ldbKF17Q0Ej2**kOd*T)GqE)12vF!7v}d> zUvLC+KqiME@)fk|?D;9Ih&KB$ph^U-q?#}*>I`+POay8mYtsS{J)IioMr{6KcA(hmGpk@NmT8{L| zV;8KndOnMFe67~HSBB7gN-wL0&rm&2RzwfB!{2#?b4X=-mQPHi7T$R+ywof^pG2Ev z@k;9LU97i^54I~IXrv_2JAbUs8pzr+;rEr~YKjAVXqE26W#&N&gZ(tTp=BNL9e=j;?q4P<6_ zgdoQRqP1?S1$0v-cC8wCKOE6T9NOJB^PW3e5p9St;H)1mA!sGNc(dF)D1CiP-q%MB z__S{=jb#0Bop;bt16g~c{JxT0O>uya9D>MK=-_){MRc~;IVXZvS`W(IpRu-Nb50Fp z?QZkA5wD1#W&)veKGYNEIJ7$G{lY{SacIBja_n!lDp&Mfdq)%65MjVshiO943Vpre z)h+jKT-vzl6Sf|u27KE6nMRV)Pr7EwJ4~s8+%wbD9N;5|WcNj{KX2zN&KT5MZb|7IUSea-_7%s1ac?AIO4maLzHVc6p~+!<|gCyL|Cn z(dVUNevb@+eo-VB8`L zg;zD!oW(jC9twwav8Gix#+_P)54j2}MGn4VU0QgheSK+f$sG&!2Erj-tShdm$pLxD zm3vi0j&X}D6kgTXlNI|+cqklNRr6gc#+}-wj&jAw!8gCVi>?j(^IWY-6$I8!ghRSN zSKM=w1M(Hs-Z&he?G{3ll#Irt>n z#33^3i2+tcg@yI9VQ~99ibLnd>!R>#$d$RIk)yt6yI=1F1jh)0x?zFfNM*Gap7fB3 zmdNu}Rv^JQs*4ID45(p0=w}eDp6fn3chI3OTG3x}K+cgXmG;AV#ne7`e$t?y8_(O| zkD4n2wR_uVdLJ1)6b_zd&|kG zFF7EOazzcsWgH^}P7!T?7Cmg?p&)qLhjDwzMCV+Eapd4D&cB8v+26ST@H1?lzUt~D zcOgmtm7m>J^p_lvbL8rkc7^%*DNjQgM@EQa)>GuC?6$9o9=7mM5Iha1zmSO@J!xX)U0N+X6b^P%DK7h)WX+YM=W~@b5|i^4{Y5&eUseN1 zJ%Vy)c>kTw&0_8fo9c}s2jo$%sKL05BO}B$>-|VQn{xNLhk75ohk{_YA;zWe`WbZe zC|A_TXT|xn_rp0dLcP)&S?ao#=Sma1PoYOMCqoZc7)K7iqCeFE_U)`SWbf=QdY$FFq{-id2_xAhLcSBd zr@cTYd-F=Z!y8|J5W$gb1<=KQhKd@%R92cl(m1;Ku5c%t96o*(@_ z_Z$jhs_R}>1KxqJ>ynhWlq(Ue%fdB`cElm8rIah?gr4C4myz3hcRnAoo=fjzlwQd? zv5x2G6|7pwx)$~KSo#!)&AF^`)4M&gj`sIvbWi1P+6ZEbgX2Oj`WTpN6Aje-z(Go zV3YSzQ}6tc1M(`7V_e2DLdXs>y^9DB#VUDdi1IBu_m><%_tTMsPplHh2%+YZxDM7~ub z*Y6KM4TZNOgj7<~Z#}@n79I+Mzfyp4>AQWad3^mDHm#Lr|LqK0 zqvSKl%rdNA9U+x;|E-S#o$Yl7b)D$v21JlMtt{<4iW#9lJYjxpo#^%b>0cvZcA14DS9*@)8D+F1j<^cj-EZLQFwGDo*Mh#>cH=US?JcTH&5@O(3@$8NW z;Y;Uu`v3hZf2W9NUL`_)Jt_Sb63)`hcR#Pg{}-?LdrP{v^j}GWELQ2X&qhjK6(@?w Zl_C6W(yjGhJ5ltRvOS=m@M1Ov{Xcd_(;ol; literal 0 HcmV?d00001 diff --git a/cases_high_level_simulation/test_3d_unit_time_dep_contact/time_dep_contact.cpp b/cases_high_level_simulation/test_3d_unit_time_dep_contact/time_dep_contact.cpp new file mode 100644 index 0000000000..53f2048242 --- /dev/null +++ b/cases_high_level_simulation/test_3d_unit_time_dep_contact/time_dep_contact.cpp @@ -0,0 +1,227 @@ +#include +#include "test_structural_simulation_class.h" + +TEST(StructuralSimulation, TimeDependentContact) +{ + Real scale_stl = 0.001; + Real res = 1.5; + Real end_time = 0.1; + /* MATERIAL PARAMETERS */ + Real rho_0 = 1000.0; + Real poisson = 0.35; + Real Youngs_modulus = 1e5; + Real physical_viscosity = 200; + /** STL IMPORT PARAMETERS */ + string relative_input_path = "./input/"; //path definition for linux + string plate_stl = "plate.stl"; + string plate2_stl = "plate2.stl"; + vector imported_stl_list = { plate_stl, plate2_stl }; + vector translation_list = { Vec3d(0), Vec3d(0) }; + vector resolution_list = { res, res }; + LinearElasticSolid material = LinearElasticSolid(rho_0, Youngs_modulus, poisson); + vector material_model_list = { material, material }; + StructuralSimulationInput input + { + relative_input_path, + imported_stl_list, + scale_stl, + translation_list, + resolution_list, + material_model_list, + physical_viscosity, + {} + }; + pair, array> contact_pair( {0, 1}, {end_time * 0.5, end_time} ); + input.time_dep_contacting_body_pairs_list_ = { contact_pair }; + // position the plate + input.position_solid_body_tuple_ = { PositionSolidBodyTuple(1, 0.0, end_time * 0.5, Vec3d(0, 0, 10.0) * scale_stl), + PositionSolidBodyTuple(1, end_time * 0.5, end_time, Vec3d(0, 0, 0.0) * scale_stl) }; + + // spring damper constraint + Real spring_stiffness = 500; + Real damping_ratio = 0.02; + input.spring_damper_tuple_ = { SpringDamperTuple(0, Vec3d(spring_stiffness), damping_ratio)}; + + /** SIMULATION MODEL */ + TestStructuralSimulation sim(input); + /** START SIMULATION */ + sim.TestRunSimulation(end_time); + + // check that the two plates don't overlap, this will check if the contact works properly + StdLargeVec& pos_n_1 = sim.Get_solid_body_list_()[0].get()->getElasticSolidParticles()->pos_n_; // plate 1 particles + StdLargeVec& pos_n_2 = sim.Get_solid_body_list_()[1].get()->getElasticSolidParticles()->pos_n_; // plate 2 particles + + // plate 1 max z-coordinate + Real max_z_1 = -1000.0; + for (size_t index = 0; index < pos_n_1.size(); index++) + { + if (max_z_1 < pos_n_1[index][2]) max_z_1 = pos_n_1[index][2]; + } + // plate 2 min z-coordinate + Real min_z_2 = 1000.0; + for (size_t index = 0; index < pos_n_1.size(); index++) + { + if (min_z_2 > pos_n_2[index][2]) min_z_2 = pos_n_2[index][2]; + } + // check that min_z_2, minimum z of plate 1 is larger than max_z_1, maximum z of plate 2 + EXPECT_GT(min_z_2, max_z_1); +} + +TEST(StructuralSimulation, TimeDependentContactStiff) +{ + Real scale_stl = 0.001; + Real res = 1.5; + Real end_time = 0.04; + /* MATERIAL PARAMETERS */ + Real rho_0 = 1000.0; + Real poisson = 0.35; + Real physical_viscosity = 200; + /** STL IMPORT PARAMETERS */ + string relative_input_path = "./input/"; //path definition for linux + string plate_stl = "plate.stl"; + string plate2_stl = "plate2.stl"; + vector imported_stl_list = { plate_stl, plate2_stl }; + vector translation_list = { Vec3d(0), Vec3d(0, 0, 8) }; + vector resolution_list = { res, res }; + LinearElasticSolid material = LinearElasticSolid(rho_0, 1e4, poisson); + LinearElasticSolid material_stiff = LinearElasticSolid(rho_0, 1e8, poisson); + vector material_model_list = { material, material_stiff }; + StructuralSimulationInput input + { + relative_input_path, + imported_stl_list, + scale_stl, + translation_list, + resolution_list, + material_model_list, + physical_viscosity, + {} + }; + pair, array> contact_pair( {0, 1}, {end_time * 0.05, end_time} ); + input.time_dep_contacting_body_pairs_list_ = { contact_pair }; + // position the plate + // input.position_solid_body_tuple_ = { PositionSolidBodyTuple(1, 0.0, end_time * 0.5, Vec3d(0, 0, 10.0) * scale_stl), + // PositionSolidBodyTuple(1, end_time * 0.5, end_time, Vec3d(0, 0, 0.0) * scale_stl) }; + + input.non_zero_gravity_ = { GravityPair(1, Vec3d(0, 0, -100)) }; + + // spring damper constraint + Real spring_stiffness = 500; + Real damping_ratio = 0.02; + input.spring_damper_tuple_ = { SpringDamperTuple(0, Vec3d(spring_stiffness), damping_ratio)}; + + /** SIMULATION MODEL */ + TestStructuralSimulation sim(input); + /** START SIMULATION */ + sim.TestRunSimulation(end_time); + + // check that the two plates don't overlap, this will check if the contact works properly + StdLargeVec& pos_n_1 = sim.Get_solid_body_list_()[0].get()->getElasticSolidParticles()->pos_n_; // plate 1 particles + StdLargeVec& pos_n_2 = sim.Get_solid_body_list_()[1].get()->getElasticSolidParticles()->pos_n_; // plate 2 particles + + // plate 1 max z-coordinate + Real max_z_1 = -1000.0; + for (size_t index = 0; index < pos_n_1.size(); index++) + { + if (max_z_1 < pos_n_1[index][2]) max_z_1 = pos_n_1[index][2]; + } + // plate 2 min z-coordinate + Real min_z_2 = 1000.0; + for (size_t index = 0; index < pos_n_1.size(); index++) + { + if (min_z_2 > pos_n_2[index][2]) min_z_2 = pos_n_2[index][2]; + } + // check that min_z_2, minimum z of plate 1 is larger than max_z_1, maximum z of plate 2 + EXPECT_GT(min_z_2, max_z_1); +} + +TEST(StructuralSimulation, MockStent) +{ + Real scale_stl = 0.001; + Real end_time = 0.08; + /* MATERIAL PARAMETERS */ + Real rho_0 = 1000.0; + Real poisson = 0.35; + Real physical_viscosity = 200; + /** STL IMPORT PARAMETERS */ + string relative_input_path = "./input/"; //path definition for linux + vector imported_stl_list = { "mock_stent.stl", "plate_stent.stl", "vessel_cylinder.stl" }; + vector translation_list = { Vec3d(0, 5, 0), Vec3d(0, 28, 0), Vec3d(0) }; + vector resolution_list = { 3, 2, 1 }; + LinearElasticSolid material = LinearElasticSolid(rho_0, 1e4, poisson); + LinearElasticSolid material_stiff = LinearElasticSolid(rho_0, 1e6, poisson); + vector material_model_list = { material_stiff, material, material }; + StructuralSimulationInput input + { + relative_input_path, + imported_stl_list, + scale_stl, + translation_list, + resolution_list, + material_model_list, + physical_viscosity, + {} + }; + pair, array> contact_pair_1( {0, 1}, {0.0, end_time * 0.4} ); + pair, array> contact_pair_2( {0, 2}, {end_time * 0.5, end_time} ); + input.time_dep_contacting_body_pairs_list_ = { contact_pair_1, contact_pair_2 }; + + input.non_zero_gravity_ = { GravityPair(0, Vec3d(0, 100, 0)) }; + Vecd translation_vector = Vec3d(0, -7.5, 0) * scale_stl; + input.translation_solid_body_tuple_ = { TranslateSolidBodyTuple(1, 0.0, end_time * 0.5, translation_vector) }; + + // spring damper constraint + Real spring_stiffness = 100; + Real damping_ratio = 0.02; + input.spring_damper_tuple_ = { SpringDamperTuple(2, Vec3d(spring_stiffness), damping_ratio) }; + + /** SIMULATION MODEL */ + TestStructuralSimulation sim(input); + + EXPECT_EQ(sim.Get_contacting_body_pairs_list_().size(), 0); + EXPECT_EQ(sim.Get_time_dep_contacting_body_pairs_list_().size(), 2); + EXPECT_EQ(sim.Get_contact_list_().size(), 4); + EXPECT_EQ(sim.Get_contact_density_list_().size(), 4); + EXPECT_EQ(sim.Get_contact_force_list_().size(), 4); + + /** START SIMULATION */ + sim.TestRunSimulation(end_time); + + // check the plate position, that the translation is correct + StdLargeVec& pos_0 = sim.Get_solid_body_list_()[1].get()->getElasticSolidParticles()->pos_0_; + StdLargeVec& pos_n = sim.Get_solid_body_list_()[1].get()->getElasticSolidParticles()->pos_n_; + for (size_t index = 0; index < pos_0.size(); index++) + { + Vec3d end_pos = pos_0[index] + translation_vector; + EXPECT_NEAR(pos_n[index][2], end_pos[2], end_pos.norm() * 1e-2); + } + + // check that the stent is inside the vessel: + // 1. the max y pos is larger for the vessel than the stent + // 1. the min y pos is smaller for the vessel than the stent + StdLargeVec& pos_n_stent = sim.Get_solid_body_list_()[0].get()->getElasticSolidParticles()->pos_n_; // stent particles + StdLargeVec& pos_n_vessel = sim.Get_solid_body_list_()[2].get()->getElasticSolidParticles()->pos_n_; // vessel particles + + Real max_y_stent = -1000.0; + Real min_y_stent = 1000.0; + for (size_t index = 0; index < pos_n_stent.size(); index++) + { + if (max_y_stent < pos_n_stent[index][1]) max_y_stent = pos_n_stent[index][1]; + if (min_y_stent > pos_n_stent[index][1]) min_y_stent = pos_n_stent[index][1]; + } + Real max_y_vessel = -1000.0; + Real min_y_vessel = 1000.0; + for (size_t index = 0; index < pos_n_vessel.size(); index++) + { + if (max_y_vessel < pos_n_vessel[index][1]) max_y_vessel = pos_n_vessel[index][1]; + if (min_y_vessel > pos_n_vessel[index][1]) min_y_vessel = pos_n_vessel[index][1]; + } + EXPECT_GT(max_y_vessel, max_y_stent); + EXPECT_GT(min_y_stent, min_y_vessel); +} + +int main(int argc, char* argv[]) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/cases_test/test_1d_shock_tube/src/CMakeLists.txt b/cases_test/test_1d_shock_tube/src/CMakeLists.txt index 6191e91220..7c41d09d48 100644 --- a/cases_test/test_1d_shock_tube/src/CMakeLists.txt +++ b/cases_test/test_1d_shock_tube/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_T_shaped_pipe/src/CMakeLists.txt b/cases_test/test_2d_T_shaped_pipe/src/CMakeLists.txt index c82b8fe14b..6d1d678de7 100644 --- a/cases_test/test_2d_T_shaped_pipe/src/CMakeLists.txt +++ b/cases_test/test_2d_T_shaped_pipe/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_airfoil/CMakeLists.txt b/cases_test/test_2d_airfoil/CMakeLists.txt index 322aa318f6..876a315d73 100644 --- a/cases_test/test_2d_airfoil/CMakeLists.txt +++ b/cases_test/test_2d_airfoil/CMakeLists.txt @@ -1,5 +1,5 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.8.0) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_airfoil/airfoil_2d.cpp b/cases_test/test_2d_airfoil/airfoil_2d.cpp index 4440565c9a..f45ed8af16 100644 --- a/cases_test/test_2d_airfoil/airfoil_2d.cpp +++ b/cases_test/test_2d_airfoil/airfoil_2d.cpp @@ -36,7 +36,7 @@ int main(int ac, char* av[]) // Define simple file input and outputs functions. //---------------------------------------------------------------------- BodyStatesRecordingToVtu airfoil_recording_to_vtu(in_output, { airfoil }); - MeshRecordingToPlt mesh_cell_linked_list_recording(in_output, airfoil, airfoil->mesh_cell_linked_list_); + MeshRecordingToPlt cell_linked_list_recording(in_output, airfoil, airfoil->cell_linked_list_); //---------------------------------------------------------------------- // Define body relation map. // The contact map gives the topological connections between the bodies. @@ -61,7 +61,7 @@ int main(int ac, char* av[]) // First output before the simulation. //---------------------------------------------------------------------- airfoil_recording_to_vtu.writeToFile(0); - mesh_cell_linked_list_recording.writeToFile(0); + cell_linked_list_recording.writeToFile(0); //---------------------------------------------------------------------- // Particle relaxation time stepping start here. //---------------------------------------------------------------------- diff --git a/cases_test/test_2d_airfoil/airfoil_2d.h b/cases_test/test_2d_airfoil/airfoil_2d.h index 583fc4ac95..855f5fff8f 100644 --- a/cases_test/test_2d_airfoil/airfoil_2d.h +++ b/cases_test/test_2d_airfoil/airfoil_2d.h @@ -36,7 +36,7 @@ class Airfoil : public SolidBody public: Airfoil(SPHSystem &system, std::string body_name) : SolidBody(system, body_name, - new ParticleSpacingByBodyShape(1.15, 0, 2), + new ParticleSpacingByBodyShape(1.15, 1.0, 2), new ParticleGeneratorMultiResolution()) { /** Geometry definition. */ diff --git a/cases_test/test_2d_collision/src/CMakeLists.txt b/cases_test/test_2d_collision/src/CMakeLists.txt index dc47faedda..ee0c2a544e 100644 --- a/cases_test/test_2d_collision/src/CMakeLists.txt +++ b/cases_test/test_2d_collision/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_dambreak/src/CMakeLists.txt b/cases_test/test_2d_dambreak/src/CMakeLists.txt index c37366a0ef..792c519dc5 100644 --- a/cases_test/test_2d_dambreak/src/CMakeLists.txt +++ b/cases_test/test_2d_dambreak/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_depolarization/src/CMakeLists.txt b/cases_test/test_2d_depolarization/src/CMakeLists.txt index c82b8fe14b..6d1d678de7 100644 --- a/cases_test/test_2d_depolarization/src/CMakeLists.txt +++ b/cases_test/test_2d_depolarization/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_depolarization/src/depolarization.cpp b/cases_test/test_2d_depolarization/src/depolarization.cpp index cde66fb2b7..4560209dc2 100644 --- a/cases_test/test_2d_depolarization/src/depolarization.cpp +++ b/cases_test/test_2d_depolarization/src/depolarization.cpp @@ -187,7 +187,7 @@ int main() write_recorded_voltage.writeToFile(0); int ite = 0; - Real T0 = 16.0; + Real T0 = 8.0; Real End_Time = T0; Real D_Time = 0.5; /**< Time period for output */ Real Dt = 0.01 * D_Time; /**< Time period for data observing */ diff --git a/cases_test/test_2d_diffusion/src/CMakeLists.txt b/cases_test/test_2d_diffusion/src/CMakeLists.txt index c82b8fe14b..6d1d678de7 100644 --- a/cases_test/test_2d_diffusion/src/CMakeLists.txt +++ b/cases_test/test_2d_diffusion/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_elastic_gate/src/CMakeLists.txt b/cases_test/test_2d_elastic_gate/src/CMakeLists.txt index c82b8fe14b..6d1d678de7 100644 --- a/cases_test/test_2d_elastic_gate/src/CMakeLists.txt +++ b/cases_test/test_2d_elastic_gate/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_elastic_gate/src/elastic_gate.cpp b/cases_test/test_2d_elastic_gate/src/elastic_gate.cpp index 762215443c..697b5dbf33 100644 --- a/cases_test/test_2d_elastic_gate/src/elastic_gate.cpp +++ b/cases_test/test_2d_elastic_gate/src/elastic_gate.cpp @@ -148,7 +148,7 @@ class Gate : public SolidBody { public: Gate(SPHSystem &system, std::string body_name) - : SolidBody(system, body_name, new ParticleAdaptation(1.15, 1)) + : SolidBody(system, body_name, new ParticleAdaptation(1.15, 2.0)) { /** Geomtry definition. */ std::vector gate_shape = createGateShape(); @@ -213,7 +213,7 @@ class Observer : public FictitiousBody { public: Observer(SPHSystem &system, std::string body_name) : - FictitiousBody(system, body_name, new ParticleAdaptation(1.15, 1)) + FictitiousBody(system, body_name, new ParticleAdaptation(1.15, 2.0)) { /** Add observation point. */ body_input_points_volumes_.push_back(std::make_pair(GateP_lb, 0.0)); diff --git a/cases_test/test_2d_eulerian_taylor_green/src/CMakeLists.txt b/cases_test/test_2d_eulerian_taylor_green/src/CMakeLists.txt index c82b8fe14b..6d1d678de7 100644 --- a/cases_test/test_2d_eulerian_taylor_green/src/CMakeLists.txt +++ b/cases_test/test_2d_eulerian_taylor_green/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_filling_tank/src/CMakeLists.txt b/cases_test/test_2d_filling_tank/src/CMakeLists.txt index 806f60e5f0..3cddf16b63 100644 --- a/cases_test/test_2d_filling_tank/src/CMakeLists.txt +++ b/cases_test/test_2d_filling_tank/src/CMakeLists.txt @@ -1,5 +1,5 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.10) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_filling_tank/src/filling_tank.cpp b/cases_test/test_2d_filling_tank/src/filling_tank.cpp index c7cd4d1c93..e6ab224af1 100644 --- a/cases_test/test_2d_filling_tank/src/filling_tank.cpp +++ b/cases_test/test_2d_filling_tank/src/filling_tank.cpp @@ -178,7 +178,7 @@ int main() // The contact map gives the topological connections between the bodies. // Basically the the range of bodies to build neighbor particle lists. //---------------------------------------------------------------------- - ComplexBodyRelation* water_block_complex_relation = new ComplexBodyRelation(water_block, { wall_boundary }); + ComplexBodyRelation* water_block_complex = new ComplexBodyRelation(water_block, { wall_boundary }); BodyRelationContact* fluid_observer_contact_relation = new BodyRelationContact(fluid_observer, { water_block }); //---------------------------------------------------------------------- // Define all numerical methods which are used in this case. @@ -188,15 +188,15 @@ int main() Inlet* inlet = new Inlet(water_block, "Inlet"); InletInflowCondition inflow_condition(water_block, inlet); fluid_dynamics::EmitterInflowInjecting inflow_emitter(water_block, inlet, 300, 0, true); - fluid_dynamics::DensitySummationFreeSurfaceComplex update_density_by_summation(water_block_complex_relation); - fluid_dynamics::SpatialTemporalFreeSurfaceIdentificationComplex indicate_free_surface(water_block_complex_relation); + fluid_dynamics::DensitySummationFreeSurfaceComplex update_density_by_summation(water_block_complex); + fluid_dynamics::SpatialTemporalFreeSurfaceIdentificationComplex indicate_free_surface(water_block_complex); /** We can output a method-specific particle data for debug reason */ fluid_particles.addAVariableToWrite("PositionDivergence"); fluid_particles.addAVariableToWrite("SurfaceIndicator"); fluid_dynamics::AdvectionTimeStepSize get_fluid_advection_time_step_size(water_block, U_f); fluid_dynamics::AcousticTimeStepSize get_fluid_time_step_size(water_block); - fluid_dynamics::PressureRelaxationRiemannWithWall pressure_relaxation(water_block_complex_relation); - fluid_dynamics::DensityRelaxationRiemannWithWall density_relaxation(water_block_complex_relation); + fluid_dynamics::PressureRelaxationRiemannWithWall pressure_relaxation(water_block_complex); + fluid_dynamics::DensityRelaxationRiemannWithWall density_relaxation(water_block_complex); //---------------------------------------------------------------------- // File Output //---------------------------------------------------------------------- @@ -222,7 +222,7 @@ int main() { GlobalStaticVariables::physical_time_ = restart_io.readRestartFiles(system.restart_step_); water_block->updateCellLinkedList(); - water_block_complex_relation->updateConfiguration(); + water_block_complex->updateConfiguration(); } body_states_recording.writeToFile(0); write_water_mechanical_energy.writeToFile(0); @@ -232,8 +232,8 @@ int main() size_t number_of_iterations = system.restart_step_; int screen_output_interval = 100; int restart_output_interval = screen_output_interval*10; - Real End_Time = 50.0; /**< End time. */ - Real D_Time = 0.01; /**< Time stamps for output of body states. */ + Real End_Time = 30.0; /**< End time. */ + Real D_Time = 0.1; /**< Time stamps for output of body states. */ Real Dt = 0.0; /**< Default advection time step sizes. */ Real dt = 0.0; /**< Default acoustic time step sizes. */ /** statistics for computing CPU time. */ @@ -282,7 +282,7 @@ int main() /** Update cell linked list and configuration. */ water_block->updateCellLinkedList(); - water_block_complex_relation->updateConfiguration(); + water_block_complex->updateConfiguration(); fluid_observer_contact_relation->updateConfiguration(); indicate_free_surface.parallel_exec(); } diff --git a/cases_test/test_2d_flow_around_cylinder/src/2d_flow_around_cylinder.h b/cases_test/test_2d_flow_around_cylinder/src/2d_flow_around_cylinder.h index 0e5f573aa1..c78e830329 100644 --- a/cases_test/test_2d_flow_around_cylinder/src/2d_flow_around_cylinder.h +++ b/cases_test/test_2d_flow_around_cylinder/src/2d_flow_around_cylinder.h @@ -113,7 +113,7 @@ class Cylinder : public SolidBody { public: Cylinder(SPHSystem& system, std::string body_name) - : SolidBody(system, body_name, new ParticleAdaptation(1.15, 1)) + : SolidBody(system, body_name, new ParticleAdaptation(1.15, 2.0)) { /** Geomtry definition. */ ComplexShape original_body_shape; diff --git a/cases_test/test_2d_flow_around_cylinder/src/CMakeLists.txt b/cases_test/test_2d_flow_around_cylinder/src/CMakeLists.txt index 506fce0f62..59688b721c 100644 --- a/cases_test/test_2d_flow_around_cylinder/src/CMakeLists.txt +++ b/cases_test/test_2d_flow_around_cylinder/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_free_stream_around_cylinder/src/2d_free_stream_around_cylinder.h b/cases_test/test_2d_free_stream_around_cylinder/src/2d_free_stream_around_cylinder.h index 3b38ecc118..859e0f4196 100644 --- a/cases_test/test_2d_free_stream_around_cylinder/src/2d_free_stream_around_cylinder.h +++ b/cases_test/test_2d_free_stream_around_cylinder/src/2d_free_stream_around_cylinder.h @@ -84,7 +84,7 @@ class Cylinder : public SolidBody { public: Cylinder(SPHSystem& system, string body_name) - : SolidBody(system, body_name, new ParticleAdaptation(1.15, 1)) + : SolidBody(system, body_name, new ParticleAdaptation(1.15, 2.0)) { /** Geomtry definition. */ ComplexShape original_body_shape; diff --git a/cases_test/test_2d_free_stream_around_cylinder/src/CMakeLists.txt b/cases_test/test_2d_free_stream_around_cylinder/src/CMakeLists.txt index 506fce0f62..59688b721c 100644 --- a/cases_test/test_2d_free_stream_around_cylinder/src/CMakeLists.txt +++ b/cases_test/test_2d_free_stream_around_cylinder/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_fsi2/src/CMakeLists.txt b/cases_test/test_2d_fsi2/src/CMakeLists.txt index dc47faedda..ee0c2a544e 100644 --- a/cases_test/test_2d_fsi2/src/CMakeLists.txt +++ b/cases_test/test_2d_fsi2/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_fsi2/src/fsi2_case.h b/cases_test/test_2d_fsi2/src/fsi2_case.h index 1cce31de5c..c1d331ed89 100644 --- a/cases_test/test_2d_fsi2/src/fsi2_case.h +++ b/cases_test/test_2d_fsi2/src/fsi2_case.h @@ -165,7 +165,7 @@ class InsertedBody : public SolidBody { public: InsertedBody(SPHSystem& system, std::string body_name) - : SolidBody(system, body_name, new ParticleAdaptation(1.15, 1)) + : SolidBody(system, body_name, new ParticleAdaptation(1.15, 2.0)) { /** Geomtry definition. */ std::vector beam_shape = CreatBeamShape(); @@ -256,7 +256,7 @@ class BeamObserver : public FictitiousBody { public: BeamObserver(SPHSystem& system, std::string body_name) - : FictitiousBody(system, body_name, new ParticleAdaptation(1.15, 1)) + : FictitiousBody(system, body_name, new ParticleAdaptation(1.15, 2.0)) { /** the measuring particle with zero volume */ body_input_points_volumes_.push_back(std::make_pair(0.5 * (BRT + BRB), 0.0)); diff --git a/cases_test/test_2d_heat_transfer/src/CMakeLists.txt b/cases_test/test_2d_heat_transfer/src/CMakeLists.txt index 6191e91220..7c41d09d48 100644 --- a/cases_test/test_2d_heat_transfer/src/CMakeLists.txt +++ b/cases_test/test_2d_heat_transfer/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_hydrostatic_fsi/src/CMakeLists.txt b/cases_test/test_2d_hydrostatic_fsi/src/CMakeLists.txt index 6191e91220..7c41d09d48 100644 --- a/cases_test/test_2d_hydrostatic_fsi/src/CMakeLists.txt +++ b/cases_test/test_2d_hydrostatic_fsi/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_hydrostatic_fsi/src/hydrostatic_fsi.cpp b/cases_test/test_2d_hydrostatic_fsi/src/hydrostatic_fsi.cpp index aa29183213..f508818985 100644 --- a/cases_test/test_2d_hydrostatic_fsi/src/hydrostatic_fsi.cpp +++ b/cases_test/test_2d_hydrostatic_fsi/src/hydrostatic_fsi.cpp @@ -2,7 +2,7 @@ * @file hydrostatic_fsi.cpp * @brief structure deformation due to hydrostatic pressue under gravity. * @details This is the one of the basic test cases - * for understanding SPH method for fluid-structure-interaction (FSI) similation. + * for understanding SPH method for fluid-structure-interaction (FSI) simulation. * @author Yujie Zhu, Chi Zhang and Xiangyu Hu * @version 0.1 */ @@ -23,21 +23,21 @@ BoundingBox system_domain_bounds(Vec2d(-BW, -BW), Vec2d(DL + BW, DH + BW)); Real dp_s = 0.5 * particle_spacing_ref; Vec2d offset = Vec2d(0.0, 0.0); //---------------------------------------------------------------------- -// Define the corner point of water block geomerty. +// Define the corner point of water block geometry. //---------------------------------------------------------------------- Vec2d DamP_lb(0.0, 0.0); /**< Left bottom. */ Vec2d DamP_lt(0.0, Dam_H); /**< Left top. */ Vec2d DamP_rt(Dam_L, Dam_H); /**< Right top. */ Vec2d DamP_rb(Dam_L, 0.0); /**< Right bottom. */ //---------------------------------------------------------------------- -// Define the corner point of gate geomerty. +// Define the corner point of gate geometry. //---------------------------------------------------------------------- Vec2d GateP_lb(-BW, -Gate_width); Vec2d GateP_lt(-BW, 0.0); Vec2d GateP_rt(Dam_L + BW, 0.0); Vec2d GateP_rb(Dam_L + BW, -Gate_width); //---------------------------------------------------------------------- -// Define the geomerty for gate constrian. +// Define the geometry for gate constrian. //---------------------------------------------------------------------- Vec2d ConstrainLP_lb(-BW, -Gate_width); Vec2d ConstrainLP_lt(-BW, 0.0); @@ -136,7 +136,7 @@ class WallBoundary : public SolidBody WallBoundary(SPHSystem& system, std::string body_name) : SolidBody(system, body_name) { - /** Geomerty definition. */ + /** Geometry definition. */ std::vector outer_wall_shape = createOuterWallShape(); std::vector inner_wall_shape = createInnerWallShape(); body_shape_ = new ComplexShape(body_name); @@ -165,9 +165,9 @@ class Gate : public SolidBody { public: Gate(SPHSystem& system, std::string body_name) - : SolidBody(system, body_name, new ParticleAdaptation(1.15, 0)) + : SolidBody(system, body_name, new ParticleAdaptation(1.15, 1.0)) { - /** Geomerty definition. */ + /** Geometry definition. */ std::vector gate_shape = createGateShape(); body_shape_ = new ComplexShape(body_name); body_shape_->addAPolygon(gate_shape, ShapeBooleanOps::add); @@ -212,12 +212,12 @@ class GateConstrain : public BodyPartByParticle GateConstrain(SolidBody* solid_body, std::string constrianed_region_name) : BodyPartByParticle(solid_body, constrianed_region_name) { - /* Geometry defination */ + /* Geometry definition */ std::vector gate_constrain_shape_left = CreatGateConstrainShapeLeft(); body_part_shape_ = new ComplexShape(constrianed_region_name); body_part_shape_->addAPolygon(gate_constrain_shape_left, ShapeBooleanOps::add); - /* Geometry defination */ + /* Geometry definition */ std::vector gate_constrain_shape_right = CreatGateConstrainShapeRight(); body_part_shape_->addAPolygon(gate_constrain_shape_right, ShapeBooleanOps::add); @@ -246,7 +246,7 @@ class Observer : public FictitiousBody { public: Observer(SPHSystem& system, std::string body_name) - : FictitiousBody(system, body_name, new ParticleAdaptation(1.15, 0)) + : FictitiousBody(system, body_name, new ParticleAdaptation(1.15, 1.0)) { /** Add observation point. */ body_input_points_volumes_.push_back(std::make_pair(Vecd(0.5 * Dam_L, -0.5 * Gate_width), 0.0)); @@ -298,9 +298,9 @@ int main() /** Initialize particle acceleration. */ TimeStepInitialization initialize_a_fluid_step(water_block, &gravity); /** Evaluation of fluid density by summation approach. */ - fluid_dynamics::DensitySummationFreeSurfaceComplex update_fluid_desnity(water_block_complex); + fluid_dynamics::DensitySummationFreeSurfaceComplex update_fluid_density(water_block_complex); /** Compute time step size without considering sound wave speed. */ - fluid_dynamics::AdvectionTimeStepSize get_fluid_adevction_time_step_size(water_block, U_max); + fluid_dynamics::AdvectionTimeStepSize get_fluid_advection_time_step_size(water_block, U_max); /** Compute time step size with considering sound wave speed. */ fluid_dynamics::AcousticTimeStepSize get_fluid_time_step_size(water_block); /** Pressure relaxation using verlet time stepping. */ @@ -364,7 +364,7 @@ int main() Real End_Time = 0.5; /**< End time. */ Real D_Time = End_Time / 50.0; /**< time stamps for output. */ Real Dt = 0.0; /**< Default advection time step sizes. */ - Real dt = 0.0; /**< Default accoustic time step sizes. */ + Real dt = 0.0; /**< Default acoustic time step sizes. */ Real dt_s = 0.0; /**< Default acoustic time step sizes for solid. */ tick_count t1 = tick_count::now(); tick_count::interval_t interval; @@ -373,14 +373,14 @@ int main() //---------------------------------------------------------------------- while (GlobalStaticVariables::physical_time_ < End_Time) { - Real integeral_time = 0.0; + Real integration_time = 0.0; /** Integrate time (loop) until the next output time. */ - while (integeral_time < D_Time) + while (integration_time < D_Time) { /** Acceleration due to viscous force and gravity. */ initialize_a_fluid_step.parallel_exec(); - Dt = get_fluid_adevction_time_step_size.parallel_exec(); - update_fluid_desnity.parallel_exec(); + Dt = get_fluid_advection_time_step_size.parallel_exec(); + update_fluid_density.parallel_exec(); /** Update normal direction on elastic body. */ gate_update_normal.parallel_exec(); Real relaxation_time = 0.0; @@ -406,7 +406,7 @@ int main() } average_velocity_and_acceleration.update_averages_.parallel_exec(dt); relaxation_time += dt; - integeral_time += dt; + integration_time += dt; GlobalStaticVariables::physical_time_ += dt; } diff --git a/cases_test/test_2d_oscillating_beam/src/CMakeLists.txt b/cases_test/test_2d_oscillating_beam/src/CMakeLists.txt index c82b8fe14b..6d1d678de7 100644 --- a/cases_test/test_2d_oscillating_beam/src/CMakeLists.txt +++ b/cases_test/test_2d_oscillating_beam/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_oscillating_beam/src/oscillating_beam.cpp b/cases_test/test_2d_oscillating_beam/src/oscillating_beam.cpp index a8839d7fef..7b2a2c7cfb 100644 --- a/cases_test/test_2d_oscillating_beam/src/oscillating_beam.cpp +++ b/cases_test/test_2d_oscillating_beam/src/oscillating_beam.cpp @@ -155,7 +155,7 @@ class BeamObserver : public FictitiousBody { public: BeamObserver(SPHSystem &system, std::string body_name) - : FictitiousBody(system, body_name, new ParticleAdaptation(1.15, 1)) + : FictitiousBody(system, body_name, new ParticleAdaptation(1.15, 2.0)) { body_input_points_volumes_.push_back(std::make_pair(Vecd(PL, 0.0), 0.0)); } diff --git a/cases_test/test_2d_owsc/src/CMakeLists.txt b/cases_test/test_2d_owsc/src/CMakeLists.txt index f50b3b17af..3d4b0dc897 100644 --- a/cases_test/test_2d_owsc/src/CMakeLists.txt +++ b/cases_test/test_2d_owsc/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_particle_generator_single_resolution/CMakeLists.txt b/cases_test/test_2d_particle_generator_single_resolution/CMakeLists.txt index 8d7bdcd827..823b8beca3 100644 --- a/cases_test/test_2d_particle_generator_single_resolution/CMakeLists.txt +++ b/cases_test/test_2d_particle_generator_single_resolution/CMakeLists.txt @@ -1,5 +1,5 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.10) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_particle_generator_single_resolution/particle_generator_single_resolution.cpp b/cases_test/test_2d_particle_generator_single_resolution/particle_generator_single_resolution.cpp index b0df0c5f8c..50a5eb6b24 100644 --- a/cases_test/test_2d_particle_generator_single_resolution/particle_generator_single_resolution.cpp +++ b/cases_test/test_2d_particle_generator_single_resolution/particle_generator_single_resolution.cpp @@ -38,7 +38,7 @@ int main(int ac, char* av[]) // Define simple file input and outputs functions. //---------------------------------------------------------------------- BodyStatesRecordingToVtu inputbody_recording_to_vtu(in_output, { inputbody }); - MeshRecordingToPlt mesh_cell_linked_list_recording(in_output, inputbody, inputbody->mesh_cell_linked_list_); + MeshRecordingToPlt cell_linked_list_recording(in_output, inputbody, inputbody->cell_linked_list_); //---------------------------------------------------------------------- // Define body relation map. // The contact map gives the topological connections between the bodies. @@ -61,7 +61,7 @@ int main(int ac, char* av[]) // First output before the simulation. //---------------------------------------------------------------------- inputbody_recording_to_vtu.writeToFile(0.0); - mesh_cell_linked_list_recording.writeToFile(0.0); + cell_linked_list_recording.writeToFile(0.0); //---------------------------------------------------------------------- // Particle relaxation time stepping start here. //---------------------------------------------------------------------- diff --git a/cases_test/test_2d_plate/src/2d_plate.cpp b/cases_test/test_2d_plate/src/2d_plate.cpp index 422c1b2a60..e6817564e9 100644 --- a/cases_test/test_2d_plate/src/2d_plate.cpp +++ b/cases_test/test_2d_plate/src/2d_plate.cpp @@ -145,7 +145,7 @@ int main() TimeDependentExternalForce external_force(Vec2d(0.0, q / (PT * rho0_s) - gravitational_acceleration)); /** Creat a plate body. */ - Plate *plate_body = new Plate(system, "PlateBody", new ParticleAdaptation(1.15, 0), new ParticleGeneratorDirect()); + Plate *plate_body = new Plate(system, "PlateBody", new ParticleAdaptation(1.15, 1.0), new ParticleGeneratorDirect()); /** elastic soild material properties */ PlateMaterial *plate_material = new PlateMaterial(); /** Creat particles for the elastic body. */ diff --git a/cases_test/test_2d_plate/src/CMakeLists.txt b/cases_test/test_2d_plate/src/CMakeLists.txt index c82b8fe14b..6d1d678de7 100644 --- a/cases_test/test_2d_plate/src/CMakeLists.txt +++ b/cases_test/test_2d_plate/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_poiseuille_flow/src/CMakeLists.txt b/cases_test/test_2d_poiseuille_flow/src/CMakeLists.txt index c82b8fe14b..6d1d678de7 100644 --- a/cases_test/test_2d_poiseuille_flow/src/CMakeLists.txt +++ b/cases_test/test_2d_poiseuille_flow/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_shell/src/2d_shell.cpp b/cases_test/test_2d_shell/src/2d_shell.cpp index 037becd788..6a894b73e8 100644 --- a/cases_test/test_2d_shell/src/2d_shell.cpp +++ b/cases_test/test_2d_shell/src/2d_shell.cpp @@ -149,7 +149,7 @@ int main() TimeDependentExternalForce external_force(Vec2d(0.0, gravitational_acceleration)); /** Creat a Cylinder body. */ - Cylinder *cylinder_body = new Cylinder(system, "CylinderBody", new ParticleAdaptation(1.15, 0), new ParticleGeneratorDirect()); + Cylinder *cylinder_body = new Cylinder(system, "CylinderBody", new ParticleAdaptation(1.15, 1.0), new ParticleGeneratorDirect()); /** elastic soild material properties */ CylinderMaterial *cylinder_material = new CylinderMaterial(); /** Creat particles for the elastic body. */ diff --git a/cases_test/test_2d_shell/src/CMakeLists.txt b/cases_test/test_2d_shell/src/CMakeLists.txt index c82b8fe14b..6d1d678de7 100644 --- a/cases_test/test_2d_shell/src/CMakeLists.txt +++ b/cases_test/test_2d_shell/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_sliding/src/CMakeLists.txt b/cases_test/test_2d_sliding/src/CMakeLists.txt index 6191e91220..7c41d09d48 100644 --- a/cases_test/test_2d_sliding/src/CMakeLists.txt +++ b/cases_test/test_2d_sliding/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_square_droplet/src/CMakeLists.txt b/cases_test/test_2d_square_droplet/src/CMakeLists.txt index 6191e91220..7c41d09d48 100644 --- a/cases_test/test_2d_square_droplet/src/CMakeLists.txt +++ b/cases_test/test_2d_square_droplet/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_square_droplet/src/case.h b/cases_test/test_2d_square_droplet/src/case.h index 2fa18a24bf..c4ce65ea35 100644 --- a/cases_test/test_2d_square_droplet/src/case.h +++ b/cases_test/test_2d_square_droplet/src/case.h @@ -108,7 +108,7 @@ class AirBlock : public FluidBody { public: AirBlock(SPHSystem& sph_system, std::string body_name) - : FluidBody(sph_system, body_name, new ParticleAdaptation(1.3, 0)) + : FluidBody(sph_system, body_name, new ParticleAdaptation(1.3, 1.0)) { /** Geomtry definition. */ std::vector water_block_shape = createWaterBlockShape(); diff --git a/cases_test/test_2d_static_confinement/src/CMakeLists.txt b/cases_test/test_2d_static_confinement/src/CMakeLists.txt index 6191e91220..7c41d09d48 100644 --- a/cases_test/test_2d_static_confinement/src/CMakeLists.txt +++ b/cases_test/test_2d_static_confinement/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_taylor_green/src/CMakeLists.txt b/cases_test/test_2d_taylor_green/src/CMakeLists.txt index 35ac2752c7..4e870b41a2 100644 --- a/cases_test/test_2d_taylor_green/src/CMakeLists.txt +++ b/cases_test/test_2d_taylor_green/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_tethered_dead_fish_in_flow/src/CMakeLists.txt b/cases_test/test_2d_tethered_dead_fish_in_flow/src/CMakeLists.txt index 9d87623088..ffa856d55e 100644 --- a/cases_test/test_2d_tethered_dead_fish_in_flow/src/CMakeLists.txt +++ b/cases_test/test_2d_tethered_dead_fish_in_flow/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_tethered_dead_fish_in_flow/src/tethered_dead_fish_in_flow.cpp b/cases_test/test_2d_tethered_dead_fish_in_flow/src/tethered_dead_fish_in_flow.cpp index c79d495cb8..3370ceb684 100644 --- a/cases_test/test_2d_tethered_dead_fish_in_flow/src/tethered_dead_fish_in_flow.cpp +++ b/cases_test/test_2d_tethered_dead_fish_in_flow/src/tethered_dead_fish_in_flow.cpp @@ -173,7 +173,7 @@ class FishBody : public SolidBody public: FishBody(SPHSystem& system, std::string body_name) - : SolidBody(system, body_name, new ParticleAdaptation(1.15, 1)) + : SolidBody(system, body_name, new ParticleAdaptation(1.15, 2.0)) { std::vector fish_shape = CreatFishShape(cx, cy, fish_length, particle_adaptation_->ReferenceSpacing()); @@ -247,7 +247,7 @@ class Observer : public FictitiousBody { public: Observer(SPHSystem& system, std::string body_name) - : FictitiousBody(system, body_name, new ParticleAdaptation(1.15, 1)) + : FictitiousBody(system, body_name, new ParticleAdaptation(1.15, 2.0)) { /** postion and volume. */ body_input_points_volumes_.push_back(std::make_pair(Vecd(cx + resolution_ref, cy), 0.0)); diff --git a/cases_test/test_2d_throat/src/CMakeLists.txt b/cases_test/test_2d_throat/src/CMakeLists.txt index c82b8fe14b..6d1d678de7 100644 --- a/cases_test/test_2d_throat/src/CMakeLists.txt +++ b/cases_test/test_2d_throat/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_throat/src/throat.cpp b/cases_test/test_2d_throat/src/throat.cpp index 2d07322401..027efff4ec 100644 --- a/cases_test/test_2d_throat/src/throat.cpp +++ b/cases_test/test_2d_throat/src/throat.cpp @@ -26,7 +26,7 @@ Real Re = 0.1; /**< Reynolds number*/ Real mu_f = sqrt(0.25*rho0_f * powerN(0.5*DT, 3)* gravity_g / Re); Real U_f = 0.25*powerN(0.5 * DT, 2)* gravity_g / mu_f; // For low Reynolds number flow the weakly compressible formulation need to -// consider viscousity for artificial sound speed. +// consider viscosity for artificial sound speed. Real c_f = 10.0 * SMAX(U_f, 2.0 * mu_f / rho0_f / DT); Real mu_p_f = 0.6 * mu_f; Real lambda_f = 10.0; @@ -156,12 +156,12 @@ int main() //methods used for time stepping //------------------------------------------------------------------- /** Periodic BCs in x direction. */ - PeriodicConditionInAxisDirectionUsingGhostParticles periodic_condition(fluid_block, 0); + PeriodicConditionInAxisDirectionUsingGhostParticles periodic_condition(fluid_block, xAxis); //evaluation of density by summation approach fluid_dynamics::DensitySummationComplex update_density_by_summation(fluid_block_complex); - //time step size without considering sound wave speed + //time step size without considering sound wave speed and viscosity fluid_dynamics::AdvectionTimeStepSizeForImplicitViscosity get_fluid_advection_time_step_size(fluid_block, U_f); //time step size with considering sound wave speed fluid_dynamics::AcousticTimeStepSize get_fluid_time_step_size(fluid_block); @@ -174,7 +174,7 @@ int main() //-------- common particle dynamics ---------------------------------------- TimeStepInitialization initialize_a_fluid_step(fluid_block, &gravity); fluid_dynamics::ViscousAccelerationWithWall viscous_acceleration(fluid_block_complex); - //computing viscous effect with update velocity directly other than viscous acceleration + //computing viscous effect implicitly and with update velocity directly other than viscous acceleration DampingPairwiseWithWall implicit_viscous_damping(fluid_block_complex, "Velocity", mu_f); @@ -194,12 +194,8 @@ int main() //starting time zero GlobalStaticVariables::physical_time_ = 0.0; - //initial periodic boundary condition - //which copies the particle identifies - //as extra cell linked list form - //periodic regions to the corresponding boundaries - //for building up of extra configuration system.initializeSystemCellLinkedLists(); + //initial periodic boundary condition periodic_condition.ghost_creation_.parallel_exec(); system.initializeSystemConfigurations(); diff --git a/cases_test/test_2d_two_phase_dambreak/src/CMakeLists.txt b/cases_test/test_2d_two_phase_dambreak/src/CMakeLists.txt index 6191e91220..7c41d09d48 100644 --- a/cases_test/test_2d_two_phase_dambreak/src/CMakeLists.txt +++ b/cases_test/test_2d_two_phase_dambreak/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_two_phase_dambreak/src/case.h b/cases_test/test_2d_two_phase_dambreak/src/case.h index 1266be593e..f08842da9c 100644 --- a/cases_test/test_2d_two_phase_dambreak/src/case.h +++ b/cases_test/test_2d_two_phase_dambreak/src/case.h @@ -105,7 +105,7 @@ class AirBlock : public FluidBody { public: AirBlock(SPHSystem& sph_system, std::string body_name) - : FluidBody(sph_system, body_name, new ParticleAdaptation(1.3, 0)) + : FluidBody(sph_system, body_name, new ParticleAdaptation(1.3, 1.0)) { /** Geomtry definition. */ std::vector water_block_shape = createWaterBlockShape(); diff --git a/cases_test/test_2d_wetting_effects/src/CMakeLists.txt b/cases_test/test_2d_wetting_effects/src/CMakeLists.txt index 6191e91220..7c41d09d48 100644 --- a/cases_test/test_2d_wetting_effects/src/CMakeLists.txt +++ b/cases_test/test_2d_wetting_effects/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_2d_wetting_effects/src/case.h b/cases_test/test_2d_wetting_effects/src/case.h index e36eb42505..700f9136ef 100644 --- a/cases_test/test_2d_wetting_effects/src/case.h +++ b/cases_test/test_2d_wetting_effects/src/case.h @@ -108,7 +108,7 @@ class AirBlock : public FluidBody { public: AirBlock(SPHSystem& sph_system, std::string body_name) - : FluidBody(sph_system, body_name, new ParticleAdaptation(1.3, 0)) + : FluidBody(sph_system, body_name, new ParticleAdaptation(1.3, 1.0)) { /** Geomtry definition. */ std::vector water_block_shape = createWaterBlockShape(); diff --git a/cases_test/test_3d_arch/src/3d_arch.cpp b/cases_test/test_3d_arch/src/3d_arch.cpp index 5f64a30075..76caf57739 100644 --- a/cases_test/test_3d_arch/src/3d_arch.cpp +++ b/cases_test/test_3d_arch/src/3d_arch.cpp @@ -150,7 +150,7 @@ int main() TimeDependentExternalForce external_force(Vec3d(0.0, 0.0, gravitational_acceleration)); /** Creat a Cylinder body. */ - Cylinder *cylinder_body = new Cylinder(system, "CylinderBody", new ParticleAdaptation(1.15, 0), new ParticleGeneratorDirect()); + Cylinder *cylinder_body = new Cylinder(system, "CylinderBody", new ParticleAdaptation(1.15, 1.0), new ParticleGeneratorDirect()); /** elastic solid material properties */ CylinderMaterial *cylinder_material = new CylinderMaterial(); /** Creat particles for the elastic body. */ diff --git a/cases_test/test_3d_arch/src/CMakeLists.txt b/cases_test/test_3d_arch/src/CMakeLists.txt index 7798b1c379..7378c74006 100644 --- a/cases_test/test_3d_arch/src/CMakeLists.txt +++ b/cases_test/test_3d_arch/src/CMakeLists.txt @@ -1,5 +1,5 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.10) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_3d_dambreak/src/CMakeLists.txt b/cases_test/test_3d_dambreak/src/CMakeLists.txt index 17328ac913..35a904dd34 100644 --- a/cases_test/test_3d_dambreak/src/CMakeLists.txt +++ b/cases_test/test_3d_dambreak/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_3d_heart_electromechanics/CMakeLists.txt b/cases_test/test_3d_heart_electromechanics/CMakeLists.txt index d03adf43ef..61664cd444 100644 --- a/cases_test/test_3d_heart_electromechanics/CMakeLists.txt +++ b/cases_test/test_3d_heart_electromechanics/CMakeLists.txt @@ -1,5 +1,5 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.10) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) STRING( REGEX REPLACE ".*/(.*)" "\\1" CURRENT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR} ) diff --git a/cases_test/test_3d_muscle_compression/src/CMakeLists.txt b/cases_test/test_3d_muscle_compression/src/CMakeLists.txt index 948f437c54..b768a6b005 100644 --- a/cases_test/test_3d_muscle_compression/src/CMakeLists.txt +++ b/cases_test/test_3d_muscle_compression/src/CMakeLists.txt @@ -1,5 +1,5 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.10) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_3d_muscle_compression_soft_body_contact/src/CMakeLists.txt b/cases_test/test_3d_muscle_compression_soft_body_contact/src/CMakeLists.txt index 7798b1c379..7378c74006 100644 --- a/cases_test/test_3d_muscle_compression_soft_body_contact/src/CMakeLists.txt +++ b/cases_test/test_3d_muscle_compression_soft_body_contact/src/CMakeLists.txt @@ -1,5 +1,5 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.10) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_3d_muscle_compression_soft_body_contact/src/muscle_contact_soft_body_contact.cpp b/cases_test/test_3d_muscle_compression_soft_body_contact/src/muscle_contact_soft_body_contact.cpp index 20dc4fbde1..db2a92b40f 100644 --- a/cases_test/test_3d_muscle_compression_soft_body_contact/src/muscle_contact_soft_body_contact.cpp +++ b/cases_test/test_3d_muscle_compression_soft_body_contact/src/muscle_contact_soft_body_contact.cpp @@ -72,7 +72,7 @@ class MovingPlate : public SolidBody { public: MovingPlate(SPHSystem &system, std::string body_name) - : SolidBody(system, body_name, new ParticleAdaptation(1.15, 1)) + : SolidBody(system, body_name, new ParticleAdaptation(1.15, 1.5)) { body_shape_ = new ComplexShape(body_name); body_shape_->addTriangleMeshShape(CreateMovingPlate(), ShapeBooleanOps::add); @@ -187,9 +187,9 @@ int main() /** Damping with the solid body*/ DampingWithRandomChoice> - muscle_damping(myocardium_body_inner, 0.1, "Velocity", physical_viscosity); + muscle_damping(myocardium_body_inner, 0.2, "Velocity", physical_viscosity); DampingWithRandomChoice> - plate_damping(moving_plate_inner, 0.1, "Velocity", physical_viscosity); + plate_damping(moving_plate_inner, 0.2, "Velocity", physical_viscosity); /** Output */ In_Output in_output(system); BodyStatesRecordingToVtu write_states(in_output, system.real_bodies_); diff --git a/cases_test/test_3d_myocaridum/src/CMakeLists.txt b/cases_test/test_3d_myocaridum/src/CMakeLists.txt index 7798b1c379..7378c74006 100644 --- a/cases_test/test_3d_myocaridum/src/CMakeLists.txt +++ b/cases_test/test_3d_myocaridum/src/CMakeLists.txt @@ -1,5 +1,5 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.10) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_3d_network/CMakeLists.txt b/cases_test/test_3d_network/CMakeLists.txt index f44d1c9075..c046160141 100644 --- a/cases_test/test_3d_network/CMakeLists.txt +++ b/cases_test/test_3d_network/CMakeLists.txt @@ -1,5 +1,5 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.10) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) STRING( REGEX REPLACE ".*/(.*)" "\\1" CURRENT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR} ) diff --git a/cases_test/test_3d_network/sphere.h b/cases_test/test_3d_network/sphere.h index da956bafb8..1321226814 100644 --- a/cases_test/test_3d_network/sphere.h +++ b/cases_test/test_3d_network/sphere.h @@ -32,7 +32,7 @@ class MyPolygonBody : public SolidBody { public: MyPolygonBody(SPHSystem &system, string body_name) - : SolidBody(system, body_name, new ParticleAdaptation(1.15, 0), + : SolidBody(system, body_name, new ParticleAdaptation(1.15, 1.0), new ParticleGeneratorNetwork(Vecd(-1.0, 0.0, 0.0), Vecd(-0.964, 0.0, 0.266), 15, 5.0)) { ComplexShape original_body_shape; diff --git a/cases_test/test_3d_particle_generation/CMakeLists.txt b/cases_test/test_3d_particle_generation/CMakeLists.txt index 794d894256..a555187c0d 100644 --- a/cases_test/test_3d_particle_generation/CMakeLists.txt +++ b/cases_test/test_3d_particle_generation/CMakeLists.txt @@ -1,6 +1,6 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.10) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) STRING( REGEX REPLACE ".*/(.*)" "\\1" CURRENT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR} ) diff --git a/cases_test/test_3d_particle_generation/case.h b/cases_test/test_3d_particle_generation/case.h index 719587d03f..5fb686672c 100644 --- a/cases_test/test_3d_particle_generation/case.h +++ b/cases_test/test_3d_particle_generation/case.h @@ -39,7 +39,7 @@ class ImportedModel : public SolidBody public: ImportedModel(SPHSystem &system, std::string body_name) : SolidBody(system, body_name, - new ParticleSpacingByBodyShape(1.15, 0, 2), + new ParticleSpacingByBodyShape(1.15, 1.0, 2), new ParticleGeneratorMultiResolution()) { /** Geometry definition. */ diff --git a/cases_test/test_3d_particle_generation/particle_generation.cpp b/cases_test/test_3d_particle_generation/particle_generation.cpp index 68b87d1ee9..9a219fd38d 100644 --- a/cases_test/test_3d_particle_generation/particle_generation.cpp +++ b/cases_test/test_3d_particle_generation/particle_generation.cpp @@ -37,7 +37,7 @@ int main(int ac, char* av[]) // Define simple file input and outputs functions. //---------------------------------------------------------------------- BodyStatesRecordingToVtu write_imported_model_to_vtu(in_output, { imported_model }); - MeshRecordingToPlt mesh_cell_linked_list_recording(in_output, imported_model, imported_model->mesh_cell_linked_list_); + MeshRecordingToPlt cell_linked_list_recording(in_output, imported_model, imported_model->cell_linked_list_); //---------------------------------------------------------------------- // Define body relation map. // The contact map gives the topological connections between the bodies. @@ -60,7 +60,7 @@ int main(int ac, char* av[]) update_smoothing_length_ratio.parallel_exec(); write_imported_model_to_vtu.writeToFile(); imported_model->updateCellLinkedList(); - mesh_cell_linked_list_recording.writeToFile(0); + cell_linked_list_recording.writeToFile(0); //---------------------------------------------------------------------- // Particle relaxation time stepping start here. //---------------------------------------------------------------------- diff --git a/cases_test/test_3d_particle_generator_single_resolution/CMakeLists.txt b/cases_test/test_3d_particle_generator_single_resolution/CMakeLists.txt index 4ffec2c62b..90761dc527 100644 --- a/cases_test/test_3d_particle_generator_single_resolution/CMakeLists.txt +++ b/cases_test/test_3d_particle_generator_single_resolution/CMakeLists.txt @@ -1,6 +1,6 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.10) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) STRING( REGEX REPLACE ".*/(.*)" "\\1" CURRENT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR} ) diff --git a/cases_test/test_3d_particle_generator_single_resolution/particle_generator_single_resolution_3D.cpp b/cases_test/test_3d_particle_generator_single_resolution/particle_generator_single_resolution_3D.cpp index 3775c3500b..7bd87e7e6e 100644 --- a/cases_test/test_3d_particle_generator_single_resolution/particle_generator_single_resolution_3D.cpp +++ b/cases_test/test_3d_particle_generator_single_resolution/particle_generator_single_resolution_3D.cpp @@ -36,7 +36,7 @@ int main(int ac, char* av[]) // Define simple file input and outputs functions. //---------------------------------------------------------------------- BodyStatesRecordingToVtu write_imported_model_to_vtu(in_output, { imported_model }); - MeshRecordingToPlt write_mesh_cell_linked_list(in_output, imported_model, imported_model->mesh_cell_linked_list_); + MeshRecordingToPlt write_cell_linked_list(in_output, imported_model, imported_model->cell_linked_list_); //---------------------------------------------------------------------- // Define body relation map. // The contact map gives the topological connections between the bodies. @@ -57,7 +57,7 @@ int main(int ac, char* av[]) relaxation_step_inner.surface_bounding_.parallel_exec(); write_imported_model_to_vtu.writeToFile(0.0); imported_model->updateCellLinkedList(); - write_mesh_cell_linked_list.writeToFile(0.0); + write_cell_linked_list.writeToFile(0.0); //---------------------------------------------------------------------- // Particle relaxation time stepping start here. //---------------------------------------------------------------------- diff --git a/cases_test/test_3d_passive_cantilever/src/CMakeLists.txt b/cases_test/test_3d_passive_cantilever/src/CMakeLists.txt index 25e77d3da5..ea7d556a51 100644 --- a/cases_test/test_3d_passive_cantilever/src/CMakeLists.txt +++ b/cases_test/test_3d_passive_cantilever/src/CMakeLists.txt @@ -1,5 +1,5 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.10) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_3d_passive_cantilever_neohookean/src/CMakeLists.txt b/cases_test/test_3d_passive_cantilever_neohookean/src/CMakeLists.txt index 25e77d3da5..ea7d556a51 100644 --- a/cases_test/test_3d_passive_cantilever_neohookean/src/CMakeLists.txt +++ b/cases_test/test_3d_passive_cantilever_neohookean/src/CMakeLists.txt @@ -1,5 +1,5 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.10) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_3d_pkj_lv_electrocontraction/CMakeLists.txt b/cases_test/test_3d_pkj_lv_electrocontraction/CMakeLists.txt index 92be94a0aa..664f0a1baf 100644 --- a/cases_test/test_3d_pkj_lv_electrocontraction/CMakeLists.txt +++ b/cases_test/test_3d_pkj_lv_electrocontraction/CMakeLists.txt @@ -1,5 +1,5 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.10) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) STRING( REGEX REPLACE ".*/(.*)" "\\1" CURRENT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR} ) diff --git a/cases_test/test_3d_play_simbody/src/CMakeLists.txt b/cases_test/test_3d_play_simbody/src/CMakeLists.txt index 06ec367ed1..f882c71aea 100644 --- a/cases_test/test_3d_play_simbody/src/CMakeLists.txt +++ b/cases_test/test_3d_play_simbody/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_3d_roof/src/3d_roof.cpp b/cases_test/test_3d_roof/src/3d_roof.cpp index 80d395a905..9f6e658ab4 100644 --- a/cases_test/test_3d_roof/src/3d_roof.cpp +++ b/cases_test/test_3d_roof/src/3d_roof.cpp @@ -149,7 +149,7 @@ int main() TimeDependentExternalForce external_force(Vec3d(0.0, 0.0, gravitational_acceleration)); /** Creat a Cylinder body. */ - Cylinder *cylinder_body = new Cylinder(system, "CylinderBody", new ParticleAdaptation(1.15, 0), new ParticleGeneratorDirect()); + Cylinder *cylinder_body = new Cylinder(system, "CylinderBody", new ParticleAdaptation(1.15, 1.0), new ParticleGeneratorDirect()); /** elastic soild material properties */ CylinderMaterial *cylinder_material = new CylinderMaterial(); /** Creat particles for the elastic body. */ diff --git a/cases_test/test_3d_roof/src/CMakeLists.txt b/cases_test/test_3d_roof/src/CMakeLists.txt index fb16d244eb..971c7efd7a 100644 --- a/cases_test/test_3d_roof/src/CMakeLists.txt +++ b/cases_test/test_3d_roof/src/CMakeLists.txt @@ -1,5 +1,5 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.10) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_3d_taylor_bar/src/CMakeLists.txt b/cases_test/test_3d_taylor_bar/src/CMakeLists.txt index f2e32e2c41..7d4d699c51 100644 --- a/cases_test/test_3d_taylor_bar/src/CMakeLists.txt +++ b/cases_test/test_3d_taylor_bar/src/CMakeLists.txt @@ -1,5 +1,5 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.10) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_3d_taylor_bar/src/case.h b/cases_test/test_3d_taylor_bar/src/case.h index edc8c6635b..00abe19320 100644 --- a/cases_test/test_3d_taylor_bar/src/case.h +++ b/cases_test/test_3d_taylor_bar/src/case.h @@ -53,7 +53,7 @@ class Wall : public SolidBody { public: Wall(SPHSystem& system, string body_name) : - SolidBody(system, body_name, new ParticleAdaptation(1.15, 0)) + SolidBody(system, body_name, new ParticleAdaptation(1.15, 1.0)) { body_shape_ = new ComplexShape(body_name); body_shape_->addTriangleMeshShape(CreateHolder(), ShapeBooleanOps::add); @@ -66,7 +66,7 @@ class Column : public SolidBody { public: Column(SPHSystem& system, std::string body_name) : - SolidBody(system, body_name, new ParticleAdaptation(1.3, 0)) + SolidBody(system, body_name, new ParticleAdaptation(1.3, 1.0)) { ComplexShape original_body_shape; original_body_shape.addTriangleMeshShape(CreateColumn(), ShapeBooleanOps::add); diff --git a/cases_test/test_3d_thin_plate/src/CMakeLists.txt b/cases_test/test_3d_thin_plate/src/CMakeLists.txt index fb16d244eb..971c7efd7a 100644 --- a/cases_test/test_3d_thin_plate/src/CMakeLists.txt +++ b/cases_test/test_3d_thin_plate/src/CMakeLists.txt @@ -1,5 +1,5 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.10) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_3d_thin_plate/src/test_3d_thin_plate.cpp b/cases_test/test_3d_thin_plate/src/test_3d_thin_plate.cpp index 2af8a8bdb7..702ce09d78 100644 --- a/cases_test/test_3d_thin_plate/src/test_3d_thin_plate.cpp +++ b/cases_test/test_3d_thin_plate/src/test_3d_thin_plate.cpp @@ -150,7 +150,7 @@ int main() TimeDependentExternalForce external_force(Vec3d(0.0, 0.0, q / (PT * rho0_s) - gravitational_acceleration)); /** Creat a plate body. */ - Plate *plate_body = new Plate(system, "PlateBody", new ParticleAdaptation(1.15, 0), new ParticleGeneratorDirect()); + Plate *plate_body = new Plate(system, "PlateBody", new ParticleAdaptation(1.15, 1.0), new ParticleGeneratorDirect()); /** elastic solid material properties */ PlateMaterial *plate_material = new PlateMaterial(); /** Creat particles for the elastic body. */ diff --git a/cases_test/test_3d_twisting_column/src/CMakeLists.txt b/cases_test/test_3d_twisting_column/src/CMakeLists.txt index fb16d244eb..971c7efd7a 100644 --- a/cases_test/test_3d_twisting_column/src/CMakeLists.txt +++ b/cases_test/test_3d_twisting_column/src/CMakeLists.txt @@ -1,5 +1,5 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.10) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/cases_test/test_3d_twisting_column/src/case.h b/cases_test/test_3d_twisting_column/src/case.h index 061e50e208..c1edfd6477 100644 --- a/cases_test/test_3d_twisting_column/src/case.h +++ b/cases_test/test_3d_twisting_column/src/case.h @@ -30,7 +30,7 @@ int resolution(20); Real rho_0 = 1100.0; /**< Reference density. */ Real poisson = 0.45; /**< Poisson ratio. */ Real Youngs_modulus = 1.7e7; -Real angular_0 = -200.0; +Real angular_0 = -300.0; /** Define the body geometry. */ TriangleMeshShape* CreateCantilever() { @@ -55,7 +55,7 @@ TriangleMeshShape* CreateHolder() class Column : public SolidBody { public: - Column(SPHSystem& system, std::string body_name) : SolidBody(system, body_name, new ParticleAdaptation(1.15, 0)) + Column(SPHSystem& system, std::string body_name) : SolidBody(system, body_name, new ParticleAdaptation(1.15, 1.0)) { body_shape_ = new ComplexShape(body_name); body_shape_->addTriangleMeshShape(CreateCantilever(), ShapeBooleanOps::add); diff --git a/cases_test/test_3d_twisting_column/src/twisting_column.cpp b/cases_test/test_3d_twisting_column/src/twisting_column.cpp index 6d4b5ebc78..2273d7609b 100644 --- a/cases_test/test_3d_twisting_column/src/twisting_column.cpp +++ b/cases_test/test_3d_twisting_column/src/twisting_column.cpp @@ -62,8 +62,8 @@ int main() // Setup time-stepping realted simulation parameters. //---------------------------------------------------------------------- int ite = 0; - Real end_time = 0.1; - Real output_period = end_time / 50.0; + Real end_time = 0.5; + Real output_period = end_time / 250.0; Real dt = 0.0; /** Statistics for computing time. */ tick_count t1 = tick_count::now(); diff --git a/gpuSPHINXsys/gpuSPHinxsys.cu b/gpuSPHINXsys/gpuSPHinxsys.cu new file mode 100644 index 0000000000..2c7f14f0c4 --- /dev/null +++ b/gpuSPHINXsys/gpuSPHinxsys.cu @@ -0,0 +1,198 @@ +/* + * Massoud Rezavand 2019. + * Technical University of Munich + * gpuSPHinxsys - An SPH solver for CUDA enabled GPUs + * + * This is the main interface to communicate between + * Host and Device + */ + +#include"gpuSPHinxsys.cuh" +#include"System.h" +#include"ParticleData.cuh" +#include"ParticleGroup.cuh" +#include"CellList.cuh" +#include"timeStepping.cuh" +#include"dtSizeCalc.cuh" + + +using gpu::access; +using real = gpu::real; +using real3 = gpu::real3; +using real4 = gpu::real4; +using timeStepping = gpu::timeStepping; + + +gpuSPHinxsys::gpuSPHinxsys(Parameters par): + particle_size(par.particle_size), + box_size(par.box_size), + body_force(par.body_force), + probe1(par.probe1), probe2(par.probe2), + U_f(par.U_f), c_f(par.c_f), rho0_f(par.rho0_f), + c_s(par.c_s), rho0_s(par.rho0_s), rho0_g(par.rho0_g) +{ + std::cout << "\t*************************************************" << "\n"; + std::cout << "\t gpuSPHinXsys: An SPH solver on GPUs " << "\n"; + std::cout << "\t*************************************************" << "\n"; + // /*checkCudaErrors*/(cudaMalloc((void **)&devicePar->VelMax, sizeof(real))); +} + +gpuSPHinxsys::~gpuSPHinxsys() +{ + std::cout << "\t*************************************************" << "\n"; + std::cout << "\t gpuSPHinXsys call ended! " << "\n"; + std::cout << "\t*************************************************" << "\n"; + // if(devicePar->VelMax != NULL) cudaFree(devicePar->VelMax); +} + +struct GPU{ + std::shared_ptr pd; + std::shared_ptr sys; + std::shared_ptr nl; //neighborlist +}; + +//initialize a GPU system +GPU initializeGPU(size_t np){ + auto sys = std::make_shared(); + auto pd = std::make_shared(np, sys); + auto pg = std::make_shared(pd, sys, "All"); + auto nl = std::make_shared(pd, pg, sys); + return {pd, sys, nl}; +} + +//Copy particle position_type to device and allocate density and mass +void copyPositionsToDevice(const GPU & gpu_state, + const std::vector &fluidParticles, + const std::vector &wallParticles, + const std::vector &thirdBodyParticles, + float rho0_fluid, + float rho0_gas, + int DIM, + float dp){ + auto pos_type = gpu_state.pd->getPos(access::location::cpu, access::mode::write); + auto rho = gpu_state.pd->getRho(access::location::cpu, access::mode::write); + auto rho0 = gpu_state.pd->getRho0(access::location::cpu, access::mode::write); + auto mass = gpu_state.pd->getMass(access::location::cpu, access::mode::write); + auto vol = gpu_state.pd->getVol(access::location::cpu, access::mode::write); + size_t npFluid = fluidParticles.size()/3; + size_t npWall = wallParticles.size()/3; + size_t npThirdBody = thirdBodyParticles.size()/3; + + for(int i = 0; igetVel(access::location::cpu, access::mode::write); + auto p = gpu_state.pd->getPressure(access::location::cpu, access::mode::write); + std::fill(vel.begin(), vel.end(), real3()); + std::fill(p.begin(), p.end(), real()); +#ifdef _TRANSPORT_VELOCITY_ + auto vel_tv = gpu_state.pd->getVel_tv(access::location::cpu, access::mode::write); + auto F_Pb = gpu_state.pd->getF_Pb(access::location::cpu, access::mode::write); + std::fill(vel_tv.begin(), vel_tv.end(), real3()); + std::fill(F_Pb.begin(), F_Pb.end(), real3()); +#endif +} + +void gpuSPHinxsys::call_gpuSPHinxsys(std::vector &fluidParticles, + std::vector &wallParticles, + std::vector &thirdBodyParticles){ + + int npFluid = fluidParticles.size()/3; + int npWall = wallParticles.size()/3; + int npThirdBody = thirdBodyParticles.size()/3; + int np = npFluid + npWall + npThirdBody; + + printf("Copied to device: npFluid: %d npWall: %d npThirdBody: %d np: %d \n", npFluid, npWall, npThirdBody, np); + + //copy the box size + real3 boxSize = gpu::make_real3(std::get<0>(box_size), + std::get<1>(box_size), + std::get<2>(box_size)); + //copy the external body force + real3 bodyForceTmp = gpu::make_real3(std::get<0>(body_force), + std::get<1>(body_force), + std::get<2>(body_force)); + + int DIM = boxSize.z > real(0.0)?3:2; + + auto gpu_state = initializeGPU(np); + + copyPositionsToDevice(gpu_state, fluidParticles, wallParticles, + thirdBodyParticles, rho0_f, rho0_g, DIM, particle_size); + + //creat a pointer to the timeStepping module + timeStepping::Parameters parTS; + parTS.U_f = U_f; + parTS.box = gpu::Box(boxSize); + parTS.h = 1.3*particle_size; + parTS.c_f = c_f; + parTS.rho0_f = rho0_f; + parTS.bodyForce = bodyForceTmp; + parTS.probe1 = probe1; + parTS.probe2 = probe2; + auto timeStepping_ptr = std::make_shared(gpu_state.pd, gpu_state.nl, gpu_state.sys, parTS); + + initialize_properties(gpu_state); + + gpu_state.pd->sortParticles(); + + //the time integration functions to Run the simulation + timeStepping_ptr->velVerletIntg(); //velocity Verlet scheme +// timeStepping_ptr->dualCriteriaIntg(); //dual criteria scheme +// timeStepping_ptr->solidDynaIntg(); //integration scheme for solid dynamics + + gpu_state.sys->finish(); +} diff --git a/gpuSPHINXsys/gpuSPHinxsys.cuh b/gpuSPHINXsys/gpuSPHinxsys.cuh new file mode 100644 index 0000000000..4756f1e946 --- /dev/null +++ b/gpuSPHINXsys/gpuSPHinxsys.cuh @@ -0,0 +1,45 @@ +/* + * Massoud Rezavand 2019. + * Technical University of Munich + * gpuSPHinxsys - An SPH solver for CUDA enabled GPUs + * + * This is the main interface to communicate between + * Host and Device + */ + +#ifndef GPUSPHINXSYS_CUH +#define GPUSPHINXSYS_CUH + +#include +#include +#include +#include + +class gpuSPHinxsys +{ +public: + + struct Parameters{ + float particle_size; + std::tuple box_size, body_force; + std::tuple probe1, probe2; + float U_f, c_f, c_s, rho0_f, rho0_g, rho0_s; + }; + + gpuSPHinxsys(Parameters par); + ~gpuSPHinxsys(); + + void call_gpuSPHinxsys(std::vector &fluidParticles, + std::vector &wallParticles, + std::vector &imprtdBodyParticles); + +private: + + float particle_size; + std::tuple box_size, body_force; + std::tuple probe1, probe2; + float U_f, c_f, c_s, rho0_f, rho0_g, rho0_s; + +}; + +#endif diff --git a/gpuSPHINXsys/src/Box.cuh b/gpuSPHINXsys/src/Box.cuh new file mode 100644 index 0000000000..43ed0ae1d9 --- /dev/null +++ b/gpuSPHINXsys/src/Box.cuh @@ -0,0 +1,67 @@ +#ifndef BOX_CUH +#define BOX_CUH + +#include +#include"defines.h" +#include"vector.cuh" + +namespace gpu{ + struct Box{ + real3 boxSize, minusInvBoxSize; + + Box():Box(0){} + Box(real L):Box(make_real3(L)){} + Box(real2 L):Box(make_real3(L, 0)){} + Box(real3 L): boxSize(L), minusInvBoxSize(make_real3(real(-1.0)/L.x, real(-1.0)/L.y, real(-1.0)/L.z)){ + if(boxSize.x==real(0.0)) minusInvBoxSize.x = real(0.0); + if(boxSize.y==real(0.0)) minusInvBoxSize.y = real(0.0); + if(boxSize.z==real(0.0)) minusInvBoxSize.z = real(0.0); + } + //Sets the periodicity of each dimension of the box. + inline void setPeriodicity(bool x, bool y, bool z){ + if(!x) minusInvBoxSize.x = 0; + if(!y) minusInvBoxSize.y = 0; + if(!z) minusInvBoxSize.z = 0; + } + inline __host__ __device__ bool isPeriodicX() const{return minusInvBoxSize.x != 0;} + inline __host__ __device__ bool isPeriodicY() const{return minusInvBoxSize.y != 0;} + inline __host__ __device__ bool isPeriodicZ() const{return minusInvBoxSize.z != 0;} + + inline __host__ __device__ real3 apply_pbc(const real3 &r) const{ + //return r - floorf(r/L+real(0.5))*L; //MIC Algorithm + real3 offset = floorf(r*minusInvBoxSize + real(0.5)); //MIC Algorithm + if(!isPeriodicX()) offset.x = 0; + if(!isPeriodicY()) offset.y = 0; + if(!isPeriodicZ()) offset.z = 0; + return r + offset*boxSize; + } + template< class vecType> + inline __device__ __host__ bool isInside(const vecType &pos) const{ + real3 boxSizehalf = real(0.5)*boxSize; + if(pos.x <= -boxSizehalf.x || pos.x > boxSizehalf.x) return false; + if(pos.y <= -boxSizehalf.y || pos.y > boxSizehalf.y) return false; + if(pos.z <= -boxSizehalf.z || pos.z > boxSizehalf.z) return false; + return true; + } + inline __device__ __host__ real getVolume() const{ + if(boxSize.z != real(0.0)) + return boxSize.x*boxSize.y*boxSize.z; + else + return boxSize.x*boxSize.y; + } + + bool operator == (const Box &other) const { + return boxSize.x == other.boxSize.x and + boxSize.y == other.boxSize.y and + boxSize.z == other.boxSize.z and + isPeriodicX() == other.isPeriodicX() and + isPeriodicY() == other.isPeriodicY() and + isPeriodicZ() == other.isPeriodicZ(); + } + bool operator != (const Box &other) const{ + return !(this->operator==(other)); + } + }; + +} +#endif diff --git a/gpuSPHINXsys/src/CellList.cuh b/gpuSPHINXsys/src/CellList.cuh new file mode 100644 index 0000000000..f76bceb23b --- /dev/null +++ b/gpuSPHINXsys/src/CellList.cuh @@ -0,0 +1,568 @@ +/* +References: + +[1] http://developer.download.nvidia.com/assets/cuda/files/particles.pdf + + */ +#ifndef CELLLIST_CUH +#define CELLLIST_CUH + +#include"ParticleData.cuh" +#include"ParticleGroup.cuh" +#include"ParticleSorter.cuh" +#include"Box.cuh" +#include"Grid.cuh" +#include"System.h" +#include +#include +#include +#include + +namespace gpu{ + namespace CellList_ns{ + template + __global__ void fillCellList(InputIterator sortPos, + uint *cellStart, int *cellEnd, + uint currentValidCell, + int *errorFlag, + int N, Grid grid){ + uint id = blockIdx.x*blockDim.x + threadIdx.x; + if(id0){ /*Shared memory target VVV*/ + icell2 = grid.getCellIndex(grid.getCell(make_real3(sortPos[id-1]))); + } + else{ + icell2 = 0; + } + const int ncells = grid.getNumberCells(); + if(icell>=ncells or icell2>=ncells){ + errorFlag[0] = 1; + return; + } + if(icell != icell2 or id == 0){ + cellStart[icell] = id+currentValidCell; + if(id>0) + cellEnd[icell2] = id; + } + if(id == N-1) cellEnd[icell] = N; + } + } + + template + __global__ void fillNeighbourList(const real4* sortPos, + const int *groupIndex, + NeighbourContainer ni, + int *neighbourList, int*nNeighbours, + int maxNeighbours, + real cutOff2, + int N, Box box, + int* tooManyNeighboursFlag){ + int id = blockIdx.x*blockDim.x + threadIdx.x; + if(id>=N) return; + int nneigh = 0; + const int offset = id*maxNeighbours; +#if CUB_PTX_ARCH < 300 + constexpr auto cubModifier = cub::LOAD_DEFAULT; +#else + constexpr auto cubModifier = cub::LOAD_LDG; +#endif + const real3 pi = make_real3(cub::ThreadLoad(sortPos + id)); + ni.set(id); + auto it = ni.begin(); + while(it){ + auto n = *it++; + const int cur_j = n.getInternalIndex(); + const real3 pj = make_real3(cub::ThreadLoad(sortPos + cur_j)); + const real3 rij = box.apply_pbc(pj-pi); + if(dot(rij, rij) <= cutOff2){ + nneigh++; + if(nneigh>=maxNeighbours){ + atomicMax(tooManyNeighboursFlag, nneigh); + return; + } + neighbourList[offset + nneigh-1] = cur_j; + } + } + //Include self interactions + neighbourList[offset + nneigh] = id; + nNeighbours[id] = nneigh+1; + } + } + + class CellList{ + protected: + thrust::device_vector cellStart; + uint currentValidCell; + int currentValidCell_counter; + thrust::device_vector cellEnd; + + Grid grid; + + thrust::device_vector errorFlags; + + thrust::device_vector neighbourList, numberNeighbours; + thrust::device_vector sortPos; + int maxNeighboursPerParticle; + ParticleSorter ps; + + shared_ptr pd; + shared_ptr pg; + shared_ptr sys; + + bool force_next_update = true; + bool rebuildNlist; + + real3 currentCutOff; + + connection numParticlesChangedConnection, posWriteConnection; + cudaEvent_t event; + + + void handleNumParticlesChanged(int Nnew){ + sys->log("[CellList] Number particles changed signal handled."); + int numberParticles = pg->getNumberParticles(); + if(neighbourList.size()){ + neighbourList.resize(numberParticles*maxNeighboursPerParticle); + numberNeighbours.resize(numberParticles); + } + currentValidCell_counter = -1; + force_next_update = true; + } + + void handlePosWriteRequested(){ + sys->log("[CellList] Issuing a list update after positions were written to."); + force_next_update = true; + } + + struct NeighbourListOffsetFunctor{ + NeighbourListOffsetFunctor(int str, int* groupIndex): + stride(str), groupIndex(groupIndex){} + int stride; + int *groupIndex; + inline __host__ __device__ int operator()(const int &index) const{ + return groupIndex[index]*stride; + } + }; + + using CountingIterator = cub::CountingInputIterator; + using StrideIterator = cub::TransformInputIterator; + + public: + + CellList(shared_ptr pd, + shared_ptr sys): + CellList(pd, std::make_shared(pd, sys), sys){} + + CellList(shared_ptr pd, + shared_ptr pg, + shared_ptr sys): pd(pd), pg(pg), sys(sys), ps(sys), currentCutOff(real3()){ + sys->log("[CellList] Created"); + + maxNeighboursPerParticle = 32; + + pd->getNumParticlesChangedSignal()->connect([this](int Nnew){this->handleNumParticlesChanged(Nnew);}); + pd->getPosWriteRequestedSignal()->connect([this](){this->handlePosWriteRequested();}); + + CudaSafeCall(cudaEventCreateWithFlags(&event, cudaEventDisableTiming)); + + currentValidCell_counter = -1; + CudaCheckError(); + } + + ~CellList(){ + sys->log("[CellList] Destroyed"); + numParticlesChangedConnection.disconnect(); + posWriteConnection.disconnect(); + } + + struct NeighbourListData{ + int * neighbourList; + int *numberNeighbours; + StrideIterator particleStride = StrideIterator(CountingIterator(0), NeighbourListOffsetFunctor(0, nullptr)); + }; + + NeighbourListData getNeighbourList(cudaStream_t st = 0){ + if(currentCutOff.x != currentCutOff.y or + currentCutOff.x != currentCutOff.z or + currentCutOff.z != currentCutOff.y){ + sys->log("[CellList] Invalid cutoff in getNeighbourList: %f %f %f", + currentCutOff.x, currentCutOff.y, currentCutOff.z); + throw std::invalid_argument("Cannot use NeighbourList with a different cutOff in each direction"); + } + const int numberParticles = pg->getNumberParticles(); + if(rebuildNlist){ + rebuildNeighbourList(st); + } + else{ + auto pos = pd->getPos(access::location::gpu, access::mode::read); + auto posGroupIterator = pg->getPropertyInputIterator(pos.raw(), access::location::gpu); + ps.applyCurrentOrder(posGroupIterator, sortPos.begin(), numberParticles, st); + } + NeighbourListData nl; + nl.neighbourList = thrust::raw_pointer_cast(neighbourList.data()); + nl.numberNeighbours = thrust::raw_pointer_cast(numberNeighbours.data()); + nl.particleStride = StrideIterator(CountingIterator(0), + NeighbourListOffsetFunctor(maxNeighboursPerParticle, + ps.getSortedIndexArray(numberParticles))); + return nl; + } + + bool needsRebuild(Box box, real3 cutOff){ + if(force_next_update) return true; + if(cutOff.x != currentCutOff.x) return true; + if(cutOff.y != currentCutOff.y) return true; + if(cutOff.z != currentCutOff.z) return true; + if(box != grid.box) return true; + return false; + } + + void updateNeighbourList(Grid in_grid, real3 cutOff, cudaStream_t st = 0){ + sys->log("[CellList] Updating list"); + if(!isGridAndCutOffValid(in_grid, cutOff)) + throw std::runtime_error("CellList encountered an invalid grid and/or cutoff"); + if(needsRebuild(in_grid.box, cutOff) == false) + return; + else + currentCutOff = cutOff; + pd->hintSortByHash(in_grid.box, cutOff); + force_next_update = false; + this->grid = in_grid; + sys->log("[CellList] Using %d %d %d cells", grid.cellDim.x, grid.cellDim.y, grid.cellDim.z); + resizeCellListToCurrentGrid(); + updateCurrentValidCell(); + updateOrderAndStoreInSortPos(st); + fillCellList(st); + CudaCheckError(); + rebuildNlist = true; + } + + void updateNeighbourList(Box box, real cutOff, cudaStream_t st = 0){ + updateNeighbourList(box, make_real3(cutOff), st); + } + + void updateNeighbourList(Box box, real3 cutOff, cudaStream_t st = 0){ + Grid a_grid = Grid(box, cutOff); + int3 cellDim = a_grid.cellDim; + if(cellDim.x < 3) cellDim.x = 3; + if(cellDim.y < 3) cellDim.y = 3; + if(box.boxSize.z > real(0.0) && cellDim.z < 3) cellDim.z = 3; + a_grid = Grid(box, cellDim); + updateNeighbourList(a_grid, cutOff, st); + } + + //This accesor function is part of CellList only, not part of the NeighbourList interface + //They allow to obtain a reference to the cell list structures to use them outside + struct CellListData{ + //[all particles in cell 0, all particles in cell 1,..., all particles in cell ncells] + //cellStart[i] stores the index of the first particle in cell i (in internal index) + //cellEnd[i] stores the last particle in cell i (in internal index) + //So the number of particles in cell i is cellEnd[i]-cellStart[i] + const uint * cellStart; + const int * cellEnd; + const real4 *sortPos; //Particle positions in internal index + const int* groupIndex; //Transformation between internal indexes and group indexes + Grid grid; + uint VALID_CELL; + }; + CellListData getCellList(){ + this->updateNeighbourList(grid, currentCutOff); + CellListData cl; + try{ + cl.cellStart = thrust::raw_pointer_cast(cellStart.data()); + cl.cellEnd = thrust::raw_pointer_cast(cellEnd.data()); + cl.sortPos = thrust::raw_pointer_cast(sortPos.data()); + } + catch(thrust::system_error &e){ + sys->log("[CellList] Thrust could not access cellList arrays with error: %s", e.what()); + } + int numberParticles = pg->getNumberParticles(); + cl.groupIndex = ps.getSortedIndexArray(numberParticles); + cl.grid = grid; + cl.VALID_CELL = currentValidCell; + return cl; + } + + class NeighbourContainer; //forward declaration for befriending + private: + class NeighbourIterator; //forward declaration for befriending + + //Neighbour is a small accesor for NeighbourIterator + //Represents a particle, you can ask for its index and position + struct Neighbour{ + __device__ Neighbour(const Neighbour &other): + internal_i(other.internal_i){ + groupIndex = other.groupIndex; + sortPos = other.sortPos; + } + //Index in the internal sorted index of the cell list + __device__ int getInternalIndex(){return internal_i;} + //Index in the particle group + __device__ int getGroupIndex(){return groupIndex[internal_i];} + __device__ real4 getPos(){return cub::ThreadLoad(sortPos+internal_i);} + + private: + int internal_i; + const int* groupIndex; + const real4* sortPos; + friend class NeighbourIterator; + __device__ Neighbour(int i, const int* gi, const real4* sp): + internal_i(i), groupIndex(gi), sortPos(sp){} + }; + + //This forward iterator must be constructed by NeighbourContainer, + class NeighbourIterator: + public thrust::iterator_adaptor< + NeighbourIterator, + int, Neighbour, + thrust::any_system_tag, + thrust::forward_device_iterator_tag, + Neighbour, int + >{ + friend class thrust::iterator_core_access; + + int j; //Current neighbour index + CellListData nl; + + int ci; //Current cell + + int3 celli; //Cell of particle i + int lastParticle; //Index of last particle in current cell + + uint VALID_CELL; + //Take j to the start of the next cell and return true, if no more cells remain then return false + __device__ bool nextcell(){ + const bool is2D = nl.grid.cellDim.z<=1; + if(ci >= (is2D?9:27)) return false; + bool empty = true; + do{ + int3 cellj = celli; + cellj.x += ci%3-1; + cellj.y += (ci/3)%3-1; + cellj.z = is2D?0:(celli.z+ci/9-1); + cellj = nl.grid.pbc_cell(cellj); + const bool isPeriodicCellInNonPeriodicBox = (!nl.grid.box.isPeriodicX() and abs(cellj.x-celli.x)>1) or + (!nl.grid.box.isPeriodicY() and abs(cellj.y-celli.y)>1) or + (!nl.grid.box.isPeriodicZ() and abs(cellj.z-celli.z)>1); + if(!isPeriodicCellInNonPeriodicBox){ + const int icellj = nl.grid.getCellIndex(cellj); + const uint cs = nl.cellStart[icellj]; + empty = cs= (is2D?9:27)) return !empty; + }while(empty); + return true; + } + + //Take j to the next neighbour + __device__ void increment(){ + if(++j == lastParticle) j = nextcell()?j:-1; + } + + __device__ Neighbour dereference() const{ + return Neighbour(j, nl.groupIndex, nl.sortPos); + } + + public: + //j==-1 means there are no more neighbours and the iterator is invalidated + __device__ operator bool(){ return j!= -1;} + + private: + friend class NeighbourContainer; + __device__ NeighbourIterator(int i, CellListData nl, bool begin): + j(-2), nl(nl), ci(0), lastParticle(-1), VALID_CELL(nl.VALID_CELL){ + if(begin){ + celli = nl.grid.getCell(make_real3(cub::ThreadLoad(nl.sortPos+i))); + increment(); + } + else j = -1; + } + //enables searching the neighbors of a given point: probe + __device__ NeighbourIterator(real3 probe, CellListData nl, bool begin): + j(-2), nl(nl), ci(0), lastParticle(-1), VALID_CELL(nl.VALID_CELL){ + if(begin){ + celli = nl.grid.getCell(probe); + increment(); + } + else j = -1; + } + }; + + public: + //This is a pseudocontainer which only purpose is to provide begin() and end() NeighbourIterators for a certain particle + struct NeighbourContainer{ + int my_i = -1; + CellListData nl; + NeighbourContainer(CellListData nl): nl(nl){} + __device__ void set(int i){this->my_i = i;} + __device__ NeighbourIterator begin(){return NeighbourIterator(my_i, nl, true);} + //to search neighbors for a given spatial point, e.g. a porbe + __device__ NeighbourIterator begin(real3 probe){return NeighbourIterator(probe, nl, true);} + __device__ NeighbourIterator end(){ return NeighbourIterator(my_i, nl, false);} + }; + + NeighbourContainer getNeighbourContainer(){ + auto nl = getCellList(); + return NeighbourContainer(nl); + } + + const real4* getPositionIterator(){ + return thrust::raw_pointer_cast(sortPos.data()); + } + const int* getGroupIndexIterator(){ + auto nl = getCellList(); + return nl.groupIndex; + } + + private: + + bool isGridAndCutOffValid(Grid in_grid, real3 cutOff){ + if(in_grid.cellDim.x < 3 or + in_grid.cellDim.y < 3 or + (in_grid.cellDim.z < 3 and in_grid.box.boxSize.z != real(0.0))){ + sys->log("[CellList] I cannot work with less than 3 cells per dimension!"); + return false; + } + //In the case of 3 cells per direction all the particles are checked anyway + if(in_grid.cellDim.x != 3 or + in_grid.cellDim.y != 3 or + (in_grid.cellDim.z != 3 and in_grid.box.boxSize.z != real(0.0))){ + + if(in_grid.cellSize.x < cutOff.x or + in_grid.cellSize.y < cutOff.y or + (in_grid.cellSize.z < cutOff.z and in_grid.cellSize.z>1)){ + sys->log("[CellList] The cell size cannot be smaller than the cut off."); + return false; + } + } + return true; + } + + void tryToResizeCellListToCurrentGrid(){ + const int ncells = grid.getNumberCells(); + if(cellStart.size()!= ncells){ + currentValidCell_counter = -1; + cellStart.resize(ncells); + } + if(cellEnd.size()!= ncells) cellEnd.resize(ncells); + CudaCheckError(); + } + + void resizeCellListToCurrentGrid(){ + try{ + tryToResizeCellListToCurrentGrid(); + } + catch(...){ + sys->log("[CellList] Raised exception at cell list resize"); + throw; + } + } + + void updateCurrentValidCell(){ + const int numberParticles = pg->getNumberParticles(); + const bool isCounterUninitialized = (currentValidCell_counter < 0); + const ullint nextStepMaximumValue = ullint(numberParticles)*(currentValidCell_counter+2); + constexpr ullint maximumStorableValue = ullint(std::numeric_limits::max())-1ull; + const bool nextStepOverflows = (nextStepMaximumValue >= maximumStorableValue); + if(isCounterUninitialized or nextStepOverflows){ + currentValidCell = numberParticles; + currentValidCell_counter = 1; + const int ncells = grid.getNumberCells(); + const int Nthreads = 512; + const int Nblocks= ncells/Nthreads + ((ncells%Nthreads)?1:0); + auto it = thrust::make_counting_iterator(0); + fillWithGPU<<>>(thrust::raw_pointer_cast(cellStart.data()), + it, 0, ncells); + CudaCheckError(); + } + else{ + currentValidCell_counter++; + currentValidCell = uint(numberParticles)*currentValidCell_counter; + } + } + + void updateOrderAndStoreInSortPos(cudaStream_t st){ + const int numberParticles = pg->getNumberParticles(); + auto pos = pd->getPos(access::location::gpu, access::mode::read); + auto posGroupIterator = pg->getPropertyInputIterator(pos.begin(), access::location::gpu); + ps.updateOrderByCellHash(posGroupIterator, + numberParticles, + grid.box, grid.cellDim, st); + CudaCheckError(); + sortPos.resize(numberParticles); + ps.applyCurrentOrder(posGroupIterator, thrust::raw_pointer_cast(sortPos.data()), numberParticles, st); + CudaCheckError(); + } + + void fillCellList(cudaStream_t st){ + const int numberParticles = pg->getNumberParticles(); + const int Nthreads = 512; + int Nblocks = numberParticles/Nthreads + ((numberParticles%Nthreads)?1:0); + sys->log("[CellList] fill Cell List, currentValidCell: %d", currentValidCell); + int h_errorFlag = 0; + errorFlags.resize(1); + int *d_errorFlag = thrust::raw_pointer_cast(errorFlags.data()); + CudaSafeCall(cudaMemcpyAsync(d_errorFlag, &h_errorFlag, sizeof(int), cudaMemcpyHostToDevice, st)); + CellList_ns::fillCellList<<>>(thrust::raw_pointer_cast(sortPos.data()), + thrust::raw_pointer_cast(cellStart.data()), + thrust::raw_pointer_cast(cellEnd.data()), + currentValidCell, + d_errorFlag, + numberParticles, + grid); + CudaSafeCall(cudaMemcpyAsync(&h_errorFlag, d_errorFlag, sizeof(int), cudaMemcpyDeviceToHost, st)); + CudaSafeCall(cudaEventRecord(event, st)); + CudaSafeCall(cudaEventSynchronize(event)); + if(h_errorFlag > 0){ + sys->log("[CellList] NaN positions found during construction"); + throw std::overflow_error("CellList encountered NaN positions"); + } + CudaCheckError(); + } + + void rebuildNeighbourList(cudaStream_t st){ + const int numberParticles = pg->getNumberParticles(); + neighbourList.resize(numberParticles*maxNeighboursPerParticle); + numberNeighbours.resize(numberParticles); + rebuildNlist = false; + int Nthreads=128; + int Nblocks=numberParticles/Nthreads + ((numberParticles%Nthreads)?1:0); + int flag; + do{ + flag = 0; + auto neighbourList_ptr = thrust::raw_pointer_cast(neighbourList.data()); + auto numberNeighbours_ptr = thrust::raw_pointer_cast(numberNeighbours.data()); + errorFlags.resize(1); + int* d_tooManyNeighboursFlag = thrust::raw_pointer_cast(errorFlags.data()); + sys->log("[CellList] fill Neighbour List"); + CellList_ns::fillNeighbourList<<>>(thrust::raw_pointer_cast(sortPos.data()), + this->getGroupIndexIterator(), + this->getNeighbourContainer(), + neighbourList_ptr, numberNeighbours_ptr, + maxNeighboursPerParticle, + currentCutOff.x*currentCutOff.x, + numberParticles, grid.box, + d_tooManyNeighboursFlag); + CudaSafeCall(cudaMemcpyAsync(&flag, d_tooManyNeighboursFlag, sizeof(int), cudaMemcpyDeviceToHost, st)); + CudaSafeCall(cudaEventRecord(event, st)); + CudaSafeCall(cudaEventSynchronize(event)); + if(flag != 0){ + this->maxNeighboursPerParticle += 32; + sys->log("[CellList] Resizing list to %d neighbours per particle", + maxNeighboursPerParticle); + int zero = 0; + CudaSafeCall(cudaMemcpyAsync(d_tooManyNeighboursFlag, &zero, sizeof(int), cudaMemcpyHostToDevice, st)); + neighbourList.resize(numberParticles*maxNeighboursPerParticle); + } + }while(flag!=0); + } + + }; +} +#endif + + diff --git a/gpuSPHINXsys/src/GPUUtils.cuh b/gpuSPHINXsys/src/GPUUtils.cuh new file mode 100644 index 0000000000..ab53170e0b --- /dev/null +++ b/gpuSPHINXsys/src/GPUUtils.cuh @@ -0,0 +1,38 @@ +#ifndef GPUUTILS_CUH +#define GPUUTILS_CUH + + +namespace gpu{ + + //Fill any iterator with the same value. It is much faster than cudaMemset + template + __global__ void fillWithGPU(OutputIterator array, T value, int N){ + int id = blockIdx.x*blockDim.x + threadIdx.x; + if(id>=N) return; + + array[id] = value; + } + + template + __global__ void fillWithGPU(OutputIterator array, Iterator indexIterator, T value, int N){ + int id = blockIdx.x*blockDim.x + threadIdx.x; + if(id>=N) return; + int i = indexIterator[id]; + array[i] = value; + } + + + template + __global__ void copyGPU(InputIterator d_in, OutputIterator d_out, int N){ + int id = blockIdx.x*blockDim.x + threadIdx.x; + if(id>=N) return; + + d_out[id] = d_in[id]; + } + + +} + + + +#endif diff --git a/gpuSPHINXsys/src/Grid.cuh b/gpuSPHINXsys/src/Grid.cuh new file mode 100644 index 0000000000..91f2820a04 --- /dev/null +++ b/gpuSPHINXsys/src/Grid.cuh @@ -0,0 +1,167 @@ +#ifndef GRID_CUH +#define GRID_CUH + +#include "Box.cuh" +#include "vector.cuh" +#include +namespace gpu{ + + struct Grid{ + int3 gridPos2CellIndex; + + int3 cellDim; + real3 cellSize; + real3 invCellSize; + Box box; + real cellVolume; + Grid(): Grid(Box(), make_int3(0,0,0)){} + + Grid(Box box, real3 minCellSize): + Grid(box, make_int3(box.boxSize/minCellSize)){} + Grid(Box box, real minCellSize): + Grid(box, make_real3(minCellSize)){} + + Grid(Box box, int3 in_cellDim): + box(box), + cellDim(in_cellDim){ + + if(cellDim.z == 0) cellDim.z = 1; + cellSize = box.boxSize/make_real3(cellDim); + invCellSize = 1.0/cellSize; + if(box.boxSize.z == real(0.0)) invCellSize.z = 0; + + gridPos2CellIndex = make_int3( 1, + cellDim.x, + cellDim.x*cellDim.y); + cellVolume = cellSize.x*cellSize.y; + if(cellDim.z > 1) cellVolume *= cellSize.z; + } + template + inline __host__ __device__ int3 getCell(const VecType &r) const{ + // return int( (p+0.5L)/cellSize ) + int3 cell = make_int3((box.apply_pbc(make_real3(r)) + real(0.5)*box.boxSize)*invCellSize); + //Anti-Traquinazo guard, you need to explicitly handle the case where a particle + // is exactly at the box limit, AKA -L/2. This is due to the precision loss when + // casting int from floats, which gives non-correct results very near the cell borders. + // This is completly neglegible in all cases, except with the cell 0, that goes to the cell + // cellDim, which is catastrophic. + //Doing the previous operation in double precision (by changing 0.5f to 0.5) also works, but it is a bit of a hack and the performance appears to be the same as this. + //TODO: Maybe this can be skipped if the code is in double precision mode + if(cell.x==cellDim.x) cell.x = 0; + if(cell.y==cellDim.y) cell.y = 0; + if(cell.z==cellDim.z) cell.z = 0; + return cell; + } + + inline __host__ __device__ int getCellIndex(const int3 &cell) const{ + return dot(cell, gridPos2CellIndex); + } + + inline __host__ __device__ int getCellIndex(const int2 &cell) const{ + return dot(cell, make_int2(gridPos2CellIndex)); + } + + inline __host__ __device__ int3 pbc_cell(const int3 &cell) const{ + int3 cellPBC; + cellPBC.x = pbc_cell_coord<0>(cell.x); + cellPBC.y = pbc_cell_coord<1>(cell.y); + cellPBC.z = pbc_cell_coord<2>(cell.z); + return cellPBC; + } + + template + inline __host__ __device__ int pbc_cell_coord(int cell) const{ + int ncells = 0; + if(coordinate == 0){ + ncells = cellDim.x; + } + if(coordinate == 1){ + ncells = cellDim.y; + } + + if(coordinate == 2){ + ncells = cellDim.z; + } + + if(cell <= -1) cell += ncells; + else if(cell >= ncells) cell -= ncells; + return cell; + } + + inline __host__ __device__ int getNumberCells() const{ return cellDim.x*cellDim.y*cellDim.z;} + + inline __host__ __device__ real getCellVolume(int3 cell) const{ return getCellVolume();} + + inline __host__ __device__ real getCellVolume() const{ return cellVolume;} + + inline __host__ __device__ real3 getCellSize(int3 cell) const{return getCellSize();} + + inline __host__ __device__ real3 getCellSize() const{return cellSize;} + + inline __host__ __device__ real3 distanceToCellCenter(real3 pos, int3 cell) const{ + return box.apply_pbc(pos + box.boxSize*real(0.5) - cellSize*(make_real3(cell)+real(0.5))); + } + + inline __host__ __device__ real3 distanceToCellUpperLeftCorner(real3 pos, int3 cell) const{ + return box.apply_pbc(pos + box.boxSize*real(0.5) - cellSize*make_real3(cell)); + } + + }; + + //Looks for the closest (equal or greater) number of nodes of the form 2^a*3^b*5^c*7^d*11^e + int3 nextFFTWiseSize3D(int3 size){ + int* cdim = &size.x; + + int max_dim = std::max({size.x, size.y, size.z}); + + int n= 14; + int n5 = 6; //number higher than this are not reasonable... + int n7 = 5; + int n11 = 4; + auto powint = [](uint64_t base, uint64_t exp){if(exp==0) return uint64_t(1); uint64_t res = base; fori(0,exp-1) res*=base; return res;}; + + std::vector tmp(n*n*n5*n7*n11, 0); + do{ + tmp.resize(n*n*n5*n7*n11, 0); + fori(0,n)forj(0,n) + for(int k=0; k4 or k7>5 or k>6) continue; + + uint64_t id = i+n*j+n*n*k+n*n*n5*k7+n*n*n5*n7*k11; + tmp[id] = 0; + //Current fft wise size + uint64_t number = uint64_t(powint(2,i))*powint(3,j)*powint(5,k)*powint(7, k7); + //This is to prevent overflow + if(!(i==n-1 and j==n-1 and k==n5-1 and k7==n7-1 and k110 && (i==0))) continue; + tmp[id] = number; + } + n++; + /*Sort this array in ascending order*/ + std::sort(tmp.begin(), tmp.end()); + }while(tmp.back()=powint(2,31)) set = -1; + cdim[j] = set; + break; + } + } + return size; + } +} + +#endif diff --git a/gpuSPHINXsys/src/Kernel.cuh b/gpuSPHINXsys/src/Kernel.cuh new file mode 100644 index 0000000000..1ab41491cf --- /dev/null +++ b/gpuSPHINXsys/src/Kernel.cuh @@ -0,0 +1,121 @@ +#ifndef KERNEL_CUH +#define KERNEL_CUH + +#include"defines.h" + +namespace gpu{ + namespace KernelFunction{ + struct M4CubicSpline{ + inline __device__ __host__ real operator()(real3 r12, real h, real boxZ){ + real r2 = dot(r12, r12); + real r = sqrtf(r2); + + real q = abs(r)/h; + + if(q >= real(2.0)) return real(0.0); + + real twomq = real(2.0)-q; + + real W = twomq*twomq*twomq; + if(q <= real(1.0)){ + real onemq = real(1.0) - q; + real onemq3 = onemq*onemq*onemq; + W -= real(4.0)*onemq3; + } + + W *= real(1.0)/(h*h*h*real(4.0)*real(M_PI)); + return W; + } + inline __device__ __host__ real3 gradient(real3 r12, real h, real boxZ){ + + real r2 = dot(r12, r12); + real r = sqrtf(r2); + + real invh = real(1.0)/h; + + real q = r*invh; + if(q >= real(2.0) ) return make_real3(0.0); + + real invh3 = invh*invh*invh; + + real3 gradW = -invh3*invh3*real(3.0)*r12/(real(4.0)*real(M_PI)); + + + if(q <= real(1.0)){ + gradW *= (real(-4.0)*h + real(3.0)*r); + } + else if(q <= real(2.0) ){ + real f = (real(2.0)*h - r); + gradW *= f*f; + + } + return gradW; + } + + static inline __device__ __host__ real getCutOff(real h){ + return real(2.0)*h; + } + + }; + + struct Wendland_C4{ + inline __device__ __host__ real operator()(real3 r12, real h, real boxZ){ + real r2 = dot(r12, r12); + real r = sqrtf(r2); + + real q = abs(r)/h; + real C = real(); + real val = real(); + + + if(boxZ == real(0.0)) + C = real(1.0)/real(M_PI)/(h*h)*real(7./4.);//for 2D + else + C = real(1.0)/real(M_PI)/(h*h*h)*real(21./16.);//for 3D + + // (1-q/2)^4 + real factor = (real(1.0)-real(0.5)*q)*(real(1.0)-real(0.5)*q) + *(real(1.0)-real(0.5)*q)*(real(1.0)-real(0.5)*q); + + if(q < real(2.0)) + val = C*factor*(real(1.0) + real(2.0)*q); + else + val = real(0.0); + + return val; + } + inline __device__ __host__ real gradient(real3 r12, real h, real boxZ){ + + real r2 = dot(r12, r12); + real r = sqrtf(r2); + + real q = abs(r)/h; + real C = real(); + real val = real(); + + if(boxZ == real(0.0)) + C = real(1.0)/real(M_PI)/(h*h)*real(7./4.);//for 2D + else + C = real(1.0)/real(M_PI)/(h*h*h)*real(21./16.);//for 3D + + // (1-q/2)^3 + real factor = (real(1.0)-real(0.5)*q)*(real(1.0)-real(0.5)*q) + *(real(1.0)-real(0.5)*q); + + if(q < real(2.0)) + val = -real(5.0)*C*factor*q/h/r; + else + val = real(0.0); + + return val; + } + + static inline __device__ __host__ real getCutOff(real h){ + return real(2.0)*h; + } + + }; + } +} + +#endif diff --git a/gpuSPHINXsys/src/Log.h b/gpuSPHINXsys/src/Log.h new file mode 100644 index 0000000000..957f01ef18 --- /dev/null +++ b/gpuSPHINXsys/src/Log.h @@ -0,0 +1,63 @@ +#ifndef LOG_H +#define LOG_H +#include +#include +#include +#include +namespace gpu{ + namespace Logging{ + using ElementType = std::tuple; + enum LogLevel{CRITICAL=0, ERROR, EXCEPTION, WARNING, MESSAGE, STDERR, STDOUT, + DEBUG, DEBUG1, DEBUG2, DEBUG3, DEBUG4, DEBUG5, DEBUG6, DEBUG7}; + +#ifdef MAXLOGLEVEL + constexpr int maxLogLevel = MAXLOGLEVEL; +#else + constexpr int maxLogLevel = 6; +#endif + + ElementType getLogLevelInfo(int level){ + static const std::map printMap{ + {CRITICAL , std::make_tuple(stderr, "\e[101m[CRITICAL] ", "\e[0m\n")}, + {ERROR , std::make_tuple(stderr, "\e[91m[ERROR] \e[0m", "\n")}, + {EXCEPTION , std::make_tuple(stderr, "\e[1m\e[91m[EXCEPTION] \e[0m", "\n")}, + {WARNING , std::make_tuple(stderr, "\e[93m[WARNING] \e[0m", "\n")}, + {MESSAGE , std::make_tuple(stderr, "\e[92m[MESSAGE] \e[0m", "\n")}, + {STDERR , std::make_tuple(stderr, " ", "\n")}, + {STDOUT , std::make_tuple(stdout, " ", "\n")}, + {DEBUG , std::make_tuple(stderr, "\e[96m[ DEBUG ] \e[0m", "\n")}, + {DEBUG1 , std::make_tuple(stderr, "\e[96m[ DEBUG ] \e[0m", "\n")}, + {DEBUG2 , std::make_tuple(stderr, "\e[96m[ DEBUG ] \e[0m", "\n")}, + {DEBUG3 , std::make_tuple(stderr, "\e[96m[ DEBUG ] \e[0m", "\n")}, + {DEBUG4 , std::make_tuple(stderr, "\e[96m[ DEBUG ] \e[0m", "\n")}, + {DEBUG5 , std::make_tuple(stderr, "\e[96m[ DEBUG ] \e[0m", "\n")}, + {DEBUG6 , std::make_tuple(stderr, "\e[96m[ DEBUG ] \e[0m", "\n")}, + {DEBUG7 , std::make_tuple(stderr, "\e[96m[ DEBUG ] \e[0m", "\n")} + }; + return printMap.at(level); + } + + template + static inline void log(char const *fmt, ...){ + if(level<=maxLogLevel){ + const auto currentLevelInfo = getLogLevelInfo(level); + auto stream = std::get<0>(currentLevelInfo); + auto prefix = std::get<1>(currentLevelInfo); + auto suffix = std::get<2>(currentLevelInfo); + va_list args; + va_start(args, fmt); + fprintf(stream, "%s", prefix); + vfprintf(stream, fmt, args); + fprintf(stream, "%s", suffix); + va_end(args); + } + } + + template + static inline void log(const std::string &msg){ + log("%s", msg.c_str()); + } + + } +} +#endif diff --git a/gpuSPHINXsys/src/ParticleData.cuh b/gpuSPHINXsys/src/ParticleData.cuh new file mode 100644 index 0000000000..a3d33eb02b --- /dev/null +++ b/gpuSPHINXsys/src/ParticleData.cuh @@ -0,0 +1,318 @@ +#ifndef PARTICLEDATA_CUH +#define PARTICLEDATA_CUH +#include"System.h" + +#include"Property.cuh" +#include"ParticleSorter.cuh" +#include"vector.cuh" + +#include +#include +#include +#include +#include +#include +#include + +//List here all the properties with this syntax: +/* ((PropertyName, propertyName, TYPE)) \ */ +//The preprocessor ensures that they are included wherever is needed +#define ALL_PROPERTIES_LIST ((Pos, pos, real4)) \ + ((Id, id, int)) \ + ((Mass, mass, real)) \ + ((Vol, vol, real)) \ + ((Force, force, real4)) \ + ((Energy, energy, real)) \ + ((Vel, vel, real3)) \ + ((Vel_tv, vel_tv, real3)) \ + ((F_Pb, f_Pb, real3)) \ + ((Rho, rho, real)) \ + ((Rho0, rho0, real)) \ + ((Drho, drho, real)) \ + ((Sigma0, sigma0, real)) \ + ((Pressure, pressure, real)) + +namespace gpu{ + + template + using signal = typename nod::unsafe_signal; + + using connection = nod::connection; + + //Get the Name (first letter capital) from a tuple in the property list +#define PROPNAME_CAPS(tuple) BOOST_PP_TUPLE_ELEM(3, 0 ,tuple) + //Get the name (no capital) from a tuple in the property list +#define PROPNAME(tuple) BOOST_PP_TUPLE_ELEM(3, 1 ,tuple) + //Get the type from a tuple in the property list +#define PROPTYPE(tuple) BOOST_PP_TUPLE_ELEM(3, 2 ,tuple) + +//This macro iterates through all properties applying some macro +#define PROPERTY_LOOP(macro) BOOST_PP_SEQ_FOR_EACH(macro, _, ALL_PROPERTIES_LIST) + + + + class ParticleData{ + public: + //Hints to ParticleData about how to perform different task. Mainly how to sort the particles. + struct Hints{ + bool orderByHash = false; + Box hash_box = Box(make_real3(128)); + real3 hash_cutOff = make_real3(10.0); + bool orderByType = false; + + }; + + private: + shared_ptr sys; +#define DECLARE_PROPERTIES_T(type, name) Property name; +#define DECLARE_PROPERTIES(r,data, tuple) DECLARE_PROPERTIES_T(PROPTYPE(tuple), PROPNAME(tuple)) + + //Declare all property containers + PROPERTY_LOOP(DECLARE_PROPERTIES) + + int numberParticles; + shared_ptr> reorderSignal = std::make_shared>(); + shared_ptr> numParticlesChangedSignal = std::make_shared>(); + +//Declare write access signals for all properties +#define DECLARE_SIGNAL_PROPERTIES_T(type, name) shared_ptr> BOOST_PP_CAT(name,WriteRequestedSignal = std::make_shared>();) +#define DECLARE_SIGNAL_PROPERTIES(r,data, tuple) DECLARE_SIGNAL_PROPERTIES_T(PROPTYPE(tuple), PROPNAME(tuple)) + //Declare all property write signals + PROPERTY_LOOP(DECLARE_SIGNAL_PROPERTIES) + + + + std::shared_ptr particle_sorter; + thrust::host_vector originalOrderIndexCPU; + bool originalOrderIndexCPUNeedsUpdate; + Hints hints; + + public: + ParticleData() = delete; + ParticleData(int numberParticles, shared_ptr sys); + ~ParticleData(){ + sys->log("[ParticleData] Destroyed"); + } + + + //Generate getters for all properties except ID +#define GET_PROPERTY_T(Name,name) GET_PROPERTY_R(Name,name) +#define GET_PROPERTY_R(Name, name) \ + inline auto get ## Name(access::location dev, access::mode mode) -> decltype(name.data(dev,mode)){ \ + if(!name.isAllocated()) name.resize(numberParticles); \ + if(!name.isAllocated() or mode==access::mode::write or mode==access::mode::readwrite){ \ + (*name ## WriteRequestedSignal)(); \ + } \ + return name.data(dev,mode); \ + } \ + +#define GET_PROPERTY(r, data, tuple) GET_PROPERTY_T(PROPNAME_CAPS(tuple), PROPNAME(tuple)) + + //Define getProperty() functions for all properties in list + PROPERTY_LOOP(GET_PROPERTY) + + //Generate getters for all properties except ID +#define GET_PROPERTY_IF_ALLOC_T(Name,name) GET_PROPERTY_IF_ALLOC_R(Name,name) +#define GET_PROPERTY_IF_ALLOC_R(Name, name) \ + inline auto get ## Name ## IfAllocated(access::location dev, access::mode mode) -> decltype(name.data(dev,mode)){ \ + if(!name.isAllocated()){ \ + decltype(name.data(dev,mode)) tmp; \ + return tmp; \ + } \ + return this->get ## Name(dev,mode); \ + } \ + +#define GET_PROPERTY_IF_ALLOC(r, data, tuple) GET_PROPERTY_IF_ALLOC_T(PROPNAME_CAPS(tuple), PROPNAME(tuple)) + + //Define getProperty() functions for all properties in list + PROPERTY_LOOP(GET_PROPERTY_IF_ALLOC) + + //Generate isPropAllocated for all properties +#define IS_ALLOCATED_T(Name, name) IS_ALLOCATED_R(Name, name) +#define IS_ALLOCATED_R(Name, name) \ + inline bool is##Name##Allocated(){return name.isAllocated();} \ + +#define IS_ALLOCATED(r, data, tuple) IS_ALLOCATED_T(PROPNAME_CAPS(tuple), PROPNAME(tuple)) + + PROPERTY_LOOP(IS_ALLOCATED) + + void sortParticles(); + + const int * getIdOrderedIndices(access::location dev){ + sys->log("[ParticleData] Id order requested for %d (0=cpu, 1=gpu)", dev); + auto id = getId(access::location::gpu, access::mode::read); + int *sortedIndex = particle_sorter->getIndexArrayById(id.raw(), numberParticles); + sys->log("[ParticleData] Id reorder completed."); + if(dev == access::location::gpu){ + return sortedIndex; + } + else{ + if(originalOrderIndexCPUNeedsUpdate){ + sys->log("[ParticleData] Updating CPU original order array"); + originalOrderIndexCPU.resize(numberParticles); + int * sortedIndexCPU = thrust::raw_pointer_cast(originalOrderIndexCPU.data()); + CudaSafeCall(cudaMemcpy(sortedIndexCPU, + sortedIndex, + numberParticles*sizeof(int), + cudaMemcpyDeviceToHost)); + originalOrderIndexCPUNeedsUpdate = false; + return sortedIndexCPU; + } + else{ + return thrust::raw_pointer_cast(originalOrderIndexCPU.data()); + } + } + } + + template + void applyCurrentOrder(InputIterator in, OutputIterator out, int numElements){ + particle_sorter->applyCurrentOrder(in, out, numElements); + } + + const int * getCurrentOrderIndexArray(){ + return particle_sorter->getSortedIndexArray(numberParticles); + } + + void changeNumParticles(int Nnew); + + int getNumParticles(){ return this->numberParticles;} + + shared_ptr> getReorderSignal(){ + sys->log("[ParticleData] Reorder signal requested"); + return this->reorderSignal; + } + +#define GET_PROPERTY_SIGNAL_T(Name,name) GET_PROPERTY_SIGNAL_R(Name,name) +#define GET_PROPERTY_SIGNAL_R(Name, name) \ + inline shared_ptr> get ## Name ## WriteRequestedSignal(){ \ + return this->name ## WriteRequestedSignal; \ + } +#define GET_PROPERTY_SIGNAL(r, data, tuple) GET_PROPERTY_SIGNAL_T(PROPNAME_CAPS(tuple), PROPNAME(tuple)) + PROPERTY_LOOP(GET_PROPERTY_SIGNAL) + + void emitReorder(){ + sys->log("[ParticleData] Emitting reorder signal..."); + (*this->reorderSignal)(); + } + + shared_ptr> getNumParticlesChangedSignal(){ + return this->numParticlesChangedSignal; + } + + + void hintSortByHash(Box hash_box, real3 hash_cutOff){ + hints.orderByHash = true; + hints.hash_box = hash_box; + hints.hash_cutOff = hash_cutOff; + + } + + private: + + void emitNumParticlesChanged(int Nnew){ + (*numParticlesChangedSignal)(Nnew); + } + + }; + + +#define INIT_PROPERTIES_T(NAME, name) , name(BOOST_PP_STRINGIZE(NAME), sys) +#define INIT_PROPERTIES(r,data, tuple) INIT_PROPERTIES_T(PROPNAME_CAPS(tuple), PROPNAME(tuple)) + + ParticleData::ParticleData(int numberParticles, shared_ptr sys): + numberParticles(numberParticles), + originalOrderIndexCPUNeedsUpdate(true), + sys(sys) + PROPERTY_LOOP(INIT_PROPERTIES) + { + sys->log("[ParticleData] Created with %d particles.", numberParticles); + + id.resize(numberParticles); + CudaCheckError(); + + auto id_prop = id.data(access::location::gpu, access::mode::write); + + cub::CountingInputIterator ci(0); + thrust::copy(thrust::cuda::par, + ci, ci + numberParticles, + id_prop.begin()); + + particle_sorter = std::make_shared(sys); + } + + //Sort the particles to improve a certain kind of access pattern. + void ParticleData::sortParticles(){ + sys->log("[ParticleData] Sorting particles..."); + + { + auto posPtr = pos.data(access::gpu, access::read); + if(hints.orderByHash || !hints.orderByType){ + int3 cellDim = make_int3(hints.hash_box.boxSize/hints.hash_cutOff); + particle_sorter->updateOrderByCellHash(posPtr.raw(), numberParticles, hints.hash_box, cellDim); + } + + } + //This macro reorders to the newest order a property given its name +#define APPLY_CURRENT_ORDER(r, data, tuple) APPLY_CURRENT_ORDER_R(PROPNAME(tuple)) +#define APPLY_CURRENT_ORDER_R(name) { \ + if(name.isAllocated()){ \ + auto devicePtr = name.data(access::gpu, access::write); \ + auto device_altPtr = name.getAltGPUBuffer(); \ + particle_sorter->applyCurrentOrder(devicePtr.raw(), device_altPtr, numberParticles); \ + name.swapInternalBuffers(); \ + } \ + } + //Apply current order to all allocated properties. See APPLY_CURRENT_ORDER macro + PROPERTY_LOOP(APPLY_CURRENT_ORDER) + + originalOrderIndexCPUNeedsUpdate = true; + this->emitReorder(); + } + + + + void ParticleData::changeNumParticles(int Nnew){ + sys->log("[ParticleData] CHANGE PARTICLES FUNCTIONALITY NOT IMPLEMENTED YET!!!"); + sys->log("[ParticleData] Adding/Removing particles..."); + this->numberParticles = Nnew; + pos.resize(Nnew); +#define RESIZE_PROPERTY_R(name) {if(this->name.isAllocated()){this->name.resize(this->numberParticles);}} +#define RESIZE_PROPERTY(r, data, tuple) RESIZE_PROPERTY_R(PROPNAME(tuple)) + + PROPERTY_LOOP(RESIZE_PROPERTY) + + originalOrderIndexCPUNeedsUpdate = true; + this->emitNumParticlesChanged(Nnew); + } +} + +#undef ALL_PROPERTIES_LIST +#undef PROPNAME_CAPS +#undef PROPNAME +#undef PROPTYPE +#undef PROPERTY_LOOP +#undef DECLARE_PROPERTIES_T +#undef DECLARE_PROPERTIES +#undef DECLARE_SIGNAL_PROPERTIES_T +#undef DECLARE_SIGNAL_PROPERTIES +#undef GET_PROPERTY_T +#undef GET_PROPERTY_R +#undef GET_PROPERTY +#undef GET_PROPERTY_SIGNAL_T +#undef GET_PROPERTY_SIGNAL_R +#undef GET_PROPERTY_SIGNAL +#undef IS_ALLOCATED_T +#undef IS_ALLOCATED_R +#undef IS_ALLOCATED +#undef GET_PROPERTY_IF_ALLOC +#undef GET_PROPERTY_IF_ALLOC_T +#undef GET_PROPERTY_IF_ALLOC_R +#undef APPLY_CURRENT_ORDER +#undef APPLY_CURRENT_ORDER_R +#undef RESIZE_PROPERTY_R +#undef RESIZE_PROPERTY + + + + +#endif diff --git a/gpuSPHINXsys/src/ParticleGroup.cuh b/gpuSPHINXsys/src/ParticleGroup.cuh new file mode 100644 index 0000000000..b383704a6f --- /dev/null +++ b/gpuSPHINXsys/src/ParticleGroup.cuh @@ -0,0 +1,471 @@ +#ifndef PARTICLEGROUP_CUH +#define PARTICLEGROUP_CUH + +#include"System.h" +#include"ParticleData.cuh" +#include +#include +#include + +namespace gpu{ + /*Small structs that encode different ways of selecting a certain set of particles, + i.e by type, spatial location, ID... + A particle selector must have an isSelected method that takes a particle index (not ID) and + a ParticleData reference. + It will be called for all particles except when not needed (i.e with All).*/ + namespace particle_selector{ + //Select all the particles + class All{ + public: + All(){} + static constexpr bool isSelected(int particleIndex, shared_ptr &pd){ + return true; + } + }; + + class None{ + public: + None(){} + static constexpr bool isSelected(int particleIndex, shared_ptr &pd){ + return false; + } + }; + + //Select particles with ID in a certain range + class IDRange{ + int firstID, lastID; + public: + IDRange(int first, int last): + firstID(first), + lastID(last){ + } + + bool isSelected(int particleIndex, shared_ptr &pd){ + int particleID = (pd->getId(access::cpu, access::read).raw())[particleIndex]; + return particleID>=firstID && particleID<=lastID; + } + + }; + + //Select particles inside a certain rectangular region of the simulation box. + class Domain{ + Box domain, simulationBox; + real3 origin; + public: + Domain(real3 origin, Box domain, Box simulationBox): + origin(origin), + domain(domain), + simulationBox(simulationBox){ + + } + bool isSelected(int particleIndex, shared_ptr &pd){ + real3 pos = make_real3(pd->getPos(access::cpu, access::read).raw()[particleIndex]); + pos = simulationBox.apply_pbc(pos); + pos += origin; + return domain.isInside(pos); + } + }; + + //Select particles by type (pos.w) + class Type{ + std::vector typesToSelect; + public: + Type(int type): typesToSelect({type}){ + } + + Type(std::vector typesToSelect): typesToSelect(typesToSelect){ + } + bool isSelected(int particleIndex, shared_ptr &pd){ + int type_i = int(pd->getPos(access::cpu, access::read).raw()[particleIndex].w); + for(auto type: typesToSelect){ + if(type_i==type) return true; + } + return false; + } + + + + + }; + }; + + namespace ParticleGroup_ns{ + //Updates the indices of the particles in a group using pd->getIdOrderedIndices() + __global__ void updateGroupIndices(//An array that stores the indices of the particles in the group per id. + const int * __restrict__ id2index, + //Out: the current ParticleData indices of the particles in the group + int * __restrict__ particlesIndices, + //In: Ids of the particle sin the group + const int * __restrict__ particlesIds, + int numberParticles + ){ + int tid = blockIdx.x*blockDim.x + threadIdx.x; + if(tid >= numberParticles) return; + int id = particlesIds[tid]; + int index = id2index[id]; + particlesIndices[tid] = index; + } + + } + // Keeps track of a certain subset of particles in a ParticleData entity + // You can ask ParticleGroup to return you: + // -The particle IDs of its members. + // -The current indices of its members in the ParticleData arrays. + + // You can ask for the indices as a raw memory pointer or as a custom iterator. + // Asking for the raw memory is a risky bussiness, as this array may not even exists (i.e if all particles in the system are in the group, it might decide to not create it, as it would be unnecessary). In this case, you will get a nullptr. + + class ParticleGroup{ + shared_ptr pd; + shared_ptr sys; + cudaStream_t st = 0; + //A list of the particle indices and ids of the group (updated to current order) + thrust::device_vector myParticlesIndicesGPU, myParticlesIdsGPU; + thrust::host_vector myParticlesIndicesCPU; + + bool updateHostVector = true; + bool needsIndexListUpdate = false; + + //number of particles in group and in all system (pd) + int numberParticles, totalParticles; + + bool allParticlesInGroup = false; + + std::string name; + + connection reorderConnection; + + public: + /*Defaults to all particles in group*/ + ParticleGroup(shared_ptr pd, shared_ptr sys, std::string name = std::string("noName")); + + /*Create the group from a selector*/ + template + ParticleGroup(ParticleSelector selector, + shared_ptr pd, shared_ptr sys, std::string name = std::string("noName")); + + /*Create the group from a list of particle IDs*/ + template + ParticleGroup(InputIterator begin, InputIterator end, + shared_ptr pd, shared_ptr sys, + std::string name = std::string("noName")); + + ~ParticleGroup(){ + sys->log("[ParticleGroup] Group %s destroyed", name.c_str()); + CudaCheckError(); + reorderConnection.disconnect(); + if(st) CudaSafeCall(cudaStreamDestroy(st)); + } + + //Remove all particles from the group + void clear(){ + this->numberParticles = 0; + } + //Add particles to the group via an array with ids + void addParticlesById(access::location loc, const int *ids, int N); + //Add particles to the group via an array with the current indices of the particles in pd (faster) + void addParticlesByCurrentIndex(access::location loc, const int *indices, int N); + //Update index list if needed + void computeIndexList(bool forceUpdate = false); + + void handleReorder(){ + sys->log("[ParticleGroup] Handling reorder signal in group %s", this->name.c_str()); + if(!allParticlesInGroup && numberParticles > 0){ + needsIndexListUpdate = true; + } + } + + //Access the index array only if it is not a nullptr (AKA if the group does not contain all particles) + struct IndexAccess{ + IndexAccess(const int * indices):indices(indices){} + inline __host__ __device__ int operator()(const int &i) const{ + if(!indices) return i; + else return indices[i]; + } + private: + const int * indices; + }; + + //Transform sequential indexing to indices of particle sin group + using IndexIterator = cub::TransformInputIterator>; + + static IndexIterator make_index_iterator(const int *indices){ + return IndexIterator(cub::CountingInputIterator(0), IndexAccess(indices)); + } + + //Get a raw memory pointer to the index list if it exists + inline const int * getIndicesRawPtr(access::location loc){ + if(this->allParticlesInGroup || numberParticles == 0 ) return nullptr; + this->computeIndexList(); + int *ptr; + switch(loc){ + case access::location::cpu: + if(updateHostVector){ + myParticlesIndicesCPU = myParticlesIndicesGPU; + updateHostVector = false; + } + ptr = thrust::raw_pointer_cast(myParticlesIndicesCPU.data()); + break; + case access::location::gpu: + ptr = thrust::raw_pointer_cast(myParticlesIndicesGPU.data()); + break; + default: + ptr = nullptr; + } + return ptr; + } + + //Get an iterator with the indices of particles in this group + inline IndexIterator getIndexIterator(access::location loc){ + auto ptr = getIndicesRawPtr(loc); + return make_index_iterator(ptr); + } + + //Simply reads an iterator, optionally a cub cache mode can be selected + template + struct TransformIndex{ + TransformIndex(const Iterator &it):it(it){} + using value_type = typename std::iterator_traits::value_type; + inline __host__ __device__ value_type operator()(const int &i) const{ + return it[i]; + } + private: + cub::CacheModifiedInputIterator it; + }; + + //Reads an iterator transforming sequential indexing to indices of the particles in the group + template + using accessIterator = cub::TransformInputIterator::value_type, + TransformIndex, + IndexIterator>; + private: + template + accessIterator make_access_iterator(const Iterator &it, access::location loc){ + return accessIterator(this->getIndexIterator(loc), + TransformIndex(it)); + } + + public: + + //Returns an iterator that will have size pg->getNumberParticles() and will iterate over the + // particles in the group. + //For example, If a group contains only the particle with id=10, passing pd->getPos(...).raw() to this function + // will return an iterator so that iterator[0] = pos[10]; and it will take into account any possible reordering of the pos array. + template + accessIterator getPropertyInputIterator(const Iterator & property, + access::location loc){ + return this->make_access_iterator(property, loc); + } + + int getNumberParticles(){ + return this->numberParticles; + } + + std::string getName(){ return this->name;} + }; + + + + template + ParticleGroup::ParticleGroup(ParticleSelector selector, + shared_ptr pd, shared_ptr sys, std::string name): + pd(pd), sys(sys), name(name){ + sys->log("[ParticleGroup] Group %s", name.c_str()); + totalParticles = pd->getNumParticles(); + /*Create ID list in CPU*/ + std::vector ids; + for(int i=0;ilog("[ParticleGroup] Group %s contains %d particles.", + name.c_str(), numberParticles); + /*Handle the case in which all particles belong to the group*/ + if(numberParticles==totalParticles){ + allParticlesInGroup = true; + } + else{ + myParticlesIdsGPU = ids; + + //Connect to reorder signal, index list needs to be updated each time a reorder occurs + reorderConnection = pd->getReorderSignal()->connect([this](){this->handleReorder();}); + //Allocate + myParticlesIndicesGPU.resize(numberParticles); + //Force update (creation) of the index list) + this->computeIndexList(true); + } + } + + //Specialization of a particle group with an All selector + template<> + ParticleGroup::ParticleGroup(particle_selector::All selector, + shared_ptr pd, shared_ptr sys, + std::string name): + pd(pd), sys(sys), name(name){ + sys->log("[ParticleGroup] Group %s created with All selector",name.c_str()); + this->allParticlesInGroup = true; + this->totalParticles = pd->getNumParticles(); + this->numberParticles = totalParticles; + + sys->log("[ParticleGroup] Group %s contains %d particles.", + name.c_str(), numberParticles); + } + //Specialization of an empty particle group + template<> + ParticleGroup::ParticleGroup(particle_selector::None selector, + shared_ptr pd, shared_ptr sys, + std::string name): + pd(pd), sys(sys), name(name){ + this->allParticlesInGroup = false; + this->totalParticles = pd->getNumParticles(); + this->numberParticles = 0; + reorderConnection = pd->getReorderSignal()->connect([this](){this->handleReorder();}); + } + + + //Constructor of ParticleGroup when an ID list is provided + template + ParticleGroup::ParticleGroup(InputIterator begin, InputIterator end, + shared_ptr pd, shared_ptr sys, + std::string name): + pd(pd), sys(sys), name(name){ + sys->log("[ParticleGroup] Group %s created from ID list.", name.c_str()); + numberParticles = std::distance(begin, end); + sys->log("[ParticleGroup] Group %s contains %d particles.", name.c_str(), numberParticles); + this->totalParticles = pd->getNumParticles(); + + if(numberParticles == totalParticles){ + this->allParticlesInGroup = true; + } + else{ + reorderConnection = pd->getReorderSignal()->connect([this](){this->handleReorder();}); + + //Create ID list in CPU + myParticlesIdsGPU.assign(begin, end); + myParticlesIndicesGPU.resize(numberParticles); + /*Force update (creation) of the index list)*/ + this->computeIndexList(true); + } + } + + + + //If no selector is provided, All is assumed + ParticleGroup::ParticleGroup(shared_ptr pd, shared_ptr sys, + std::string name): + ParticleGroup(particle_selector::All(), pd, sys, name){} + + + //This is trivial with pd->getIdOrderedIndices()! + //Handle a reordering of the particles (which invalids the previous relation between IDs and indices) + void ParticleGroup::computeIndexList(bool forceUpdate){ + + if(numberParticles==0) return; + if(this->needsIndexListUpdate || forceUpdate){//Update only if needed + sys->log("[ParticleGroup] Updating group %s after last particle sorting", name.c_str()); + + + const int *id2index = pd->getIdOrderedIndices(access::location::gpu); + + int Nthreads=(numberParticles>=512)?512:numberParticles; + int Nblocks=numberParticles/Nthreads + ((numberParticles%Nthreads)?1:0); + + int *myParticlesIndicesGPU_ptr = thrust::raw_pointer_cast(myParticlesIndicesGPU.data()); + int *myParticlesIdsGPU_ptr = thrust::raw_pointer_cast(myParticlesIdsGPU.data()); + ParticleGroup_ns::updateGroupIndices<<>>(id2index, + myParticlesIndicesGPU_ptr, + myParticlesIdsGPU_ptr, + numberParticles); + this->needsIndexListUpdate = false; + updateHostVector = true; + sys->log("[ParticleGroup] Updating group %s DONE!", name.c_str()); + } + } + //Add particles to the group via an array with ids + void ParticleGroup::addParticlesById(access::location loc, const int *ids, int N){ + sys->log("[ParticleGroup] Adding %d particles to group %s via ids!", N, name.c_str()); + int numberParticlesPrev = numberParticles; + numberParticles += N; + myParticlesIndicesGPU.resize(numberParticles); + myParticlesIdsGPU.resize(numberParticles); + + const int *id2index = pd->getIdOrderedIndices(access::location::gpu); + int Nthreads=(N>=128)?128:N; + int Nblocks=N/Nthreads + ((N%Nthreads)?1:0); + + int *myParticlesIndicesGPU_ptr = thrust::raw_pointer_cast(myParticlesIndicesGPU.data()); + int *myParticlesIdsGPU_ptr = thrust::raw_pointer_cast(myParticlesIdsGPU.data()); + if(!st) CudaSafeCall(cudaStreamCreate(&st)); + + auto copyKind = cudaMemcpyDeviceToDevice; + if(loc==access::location::cpu) copyKind = cudaMemcpyHostToDevice; + + CudaSafeCall(cudaMemcpyAsync(myParticlesIdsGPU_ptr+numberParticlesPrev, ids, + N*sizeof(int), copyKind, st)); + + const int *d_ids = ids; + cudaStream_t upSt = st; + if(loc==access::location::cpu){ + d_ids = myParticlesIdsGPU_ptr + numberParticlesPrev; + upSt = 0; + } + + ParticleGroup_ns::updateGroupIndices<<>>(id2index, + myParticlesIndicesGPU_ptr + numberParticlesPrev, + d_ids, + N); + CudaSafeCall(cudaStreamSynchronize(st)); + + } + + namespace ParticleGroup_ns{ + __global__ void IdsFromIndices(const int *indices, const int *index2Id, int* groupParticleIds, int N){ + int tid = blockIdx.x*blockDim.x + threadIdx.x; + if(tid>=N) return; + int index = indices[tid]; + int id = index2Id[index]; + groupParticleIds[tid] = id; + } + + } + //Add particles to the group via an array with the current indices of the particles in pd (faster) + void ParticleGroup::addParticlesByCurrentIndex(access::location loc, const int *indices, int N){ + sys->log("[ParticleGroup] Adding %d particles to group %s via indices!", N, name.c_str()); + if(N==0) return; + int numberParticlesPrev = numberParticles; + numberParticles += N; + myParticlesIndicesGPU.resize(numberParticles); + myParticlesIdsGPU.resize(numberParticles); + + const int *id2index = pd->getIdOrderedIndices(access::location::gpu); + int Nthreads=(N>=128)?128:N; + int Nblocks=N/Nthreads + ((N%Nthreads)?1:0); + + int *myParticlesIndicesGPU_ptr = thrust::raw_pointer_cast(myParticlesIndicesGPU.data()); + int *myParticlesIdsGPU_ptr = thrust::raw_pointer_cast(myParticlesIdsGPU.data()); + + if(!st) CudaSafeCall(cudaStreamCreate(&st)); + auto copyKind = cudaMemcpyDeviceToDevice; + if(loc==access::location::cpu) copyKind = cudaMemcpyHostToDevice; + + CudaSafeCall(cudaMemcpyAsync(myParticlesIndicesGPU_ptr+numberParticlesPrev, indices, + N*sizeof(int), copyKind, st)); + auto index2id = pd->getId(access::location::gpu, access::mode::read); + + const int *d_indices = indices; + cudaStream_t upSt = st; + if(loc == access::location::cpu){ + d_indices = myParticlesIndicesGPU_ptr+numberParticlesPrev; + upSt = 0; + } + ParticleGroup_ns::IdsFromIndices<<>>(d_indices, + index2id.raw(), + myParticlesIdsGPU_ptr+numberParticlesPrev, + N); + CudaSafeCall(cudaStreamSynchronize(st)); + } + +} +#endif diff --git a/gpuSPHINXsys/src/ParticleSorter.cuh b/gpuSPHINXsys/src/ParticleSorter.cuh new file mode 100644 index 0000000000..953ca18a1b --- /dev/null +++ b/gpuSPHINXsys/src/ParticleSorter.cuh @@ -0,0 +1,297 @@ +#ifndef PARTICLESORTER_CUH +#define PARTICLESORTER_CUH + +#include"Box.cuh" +#include"Grid.cuh" +#include"System.h" +#include"debugTools.cuh" +#include + +namespace gpu{ + + namespace Sorter{ + + struct MortonHash{ + Grid grid; + MortonHash(Grid grid): grid(grid){} + //Interleave a 10 bit number in 32 bits, fill one bit and leave the other 2 as zeros. See [1] + inline __host__ __device__ uint encodeMorton(const uint &i) const{ + uint x = i; + x &= 0x3ff; + x = (x | x << 16) & 0x30000ff; + x = (x | x << 8) & 0x300f00f; + x = (x | x << 4) & 0x30c30c3; + x = (x | x << 2) & 0x9249249; + return x; + } + /*Fuse three 10 bit numbers in 32 bits, producing a Z order Morton hash*/ + inline __host__ __device__ uint hash(int3 cell) const{ + return encodeMorton(cell.x) | (encodeMorton(cell.y) << 1) | (encodeMorton(cell.z) << 2); + } + + inline __host__ __device__ uint operator()(real4 pos) const{ + const int3 cell = grid.getCell(pos); + return hash(cell); + } + + }; + //The hash is the cell 1D index, this pattern is better than random for neighbour transverse, but worse than Morton + struct CellIndexHash{ + Grid grid; + CellIndexHash(Grid grid): grid(grid){} + inline __device__ __host__ uint hash(int3 cell) const{ + return grid.getCellIndex(cell); + } + inline __host__ __device__ uint operator()(real4 pos) const{ + const int3 cell = grid.getCell(pos); + return hash(cell); + } + + }; + + static int _ffs(int i) + { + int bit; + + if (0 == i) + return 0; + + for (bit = 1; !(i & 1); ++bit) + i >>= 1; + return bit; + } + + int clz(uint n){ + n |= (n >> 1); + n |= (n >> 2); + n |= (n >> 4); + n |= (n >> 8); + n |= (n >> 16); + return 32-_ffs(n - (n >> 1)); + } + + template + __global__ void assignHash(HashIterator hasher, + int* __restrict__ index, + uint* __restrict__ hash , int N){ + const int i = blockIdx.x*blockDim.x + threadIdx.x; + if(i >= N) return; + const uint ihash = hasher[i]; + index[i] = i; + hash[i] = ihash; + } + + template + __global__ void reorderArray(const InputIterator old, + OutputIterator sorted, + int* __restrict__ pindex, int N){ + int i = blockIdx.x*blockDim.x + threadIdx.x; + if(i>=N) return; + sorted[i] = old[pindex[i]]; + } + + } + + class ParticleSorter{ + public: + ParticleSorter() = delete; + ParticleSorter(std::shared_ptr sys):sys(sys){}; + + template + void sortByKey(cub::DoubleBuffer &index, + cub::DoubleBuffer &hash, + int N, cudaStream_t st = 0, int end_bit = sizeof(uint)*8){ + if(N > maxRequestedElements){ + maxRequestedElements = N; + cub_temp_storage_bytes = 0; + CudaSafeCall(cub::DeviceRadixSort::SortPairs(nullptr, cub_temp_storage_bytes, + hash, + index, + N, + 0, end_bit, + st)); + } + auto alloc = sys->getTemporaryDeviceAllocator(); + std::shared_ptr d_temp_storage(alloc.allocate(cub_temp_storage_bytes), + [=](char* ptr){ alloc.deallocate(ptr);}); + void* d_temp_storage_ptr = d_temp_storage.get(); + CudaSafeCall(cub::DeviceRadixSort::SortPairs(d_temp_storage_ptr, cub_temp_storage_bytes, + hash, + index, + N, + 0, end_bit, + st)); + } + //Return the most significant bit of an unsigned integral type + template inline int msb(T n){ + static_assert(std::is_integral::value && !std::is_signed::value, + "msb(): T must be an unsigned integral type."); + for(T i = std::numeric_limits::digits - 1, mask = 1 << i; + i >= 0; + --i, mask >>= 1){ + if((n & mask) != 0) return i; + } + return 0; + } + + template + void updateOrderWithCustomHash(HashIterator &hasher, + uint N, uint maxHash = std::numeric_limits::max(), + cudaStream_t st = 0){ + sys->log("[ParticleSorter] Updating with custom hash iterator"); + sys->log("[ParticleSorter] Assigning hash to %d elements", N); + sys->log("[ParticleSorter] Maximum hash 0x%x ( dec: %u ) last bit: %d", + maxHash, maxHash, 32-Sorter::clz(maxHash) ); + try{ + tryToUpdateOrderWithCustomHash(hasher, N, maxHash, st); + } + catch(...){ + sys->log("ParticleSorter raised an exception in updateOrderWithCustomHash"); + throw; + } + } + + template + void updateOrderByCellHash(InputIterator pos, uint N, Box box, int3 cellDim, cudaStream_t st = 0){ + Grid grid(box, cellDim); + CellHasher hasher(grid); + auto hashIterator = thrust::make_transform_iterator(pos, hasher); + auto maxHash = hasher.hash(cellDim-1); + this->updateOrderWithCustomHash(hashIterator, N, maxHash, st); + } + + void updateOrderById(int *id, int N, cudaStream_t st = 0){ + try{ + tryToUpdateOrderById(id, N, st); + } + catch(...){ + sys->log("Exception raised in ParticleSorter::updateOrderById"); + throw; + } + } + + //WARNING: _unsorted and _sorted cannot be aliased! + template + void applyCurrentOrder(InputIterator d_property_unsorted, OutputIterator d_property_sorted, + int N, cudaStream_t st = 0){ + int Nthreads=128; + int Nblocks=N/Nthreads + ((N%Nthreads)?1:0); + Sorter::reorderArray<<>>(d_property_unsorted, + d_property_sorted, + thrust::raw_pointer_cast(index.data()), + N); + CudaCheckError(); + } + + int * getSortedIndexArray(int N){ + try{ + return tryToGetSortedIndexArray(N); + } + catch(...){ + sys->log("Exception raised in ParticleSorter::getSortedIndexArray"); + throw; + } + } + + uint * getSortedHashes(){ + return thrust::raw_pointer_cast(hash.data()); + } + + int * getIndexArrayById(int * id, int N, cudaStream_t st = 0){ + try{ + return tryToGetIndexArrayById(id, N, st); + } + catch(...){ + sys->log("Exception raised in ParticleSorter::getIndexArrayById"); + throw; + } + } + + private: + bool init = false; + bool originalOrderNeedsUpdate = true; + + int maxRequestedElements = 0; + size_t cub_temp_storage_bytes = 0; + + thrust::device_vector original_index; + thrust::device_vector index, index_alt; + thrust::device_vector hash, hash_alt; + + std::shared_ptr sys; + + void tryToUpdateOrderById(int *id, int N, cudaStream_t st){ + original_index.resize(N); + index_alt.resize(N); + hash.resize(N); + hash_alt.resize(N); + cub::CountingInputIterator ci(0); + thrust::copy(thrust::cuda::par, ci, ci+N, original_index.begin()); + int* d_hash = (int*)thrust::raw_pointer_cast(hash.data()); + CudaSafeCall(cudaMemcpyAsync(d_hash, id, N*sizeof(int), cudaMemcpyDeviceToDevice,st)); + auto db_index = cub::DoubleBuffer(thrust::raw_pointer_cast(original_index.data()), + thrust::raw_pointer_cast(index_alt.data())); + auto db_hash = cub::DoubleBuffer(d_hash, (int*)thrust::raw_pointer_cast(hash_alt.data())); + this->sortByKey(db_index, db_hash, N, st); + if(db_index.selector) + original_index.swap(index_alt); + } + + template + void tryToUpdateOrderWithCustomHash(HashIterator &hasher, uint N, uint maxHash, cudaStream_t st){ + init = true; + hash.resize(N); + index.resize(N); + int Nthreads=128; + int Nblocks=N/Nthreads + ((N%Nthreads)?1:0); + Sorter::assignHash<<>>(hasher, + thrust::raw_pointer_cast(index.data()), + thrust::raw_pointer_cast(hash.data()), + N); + CudaCheckError(); + hash_alt.resize(N); + index_alt.resize(N); + auto db_index = cub::DoubleBuffer(thrust::raw_pointer_cast(index.data()), + thrust::raw_pointer_cast(index_alt.data())); + auto db_hash = cub::DoubleBuffer(thrust::raw_pointer_cast(hash.data()), + thrust::raw_pointer_cast(hash_alt.data())); + //Cub just needs this endbit at least + int maxbit = 32-Sorter::clz(maxHash); + maxbit = std::min(maxbit, 32); + this->sortByKey(db_index, db_hash, N, st, maxbit); + CudaCheckError(); + //Sometimes CUB will not swap the references in the DoubleBuffer + if(db_index.selector) + index.swap(index_alt); + if(db_hash.selector) + hash.swap(hash_alt); + originalOrderNeedsUpdate = true; + } + + int * tryToGetSortedIndexArray(int N){ + int lastN = index.size(); + if(lastN != N){ + cub::CountingInputIterator ci(lastN); + index.resize(N); + thrust::copy(thrust::cuda::par, ci, ci+(N-lastN), index.begin()+lastN); + } + return thrust::raw_pointer_cast(index.data()); + } + + int * tryToGetIndexArrayById(int * id, int N, cudaStream_t st = 0){ + if(!init) return id; + if(originalOrderNeedsUpdate){ + this->updateOrderById(id, N, st); + originalOrderNeedsUpdate = false; + } + int lastN = original_index.size(); + if(lastN != N){ + original_index.resize(N); + thrust::copy(thrust::cuda::par, id, id+(N-lastN), original_index.begin()+lastN); + } + return thrust::raw_pointer_cast(original_index.data()); + } + + }; +} +#endif diff --git a/gpuSPHINXsys/src/Property.cuh b/gpuSPHINXsys/src/Property.cuh new file mode 100644 index 0000000000..24c3725e13 --- /dev/null +++ b/gpuSPHINXsys/src/Property.cuh @@ -0,0 +1,278 @@ +#ifndef PROPERTY_CUH +#define PROPERTY_CUH + +#include +#include"System.h" +#include"GPUUtils.cuh" +#include"debugTools.cuh" +#include"vector.cuh" +#include +#include + +namespace gpu{ + //Forward declaration for friend attribute + class ParticleData; + template struct Property; + + template + class property_ptr: + public thrust::iterator_adaptor, T*> + { + + public: + using Iterator = T*; + using super_t = thrust::iterator_adaptor, Iterator>; + private: + T *ptr; + size_t m_size; + bool *isBeingRead, *isBeingWritten; + bool isCopy = false; //true if this instance was created when passed to a cuda kernel + friend class thrust::iterator_core_access; + + void unlockProperty(){ + *isBeingWritten = false; + *isBeingRead = false; + } + + public: + + property_ptr(): + super_t(nullptr), + ptr(nullptr), + m_size(0), + isBeingRead(nullptr), isBeingWritten(nullptr) + {} + + property_ptr(T* ptr, + bool *isBeingWritten, bool *isBeingRead, + size_t in_size): + super_t(ptr), + ptr(ptr), + m_size(in_size), + isBeingWritten(isBeingWritten), + isBeingRead(isBeingRead) + {} + + __host__ __device__ property_ptr(const property_ptr& _orig ):super_t(_orig.ptr) { *this = _orig; isCopy = true; } + + __host__ __device__ ~property_ptr(){ +#ifdef __CUDA_ARCH__ + return; +#else + if(isCopy) return; + if(ptr) + unlockProperty(); +#endif + } + + __host__ __device__ T* raw() const { return ptr;} + + __host__ __device__ T* get() const { return raw();} + + __host__ __device__ Iterator end() const{ + if(ptr) + return begin()+size(); + else + return nullptr; + } + + __host__ __device__ Iterator begin() const{ return get();} + + __host__ __device__ size_t size() const{ return m_size;} + }; + + struct illegal_property_access: public std::runtime_error{ + using std::runtime_error::runtime_error; + }; + + template + struct Property{ + friend class ParticleData; + public: + using valueType = T; + using iterator = property_ptr; + Property(): Property(0, "noName", nullptr){} + Property(std::string name, shared_ptr sys): Property(0, name, sys){} + Property(int N, std::string name, shared_ptr sys):N(N), name(name), sys(sys) + { + sys->log("[Property] Property %s created with size %d", name.c_str(), N); + CudaCheckError(); + } + ~Property() = default; + + void resize(int Nnew){ + N = Nnew; + } + + void swapInternalBuffers(){ + try{ + tryToResizeAndSwapInternalContainers(); + } + catch(...){ + sys->log("[Property] Exception raised during internal container swap"); + throw; + } + } + + void swapCPUContainer(std::vector &outsideHostVector){ + sys->log("[Property] Swapping internal CPU container of property (%s)", name.c_str()); + swapWithExternalContainer(hostVector, outsideHostVector); + forceUpdate(access::location::gpu); + } + + void swapGPUContainer(thrust::device_vector &outsideDeviceVector){ + sys->log("[Property] Swapping internal CPU container of property (%s)", name.c_str()); + swapWithExternalContainer(deviceVector, outsideDeviceVector); + forceUpdate(access::location::cpu); + } + + iterator data(access::location dev, access::mode mode){ + sys->log("[Property] %s requested from %d (0=cpu, 1=gpu, 2=managed) with access %d (0=r, 1=w, 2=rw)", + name.c_str(), dev, mode); + try{ + return tryToGetData(dev,mode); + } + catch(...){ + sys->log("[Property] Exception raised in data request for property "+name); + throw; + } + } + + iterator begin(access::location dev, access::mode mode){ + return data(dev, mode); + } + + void forceUpdate(access::location dev){ + switch(dev){ + case access::location::cpu: + this->hostVectorNeedsUpdate = true; + break; + case access::location::gpu: + this->deviceVectorNeedsUpdate = true; + break; + } + } + + std::string getName() const{ return this->name;} + + int size() const{ return this->N;} + + bool isAllocated() const{ return this->N>0;} + + private: + + thrust::device_vector deviceVector, deviceVector_alt; + std::vector hostVector; + + uint N = 0; + bool deviceVectorNeedsUpdate = false, hostVectorNeedsUpdate = false; + string name; + bool isBeingWritten = false, isBeingRead= false; + shared_ptr sys; + + T* getAltGPUBuffer(){ + deviceVector_alt.resize(N); + return thrust::raw_pointer_cast(deviceVector_alt.data()); + } + + property_ptr tryToGetData(access::location dev, access::mode mode){ + const bool requestedForWriting = (mode==access::mode::write or mode==access::mode::readwrite); + throwIfIllegalDataRequest(mode); + lockIfNecesary(mode); + switch(dev){ + case access::location::cpu: + updateHostData(); + if(requestedForWriting) + deviceVectorNeedsUpdate=true; + return property_ptr(hostVector.data(), &this->isBeingWritten, &this->isBeingRead, size()); + case access::location::gpu: + updateDeviceData(); + if(requestedForWriting) + hostVectorNeedsUpdate=true; + return property_ptr(thrust::raw_pointer_cast(deviceVector.data()), + &this->isBeingWritten, &this->isBeingRead, size()); + + default: + throw std::runtime_error("[Property] Invalid location requested"); + } + } + + void throwIfIllegalDataRequest(access::mode mode){ + const bool requestedForWriting = (mode==access::mode::write or mode==access::mode::readwrite); + const bool requestedForReading = (mode==access::mode::read); + { + const bool isIllegalRequestForWriting = (this->isBeingWritten or this->isBeingRead) and requestedForWriting; + const bool isIllegalRequestForReading = (this->isBeingWritten and requestedForReading); + if(isIllegalRequestForWriting or isIllegalRequestForReading){ + sys->log("[Property] You cant request " + name + " property for " + + (this->isBeingWritten?"writing":"reading") + " while its locked!"); + throw illegal_property_access("Property "+name+" requested while locked"); + } + } + } + + void lockIfNecesary(access::mode request_mode){ + const bool requestedForWritting = (request_mode==access::mode::write or request_mode==access::mode::readwrite); + const bool requestedForReading = (request_mode==access::mode::read); + if(requestedForWritting) + this->isBeingWritten = true; + if(requestedForReading) + this->isBeingRead = true; + } + void updateHostData(){ + if(hostVector.size()!= N){ + sys->log("[Property] Resizing host version of " + name + " to " + std::to_string(N)+ " elements"); + hostVector.resize(N); + } + if(hostVectorNeedsUpdate){ + sys->log("Updating host version of %s", name.c_str()); + hostVector.resize(N); + CudaSafeCall(cudaMemcpy(hostVector.data(), + thrust::raw_pointer_cast(deviceVector.data()), + N*sizeof(T), cudaMemcpyDeviceToHost)); + hostVectorNeedsUpdate=false; + } + } + + void updateDeviceData(){ + if(deviceVector.size()!= N){ + sys->log("[Property] Resizing device version of " + name + " to " + std::to_string(N)+ " elements"); + deviceVector.resize(N); + } + if(deviceVectorNeedsUpdate){ + sys->log("Updating device version of %s", name.c_str()); + deviceVector = hostVector; + deviceVectorNeedsUpdate=false; + } + } + + void tryToResizeAndSwapInternalContainers(){ + + sys->log("[Property] Swapping internal device references of %s", name.c_str()); + deviceVector_alt.resize(N); + hostVectorNeedsUpdate = true; + } + + template + void tryToSwapWithExternalContainer(InternalContainer &myContainer, ExternalContainer &outsideContainer){ + throwIfnotInSwappableState(); + if(outsideContainer.size() != N) { + sys->log("[Property] Resizing input container, had %d elements, should have %d", + outsideContainer.size(), N); + outsideContainer.resize(N); + } + myContainer.swap(outsideContainer); + } + + void throwIfnotInSwappableState(){ + if(this->isBeingRead || this->isBeingWritten){ + sys->log("[Property] Cannot swap property %s while it is locked for writing/reading", + name.c_str()); + throw illegal_property_access("Property "+name+" requested while locked"); + } + } + + }; + +} +#endif diff --git a/gpuSPHINXsys/src/System.h b/gpuSPHINXsys/src/System.h new file mode 100644 index 0000000000..8399a637b8 --- /dev/null +++ b/gpuSPHINXsys/src/System.h @@ -0,0 +1,89 @@ +#ifndef SYSTEM_H +#define SYSTEM_H + +#include"defines.h" +#include"debugTools.cuh" +#include"Log.h" +#include +#include +#include +#include +#include"allocator.h" +#include + +namespace gpu{ + + using std::shared_ptr; + using std::string; + + struct access{ + enum location{cpu, gpu, nodevice}; + enum mode{read, write, readwrite, nomode}; + }; + + class insuficient_compute_capability_exception: public std::exception{ + const char* what() const noexcept { + return "Insuficient compute capability"; + } + }; + + class System{ + public: + + using resource = gpu::device_memory_resource; + using device_temporary_memory_resource = gpu::pool_memory_resource_adaptor; + + template + using allocator = gpu::polymorphic_allocator; + + enum LogLevel{CRITICAL=0, ERROR, EXCEPTION, WARNING, MESSAGE, STDERR, STDOUT, + DEBUG, DEBUG1, DEBUG2, DEBUG3, DEBUG4, DEBUG5, DEBUG6, DEBUG7}; + + private: + + void initializeCUDA(){ + try{ + CudaSafeCall(cudaFree(0)); + CudaSafeCall(cudaDeviceSynchronize()); + CudaCheckError(); + } + catch(...){ + log("[System] Exception raised at CUDA initialization"); + throw; + } + log("[System] CUDA initialized"); + } + + public: + System(){ + this->initializeCUDA(); + CudaCheckError(); + } + + void finish(){ + log("[System] finish"); + CudaSafeCall(cudaDeviceSynchronize()); + CudaCheckError(); + } + + template + static inline void log(char const *fmt, T... args){ + Logging::log(fmt, args...); + if(level == CRITICAL){ + throw std::runtime_error("System encountered an unrecoverable error"); + } + } + template + static inline void log(const std::string &msg){ + log("%s", msg.c_str()); + } + + template + static allocator getTemporaryDeviceAllocator(){ + return allocator(); + } + + }; + +} +#endif diff --git a/gpuSPHINXsys/src/allocator.h b/gpuSPHINXsys/src/allocator.h new file mode 100644 index 0000000000..d910e2e318 --- /dev/null +++ b/gpuSPHINXsys/src/allocator.h @@ -0,0 +1,211 @@ +#ifndef ALLOCATOR_H +#define ALLOCATOR_H +#include +#include +#include"debugTools.cuh" +#include + +namespace gpu{ + + namespace detail{ + template using cuda_ptr = thrust::device_ptr; + template + class memory_resource{ + public: + using pointer = T; + using max_align_t = long double; //This C++11 alias is not available in std with g++-4.8.5 + pointer allocate(std::size_t bytes, std::size_t alignment = alignof(max_align_t)){ + return do_allocate(bytes, alignment); + } + + void deallocate(pointer p, std::size_t bytes, std::size_t alignment = alignof(max_align_t)){ + return do_deallocate(p, bytes, alignment); + } + + bool is_equal(const memory_resource &other) const noexcept{ + return do_is_equal(other); + } + + virtual pointer do_allocate(std::size_t bytes, std::size_t alignment) = 0; + + virtual void do_deallocate(pointer p, std::size_t bytes, std::size_t alignment) = 0; + + virtual bool do_is_equal(const memory_resource &other) const noexcept{ + return this == &other; + } + + }; + + template + MR* get_default_resource(){ + static MR default_resource; + return &default_resource; + } + } + + //A device memory resource, with cuda raw pointers + class device_memory_resource : public detail::memory_resource{ + using super = detail::memory_resource; + public: + + using pointer = typename super::pointer; + + virtual pointer do_allocate(std::size_t bytes, std::size_t alignment) override{ + return thrust::raw_pointer_cast(thrust::cuda::malloc(bytes)); + } + + virtual void do_deallocate(pointer p, std::size_t bytes, std::size_t alignment) override{ + thrust::cuda::pointer void_ptr(p); + thrust::cuda::free(void_ptr); + } + + }; + + //A pool device memory_resource, stores previously allocated blocks in a cache + // and retrieves them fast when similar ones are allocated again (without calling malloc everytime). + template + struct pool_memory_resource_adaptor: public detail::memory_resource{ + private: + using super = detail::memory_resource; + MR* res; + public: + using pointer = typename super::pointer; + + ~pool_memory_resource_adaptor(){ + try{ + free_all(); + } + catch(...){ + } + } + + pool_memory_resource_adaptor(MR* resource): res(resource){} + pool_memory_resource_adaptor(): res(detail::get_default_resource()){} + + using FreeBlocks = std::multimap; + using AllocatedBlocks = std::map; + FreeBlocks free_blocks; + AllocatedBlocks allocated_blocks; + + virtual pointer do_allocate( std::size_t bytes, std::size_t alignment) override{ + pointer result; + std::ptrdiff_t blockSize = 0; + auto available_blocks = free_blocks.equal_range(bytes); + auto available_block = available_blocks.first; + //Look for a block of the same size + if(available_block == free_blocks.end()){ + available_block = available_blocks.second; + } + //Try to find a block greater than requested size + if(available_block != free_blocks.end() ){ + result = pointer(available_block -> second); + blockSize = available_block -> first; + free_blocks.erase(available_block); + } + else{ + result = res->do_allocate(bytes, alignment); + blockSize = bytes; + } + allocated_blocks.insert(std::make_pair(thrust::raw_pointer_cast(result), blockSize)); + return result; + } + + virtual void do_deallocate(pointer p, std::size_t bytes, std::size_t alignment) override{ + auto block = allocated_blocks.find(thrust::raw_pointer_cast(p)); + if(block == allocated_blocks.end()){ + throw std::system_error(EFAULT, std::generic_category(), "Address is not handled by this instance."); + } + std::ptrdiff_t num_bytes = block->second; + allocated_blocks.erase(block); + free_blocks.insert(std::make_pair(num_bytes, thrust::raw_pointer_cast(p))); + } + + virtual bool do_is_equal(const super &other) const noexcept override { + return res->do_is_equal(other); + } + + void free_all(){ + for(auto &i: free_blocks) res->do_deallocate(static_cast(i.second), i.first, 0); + for(auto &i: allocated_blocks) res->do_deallocate(static_cast(i.first), i.second, 0); + free_blocks.clear(); + allocated_blocks.clear(); + } + + }; + + namespace detail{ + //Takes a pointer type (including smart pointers) and returns a reference to the underlying type + template struct pointer_to_lvalue_reference{ + private: + using element_type = typename std::pointer_traits::element_type; + public: + using type = typename std::add_lvalue_reference::type; + }; + + //Specialization for special thrust pointer/reference types... + template struct pointer_to_lvalue_reference>{ + using type = thrust::system::cuda::reference; + }; + + template struct non_void_value_type{using type = T;}; + template<> struct non_void_value_type{using type = char;}; + + } + + //An allocator that can be used for any type using the same underlying memory_resource. + //pointer type can be specified to work with thrust cuda pointers + template, + class void_pointer = T*> + class polymorphic_allocator{ + MR * res; + public: + //C++17 definitions for allocator interface + using size_type = std::size_t; + + using value_type = T; + using value_size_type = typename detail::non_void_value_type::type; + + using differente_type = std::ptrdiff_t; + + //All of the traits below are deprecated in C++17, but thrust counts on them + //using void_pointer = T*; + using pointer = typename std::pointer_traits::template rebind; + + using reference = typename detail::pointer_to_lvalue_reference::type; + using const_reference = typename detail::pointer_to_lvalue_reference>::type; + + using propagate_on_container_copy_assignment = std::true_type; + using propagate_on_container_move_assignment = std::true_type; + using propagate_on_container_swap = std::true_type; + + template + polymorphic_allocator(Other other) : res(other.resource()){} + + polymorphic_allocator(MR * resource) : res(resource){} + + polymorphic_allocator() : res(detail::get_default_resource()){} + + MR* resource() const { return this->res;} + + pointer allocate (size_type n) const{ + return static_cast(static_cast(this->res->do_allocate(n * sizeof(value_size_type), + alignof(value_size_type)))); + } + + void deallocate (pointer p, size_type n = 0) const{ + return this->res->do_deallocate(thrust::raw_pointer_cast(p), + n * sizeof(value_size_type), + alignof(value_size_type)); + } + }; + + //thrust versions are not reliable atm, std::pmr is C++17 which CUDA 10.1 does not support yet and is not thrust::cuda compatible (thrust expects device allocators to return thurst::cuda::pointers) + //template using memory_resource = thrust::mr::memory_resource; + //template using polymorphic_resource_adaptor = thrust::mr::polymorphic_adaptor_resource; + //template using allocator = thrust::mr::allocator; + //template using polymorphic_allocator = thrust::mr::polymorphic_allocator; + +} + +#endif diff --git a/gpuSPHINXsys/src/debugTools.cuh b/gpuSPHINXsys/src/debugTools.cuh new file mode 100644 index 0000000000..fb5fe625e3 --- /dev/null +++ b/gpuSPHINXsys/src/debugTools.cuh @@ -0,0 +1,44 @@ +#ifndef DEBUGTOOLS_CUH +#define DEBUGTOOLS_CUH + +#define CUDA_ERROR_CHECK + +#define CudaSafeCall(err) __cudaSafeCall(err, __FILE__, __LINE__) +#define CudaCheckError() __cudaCheckError(__FILE__, __LINE__) + +#include + +namespace gpu{ + + class cuda_generic_error: public std::runtime_error{ + cudaError_t error_code; + public: + cuda_generic_error(std::string msg, cudaError_t err): + std::runtime_error(msg + ": " + cudaGetErrorString(err) + " - code: " + std::to_string(err)), + error_code(err){} + + cudaError_t code(){return error_code;} + }; + +} + +inline void __cudaSafeCall(cudaError err, const char *file, const int line){ + #ifdef CUDA_ERROR_CHECK + if (cudaSuccess != err){ + cudaGetLastError(); //Reset CUDA error status + throw gpu::cuda_generic_error("CudaSafeCall() failed at "+ + std::string(file) + ":" + std::to_string(line), err); + } + #endif +} + +inline void __cudaCheckError(const char *file, const int line){ + cudaError err; + err = cudaGetLastError(); + if(cudaSuccess != err){ + throw gpu::cuda_generic_error("CudaCheckError() failed at "+ + std::string(file) + ":" + std::to_string(line), err); + } +} + +#endif diff --git a/gpuSPHINXsys/src/defines.h b/gpuSPHINXsys/src/defines.h new file mode 100644 index 0000000000..7227635b78 --- /dev/null +++ b/gpuSPHINXsys/src/defines.h @@ -0,0 +1,36 @@ +#ifndef DEFINES_H +#define DEFINES_H +#include"cuda_runtime.h" + +#ifndef DOUBLE_PRECISION +#define SINGLE_PRECISION +#endif + + +#define fori(x,y) for(int i=x; i pd, + shared_ptr nl, + shared_ptr sys, + Parameters par): + pd(pd), nl(nl), sys(sys), + stream(par.stream){ + printf("|dtSizeCalc| \tis called with dtMin: %f \n", dtMin); +} + +dtSizeCalc::~dtSizeCalc(){ + printf("|dtSizeCalc| \tcall ended! \n"); +} + +namespace dtSizeCalc_ns{ + +//function to get the magnitude of acc and vel +__global__ void magAccVel(int N, + const int* __restrict__ groupIndex, + real3* __restrict__ vel, + real4* __restrict__ force, + real* __restrict__ velMag, + real* __restrict__ AccMag){ + int i = blockIdx.x*blockDim.x+threadIdx.x; + if(i>=N) return; + const real3 veli = vel[groupIndex[i]]; + const real3 acci = make_real3(force[groupIndex[i]]); + velMag[groupIndex[i]] = sqrtf(dot(veli, veli)); + AccMag[groupIndex[i]] = sqrtf(dot(acci, acci)); +} + +}//dtSizeCalc_ns + +//function to find the max acceleration and velocity in the systems +template +real dtSizeCalc::maxAccVel() +{ + int np = pd->getNumParticles(); + int Nthreads=128; + int Nblocks=np/Nthreads + ((np%Nthreads)?1:0); + auto groupIndex = nl->getGroupIndexIterator(); +#ifndef _TRANSPORT_VELOCITY_ + auto vel = pd->getVel(access::location::gpu, access::mode::read); +#else + auto vel = pd->getVel_tv(access::location::gpu, access::mode::read); +#endif + auto force = pd->getForce(access::location::gpu, access::mode::read); + + velMag.resize(np); + AccMag.resize(np); + + auto velMag_ptr = thrust::raw_pointer_cast(velMag.data()); + auto AccMag_ptr = thrust::raw_pointer_cast(AccMag.data()); + + //TODO: this coud also be improved by templates when force and vel are of the same type (e.g. real3) + dtSizeCalc_ns::magAccVel<<>>(np, + groupIndex, + vel.raw(), + force.raw(), + velMag_ptr, + AccMag_ptr); + + //find the Max of reducedPar(acc or vel) among all particles + real *maxPar; + cudaMalloc(&maxPar, sizeof(real)); + { + size_t newSize = 0; + if(reductionOpt == reducedPar::Vel) + cub::DeviceReduce::Max(nullptr, newSize, velMag_ptr, maxPar, np); + else if(reductionOpt == reducedPar::Acc) + cub::DeviceReduce::Max(nullptr, newSize, AccMag_ptr, maxPar, np); + else + throw std::runtime_error("|dtSizeCalc| \treducedPar is not valid!"); + + + if(newSize > tempStorage.size()){ + tempStorage.resize(newSize); + } + } + size_t size = tempStorage.size(); + if(reductionOpt == reducedPar::Vel) + cub::DeviceReduce::Max((void*)thrust::raw_pointer_cast(tempStorage.data()), size, velMag_ptr, maxPar, np); + else if(reductionOpt == reducedPar::Acc) + cub::DeviceReduce::Max((void*)thrust::raw_pointer_cast(tempStorage.data()), size, AccMag_ptr, maxPar, np); + + + real max = 0; + CudaSafeCall(cudaMemcpy(&max, maxPar, sizeof(real), cudaMemcpyDeviceToHost)); + CudaSafeCall(cudaFree(maxPar)); + return max; +} + +//calculate the advection time step size +//Eq. 8 in doi.org/10.1016/j.jcp.2019.109135 +real dtSizeCalc::calcDtAdv(real h, real U_f){ + real cflAdv = 0.25; + real velMax = 0.0; + velMax = maxAccVel(); + real Umax = std::max(U_f, velMax); + //dt1 based on advection + const real dt1 = h / (Umax + 1e-6f); + //dt2 based on viscous terms (TODO) + const real dt2 = std::numeric_limits::max();// h*h/kinViscosity; + //new value of the dynamic time step. + real dtSize = cflAdv * std::min(dt1, dt2); + if(dtSize(); + //new value of the dynamic time step. + real dtSize = cflAcs * h / (c_f + velMax + 1e-6f); + if(dtSize(); + accMax = maxAccVel(); + //dt1 based on force per unit mass. + const real dtF = (accMax)?sqrtf(h/accMax):std::numeric_limits::max(); + //dt2 based on advection + const real dtAd = h/(std::max(c_f,velMax*10.f)); + //new value of the dynamic time step. + real dtSize=CFL*std::min(dtF,dtAd); + if(dtSize +#include + +namespace gpu{ +class dtSizeCalc{ + +private: + cudaStream_t stream; + real dtMin = 1.e-5f; + + enum reducedPar{Acc, Vel}; //reduced Parameter: acc or vel + + thrust::device_vector tempStorage; //temporary storage - cub::Reduce + thrust::device_vector velMag, AccMag; + + shared_ptr pd; + shared_ptr nl; + shared_ptr sys; + +public: + struct Parameters{ + cudaStream_t stream; + }; + + dtSizeCalc(shared_ptr pd, + shared_ptr nl, + shared_ptr sys, + Parameters par); + ~dtSizeCalc(); + + template + real maxAccVel(); + real calcDt(real h, real c_f); + real calcDtAdv(real h, real U_f); + real calcDtAcs(real h, real c_f); + +}; +}//namespace gpu + +#include"dtSizeCalc.cu" +#endif + diff --git a/gpuSPHINXsys/src/fluidDynamics.cu b/gpuSPHINXsys/src/fluidDynamics.cu new file mode 100644 index 0000000000..6e75194eb4 --- /dev/null +++ b/gpuSPHINXsys/src/fluidDynamics.cu @@ -0,0 +1,686 @@ +/* + * Massoud Rezavand 2019. + * Technical University of Munich + * gpuSPHinxsys - An SPH solver for CUDA enabled GPUs + * + * This module collects all the functions required for + * the fluid dynamics related calculations + */ + +#include"fluidDynamics.cuh" +#include"Kernel.cuh" + +using Kernel = gpu::KernelFunction::Wendland_C4; + +namespace gpu{ + +fluidDynamics::fluidDynamics(shared_ptr pd, + shared_ptr nl, + shared_ptr sys, + Parameters par): + pd(pd), nl(nl), sys(sys), + c_f(par.c_f), rho0_f(par.rho0_f), bodyForce(par.bodyForce), + stream(par.stream), box(par.box){ + printf("|fluidDynamics| \tis called with c_f = %.1f, rho0_f = %.1f \n", c_f, rho0_f); +} + +fluidDynamics::~fluidDynamics(){ + printf("|fluidDynamics| \tcall ended! \n"); +} + +namespace fluidDynamics_ns{ + +//Kernel to calculate drho/dt by Riemann Solvers (Continuity Eq.) +template +__global__ void calcDensityRiemann_ker(NeighbourContainer ni, + Kernel kernel, + const real4* __restrict__ sortPos, + const int* __restrict__ groupIndex, + int np, Box box, + real3* __restrict__ vel, + real* __restrict__ rho, + real* __restrict__ mass, + real* __restrict__ p, real h, real dt, + real c_f, + const real* __restrict__ rho0, + real* __restrict__ vol){ + int i = blockIdx.x*blockDim.x + threadIdx.x; + if(i >= np) return; + //only on LIQUID particles + const real4 posi = cub::ThreadLoad(sortPos + i); + if(posi.w != WALL){ + //Set ni to provide iterators for particle i + ni.set(i); + + const real3 ri = make_real3(cub::ThreadLoad(sortPos + i)); + const real3 veli = vel[groupIndex[i]]; + const real rhoi = rho[groupIndex[i]]; + const real pi = p[groupIndex[i]]; + real drho = real(); + + auto it = ni.begin(); //Iterator to the first neighbour of particle i + + while(it){ + auto neigh = *it++; + if(neigh.getGroupIndex() == groupIndex[i]) continue; //skip if same particle + + const real3 rj = make_real3(neigh.getPos()); + const int typej = neigh.getPos().w; + const real3 velj = vel[neigh.getGroupIndex()]; + const real rhoj = rho[neigh.getGroupIndex()]; + const real massj = mass[neigh.getGroupIndex()]; + const real volj = vol[neigh.getGroupIndex()]; + const real3 rij = box.apply_pbc(ri-rj); + + //low dissipation Riemann problem + real r2 = dot(rij, rij); + real dist = sqrtf(r2); + real3 _rij = rj - ri; + real pj = p[neigh.getGroupIndex()]; + real3 e_ij = _rij*1/(dist + 1.0e-15); + real ul = dot(e_ij, veli); + real ur = dot(e_ij, velj); + real v_star = (rhoi*ul+rhoj*ur+(pi-pj)/c_f)/(rhoi+rhoj); + real aw = kernel.gradient(rij, h, box.boxSize.z); + //only volume of wall particles into account + if (typej == WALL) + drho += 2.0*rhoi*volj*(v_star-ul)*aw*dist; + else + drho += 2.0*rhoi*massj/rhoj*(v_star-ul)*aw*dist; + } + rho[groupIndex[i]] += drho*dt; + //get the volume according to rho + vol[groupIndex[i]] = mass[groupIndex[i]]/rho[groupIndex[i]]; + // pressure calculation via the linear EoS + p[groupIndex[i]] = c_f*c_f*(rho[groupIndex[i]] - rho0[groupIndex[i]]); +// printf("rho = %f and rho0 = %f \n",rho[groupIndex[i]], rho0[groupIndex[i]] ); + + } +} + +//Kernel to calculate drho/dt using Artificial viscosity (Continuity Eq.) +template +__global__ void calcDensityArtificial_ker(NeighbourContainer ni, + Kernel kernel, + const real4* __restrict__ sortPos, + const int* __restrict__ groupIndex, + int np, Box box, + real3* __restrict__ vel, + real* __restrict__ rho, + real* __restrict__ mass, + real* __restrict__ p, + real h, real dt, + real c_f, + const real* __restrict__ rho0){ + int i = blockIdx.x*blockDim.x + threadIdx.x; + if(i >= np) return; + //only on LIQUID and gas particles + const real4 posi = cub::ThreadLoad(sortPos + i); + if(posi.w != WALL){ + //Set ni to provide iterators for particle i + ni.set(i); + + const real3 ri = make_real3(cub::ThreadLoad(sortPos + i)); + const real3 veli = vel[groupIndex[i]]; + const real rhoi = rho[groupIndex[i]]; + real drho = real(); + + auto it = ni.begin(); //Iterator to the first neighbour of particle i + + while(it){ + auto neigh = *it++; + if(neigh.getGroupIndex() == groupIndex[i]) continue; //skip if same particle + + const real3 rj = make_real3(neigh.getPos()); + const real3 velj = vel[neigh.getGroupIndex()]; + const real rhoj = rho[neigh.getGroupIndex()]; + const real massj = mass[neigh.getGroupIndex()]; + + const real3 rij = box.apply_pbc(ri-rj); + //Artificial viscosity + const real3 velij = veli - velj; + //TODO + //in this AV implemetaion we should use the Cubic kernel which indludes rij in there + const real3 kernel_grad = /*rij**/rij*kernel.gradient(rij, h, box.boxSize.z); + drho += rhoi*massj/rhoj*dot(kernel_grad, velij); + } + rho[groupIndex[i]] += drho*dt; + // pressure calculation via the linear EoS + p[groupIndex[i]] = c_f*c_f*(rho[groupIndex[i]] - rho0[groupIndex[i]]); + } +} + +//Kernel to calculate initail number density sigma0 +template +__global__ void calcInitNumDensity_ker(NeighbourContainer ni, + Kernel kernel, + const real4* __restrict__ sortPos, + const int* __restrict__ groupIndex, + real* __restrict__ sigma0, + int np, Box box, + real h, real rho0_f){ + int i = blockIdx.x*blockDim.x + threadIdx.x; + if(i >= np) return; + ni.set(i); + const real3 ri = make_real3(cub::ThreadLoad(sortPos + i)); + real sum0 = real(); + auto it = ni.begin(); + while(it){ + auto neigh = *it++; + const real3 rj = make_real3(neigh.getPos()); + const real3 rij = box.apply_pbc(ri-rj); + sum0 += kernel(rij, h, box.boxSize.z); + } + sigma0[groupIndex[i]] = sum0; +} + +//Kernel to update density using summation for free surface cases +template +__global__ void densitySumFreeSurface_ker(NeighbourContainer ni, + Kernel kernel, + const real4* __restrict__ sortPos, + const int* __restrict__ groupIndex, + real* __restrict__ sigma0, + real* __restrict__ rho, + int np, Box box, + real h, + const real* __restrict__ rho0){ + int i = blockIdx.x*blockDim.x + threadIdx.x; + if(i >= np) return; + const real4 posi = cub::ThreadLoad(sortPos + i); + if(posi.w != WALL){ + ni.set(i); + const real3 ri = make_real3(cub::ThreadLoad(sortPos + i)); + const real sigma0i = sigma0[groupIndex[i]]; + real rhoi = rho[groupIndex[i]]; + real sigma = real(); + auto it = ni.begin(); + while(it){ + auto neigh = *it++; + const real4 posj = neigh.getPos(); + // include only Fluid neighboring particles + if((posi.w ==LIQUID and posj.w == THIRDBODY) /*or + (posi.w == THIRDBODY and posj.w ==LIQUID) or + (posi.w == THIRDBODY and posj.w ==THIRDBODY)*/) continue; + const real3 rj = make_real3(neigh.getPos()); + const real3 rij = box.apply_pbc(ri-rj); + sigma += kernel(rij, h, box.boxSize.z); + } + real rhoSum = sigma * rho0[groupIndex[i]] / sigma0i; + rho[groupIndex[i]] = rhoSum + fmax(0.0f, (rhoi - rhoSum)) * rho0[groupIndex[i]] / rhoi; + } +} + +//Kernel to update density using summation for the lighter phase +template +__global__ void densitySumLightPhase_ker(NeighbourContainer ni, + Kernel kernel, + const real4* __restrict__ sortPos, + const int* __restrict__ groupIndex, + real* __restrict__ sigma0, + real* __restrict__ rho, + int np, Box box, real h, + const real* __restrict__ rho0, + real* __restrict__ p, + const real c_f, + real* __restrict__ vol, + const real* __restrict__ mass){ + int i = blockIdx.x*blockDim.x + threadIdx.x; + if(i >= np) return; + //only on gas particles + const real4 posi = cub::ThreadLoad(sortPos + i); + if(posi.w == THIRDBODY){ + ni.set(i); + const real3 ri = make_real3(cub::ThreadLoad(sortPos + i)); + + const real rho0i = rho0[groupIndex[i]]; + + real sum = real(); + auto it = ni.begin(); + while(it){ + auto neigh = *it++; + const real3 rj = make_real3(neigh.getPos()); + const real3 rij = box.apply_pbc(ri-rj); + + sum += kernel(rij, h, box.boxSize.z); + /*or in a total Lagrangian form: */ + //const real rho0j = rho0[neigh.getGroupIndex()]; + //sum += kernel(rij, h, box.boxSize.z)*2.f*rho0i/(rho0i+rho0j); + } + rho[groupIndex[i]] = sum * mass[groupIndex[i]]; + /*or in a total Lagrangian form: */ + //const real sigma0i = sigma0[groupIndex[i]]; + //rho[groupIndex[i]] = sum * rho0i / sigma0i; + + //get the volume according to rho + vol[groupIndex[i]] = mass[groupIndex[i]]/rho[groupIndex[i]]; + // pressure calculation via the linear EoS + p[groupIndex[i]] = c_f*c_f*(rho[groupIndex[i]] - rho0i); + } + +} + + +//Kernel to calculate pressure for wall particles +template +__global__ void calcPressureBC_ker(NeighbourContainer ni, + Kernel kernel, + const real4* __restrict__ sortPos, + const int* __restrict__ groupIndex, + int np, Box box, + real* __restrict__ rho, + real* __restrict__ p, + real h, real c_f, + const real* __restrict__ rho0, + real3 bodyForce){ + int i = blockIdx.x*blockDim.x + threadIdx.x; + if(i >= np) return; + //only on Wall particles + const real4 posi = cub::ThreadLoad(sortPos + i); + if(posi.w == WALL){ + //Set ni to provide iterators for particle i + ni.set(i); + + const real3 ri = make_real3(cub::ThreadLoad(sortPos + i)); + real sum0 = real(); + real sum1 = real(); + real3 sum2 = real3(); + real3 aw = real3(); + //gravity + //in case coordinates are needed ow. directly can be used + const real3 body_force = bodyForce; + + auto it = ni.begin(); //Iterator to the first neighbour of particle i + + while(it){ + auto neigh = *it++; + if(neigh.getGroupIndex() == groupIndex[i]) continue; //skip if same particle + const real4 posj = neigh.getPos(); + // include only Fluid neighboring particles + if(posj.w == WALL) continue; + + const real3 rj = make_real3(neigh.getPos()); + const real rhoj = rho[neigh.getGroupIndex()]; + const real pj = p[neigh.getGroupIndex()]; + const real3 rij = box.apply_pbc(ri-rj); + const real wij = kernel(rij, h, box.boxSize.z); +// printf("rhoj gas = %f \trho0j gas = %f \n", rho[neigh.getGroupIndex()], rho0[neigh.getGroupIndex()]); + + //fraction devided by rho_j to get pressure from the lighter fluid + sum0 += wij/rhoj; + sum1 += pj*wij/rhoj; + sum2 += rij*wij*rhoj/rhoj; + + } + aw = body_force;// - aw; //for later developments + real tmp = real(); + tmp = dot(aw, sum2); + p[groupIndex[i]] = (sum1+tmp)/(sum0+1.e-20); + //get density for wall particles + rho[groupIndex[i]] = p[groupIndex[i]]/(c_f*c_f) + rho0[groupIndex[i]]; +// printf("rho gas = %f \trho0 gas = %f \n", rho[groupIndex[i]], rho0[groupIndex[i]]); + } +} + +//Kernel to calculate pressure and viscosity related forces via RiemannSolvers +template +__global__ void calcForceRiemann_ker(NeighbourContainer ni, + Kernel kernel, + const real4* __restrict__ sortPos, + const int* __restrict__ groupIndex, + int np, Box box, + real4* __restrict__ force, + real3* __restrict__ vel, + real* __restrict__ rho, + real* __restrict__ mass, + real* __restrict__ p, + real3* __restrict__ vel_tv, + real3* __restrict__ F_Pb, real P_b, + real h, real c_f, real3 bodyForce, + real physicalTime, + real* __restrict__ vol){ + int i = blockIdx.x*blockDim.x + threadIdx.x; + if(i >= np) return; + //only on Fluid particles + const real4 posi = cub::ThreadLoad(sortPos + i); + if(posi.w != WALL){ + //gravity +#ifdef _TIMEDEPENDENT_BODYFORCE_ //for sloshing tank + const real4 body_force = make_real4(bodyForce.x*sin(2.*M_PI*0.496*physicalTime) + ,bodyForce.y, 0., 0.); +#else + const real4 body_force = make_real4(bodyForce, 0.); +#endif + + //Set ni to provide iterators for particle i + ni.set(i); + + const real3 ri = make_real3(cub::ThreadLoad(sortPos + i)); + const real rhoi = rho[groupIndex[i]]; + const real pi = p[groupIndex[i]]; + const real3 veli = vel[groupIndex[i]]; + real3 F1 = real3(); + + auto it = ni.begin(); + + while(it){ + auto neigh = *it++; + + if(neigh.getGroupIndex() == groupIndex[i]) continue; //skip if same particle + + const real3 rj = make_real3(neigh.getPos()); + const real rhoj = rho[neigh.getGroupIndex()]; + const real pj = p[neigh.getGroupIndex()]; + const real massj = mass[neigh.getGroupIndex()]; + const int typej = neigh.getPos().w; + real volj; + //only volume of wall particles into account + if(typej != WALL) + volj = massj/rhoj; + else + volj = vol[neigh.getGroupIndex()]; + const real3 velj = vel[neigh.getGroupIndex()]; + const real3 rij = box.apply_pbc(ri-rj); + //low dissipation Riemann problem + const real3 kernel_grad = rij*kernel.gradient(rij, h, box.boxSize.z); + const real4 posj = neigh.getPos(); + if(posj.w != WALL){ + real r2 = dot(rij, rij); + real dist = sqrtf(r2); + real3 _rij = rj - ri; + real3 e_ij = _rij*1/(dist + 1.0e-15); + real ul = dot(e_ij, veli); + real ur = dot(e_ij, velj); + real p_star = (rhoi*pj+rhoj*pi+rhoi*rhoj*c_f*(ul-ur)* + fmin(real(3.0)*fmax((ul-ur)/c_f, real(0.0)), real(1.0)))/(rhoi+rhoj); + real temp1 = -2.0*p_star*volj/rhoi; + F1 += temp1*kernel_grad; + }else{ + //exclude the second term only when Fluid-Wall interaction + real p_star = (rhoj*pi + rhoi*pj)/(rhoi + rhoj); + real temp1 = -2.0*p_star*volj/rhoi; + F1 += temp1*kernel_grad; + } + //transport velocity formulation +#ifdef _TRANSPORT_VELOCITY_ + if(posi.w == THIRDBODY){ + const real3 v_tv_i = vel_tv[groupIndex[i]]; + const real3 v_tv_j = vel_tv[neigh.getGroupIndex()]; + const real massi = mass[groupIndex[i]]; + const real voli = massi/rhoi; + const real coef = real(1.)/massi*(voli*voli + volj*volj); + // artificial stress tensor Aij (Adami et al. 2013) + real3 A_ij = real3(); + // x component + real3 Ax_i = (v_tv_i - veli) * rhoi * veli.x; + real3 Ax_j = (v_tv_j - velj) * rhoj * velj.x; + A_ij.x = real(0.5)*dot((Ax_i+Ax_j), kernel_grad); + // y component + real3 Ay_i = (v_tv_i - veli) * rhoi * veli.y; + real3 Ay_j = (v_tv_j - velj) * rhoj * velj.y; + A_ij.y = real(0.5)*dot((Ay_i+Ay_j), kernel_grad); + // z component + real3 Az_i = (v_tv_i - veli) * rhoi * veli.z; + real3 Az_j = (v_tv_j - velj) * rhoj * velj.z; + A_ij.z = real(0.5)*dot((Az_i+Az_j), kernel_grad); + + real3 dF_AS = A_ij * coef; + F1 += dF_AS; + // background pressure force + real P_b1 = 5.*0.001*c_f*c_f; + real temp3 = real(-2.)*P_b1*volj/rhoi; + real3 dF_Pb = kernel_grad * temp3; + F_Pb[groupIndex[i]] += dF_Pb; + } +#endif + } + force[groupIndex[i]] = make_real4(F1, 0); +// if(posi.w == LIQUID) + force[groupIndex[i]] += body_force; + } +} + +//Kernel to calculate pressure and Artificial viscosity forces +template +__global__ void calcForceArtificial_ker(NeighbourContainer ni, + Kernel kernel, + const real4* __restrict__ sortPos, + const int* __restrict__ groupIndex, + int np, Box box, + real4* __restrict__ force, + real3* __restrict__ vel, + real* __restrict__ rho, + real* __restrict__ mass, + real* __restrict__ p, + real h, real c_f, real3 bodyForce){ + int i = blockIdx.x*blockDim.x + threadIdx.x; + if(i >= np) return; + //only on Fluid particles + const real4 posi = cub::ThreadLoad(sortPos + i); + if(posi.w == LIQUID){ + //gravity + const real4 body_force = make_real4(bodyForce, 0.); + + //Set ni to provide iterators for particle i + ni.set(i); + + const real3 ri = make_real3(cub::ThreadLoad(sortPos + i)); + const real rhoi = rho[groupIndex[i]]; + const real pi = p[groupIndex[i]]; + const real massi = mass[groupIndex[i]]; + const real voli = massi/rhoi; + const real3 veli = vel[groupIndex[i]]; + + real3 F1 = real3(); + real3 F2 = real3(); + + auto it = ni.begin(); + + while(it){ + auto neigh = *it++; + + if(neigh.getGroupIndex() == groupIndex[i]) continue; //skip if same particle + + const real3 rj = make_real3(neigh.getPos()); + const real rhoj = rho[neigh.getGroupIndex()]; + const real pj = p[neigh.getGroupIndex()]; + const real massj = mass[neigh.getGroupIndex()]; + const real volj = massj/rhoj; + const real3 velj = vel[neigh.getGroupIndex()]; + const real3 rij = box.apply_pbc(ri-rj); + //Artificial viscosity + //TODO + //in this AV implemetaion we should use the Cubic kernel which indludes rij in there + const real3 kernel_grad = /*rij**/rij*kernel.gradient(rij, h, box.boxSize.z); + const real temp0 = 1./massi*(voli*voli + volj*volj); + const real pij = (rhoj*pi + rhoi*pj)/(rhoi + rhoj); + const real temp1 = -1.*pij*temp0; + F1 += temp1*kernel_grad; + + const real4 posj = neigh.getPos(); + //TODO + //if(posj.w == LIQUID){ //free-slip + const real alpha = 0.1; + const real epsilon = 0.001; + const real rhoij = (rhoi + rhoj)/2.; + const real3 velij = veli - velj; + const real vij_dr = dot(velij, rij)/(dot(rij, rij)+epsilon*h*h); + const real visc = -massj*alpha*c_f*h*vij_dr/rhoij; + F2 += visc*kernel_grad; + //} + } + force[groupIndex[i]] = make_real4(F1+F2, 0); + force[groupIndex[i]] += body_force; + } +} + +}//namspace fluidDynamics_ns + + +//calculate density +template +void fluidDynamics::calcDensity(real Dt, real h){ + int np = pd->getNumParticles(); + Kernel kernel; + // get a NeighborContainer + auto ni = nl->getNeighbourContainer(); + auto sortPos = nl->getPositionIterator(); + auto groupIndex = nl->getGroupIndexIterator(); + auto vel = pd->getVel(access::location::gpu, access::mode::readwrite).raw(); + //If mass is not allocated assume all masses are 1 + real *mass = nullptr; + if(pd->isMassAllocated()) + mass = pd->getMass(access::location::gpu, access::mode::read).raw(); + auto rho = pd->getRho(access::location::gpu, access::mode::readwrite).raw(); + auto rho0 = pd->getRho0(access::location::gpu, access::mode::readwrite).raw(); + auto pressure = pd->getPressure(access::location::gpu, access::mode::readwrite).raw(); + auto vol = pd->getVol(access::location::gpu, access::mode::readwrite).raw(); + + if(densityOpt==densityOption::RiemannDensity){ + fluidDynamics_ns::calcDensityRiemann_ker<<>>(ni, kernel, + sortPos, groupIndex, + np, box, + vel, rho, + mass, pressure, + h, Dt, c_f, rho0, vol); + }else if(densityOpt==densityOption::Continuity){ + fluidDynamics_ns::calcDensityArtificial_ker<<>>(ni, kernel, + sortPos, groupIndex, + np, box, + vel, rho, + mass, pressure, + h, Dt, c_f, rho0); + }else { + throw std::runtime_error("|fluidDynamics| \tdensityOpt is not valid!"); + } +} + +//calculate initial number density: sigma0 +void fluidDynamics::calcInitNumDensity(real h){ + int np = pd->getNumParticles(); + Kernel kernel; + // get a NeighborContainer + auto ni = nl->getNeighbourContainer(); + auto sortPos = nl->getPositionIterator(); + auto groupIndex = nl->getGroupIndexIterator(); + auto sigma0 = pd->getSigma0(access::location::gpu, access::mode::readwrite).raw(); + + fluidDynamics_ns::calcInitNumDensity_ker<<>>(ni, kernel, sortPos, + groupIndex, sigma0, + np, box, h, rho0_f); +} + +//density calculation using summation for free surface cases +void fluidDynamics::densitySumFreeSurface(real h){ + int np = pd->getNumParticles(); + Kernel kernel; + // get a NeighborContainer + auto ni = nl->getNeighbourContainer(); + auto sortPos = nl->getPositionIterator(); + auto groupIndex = nl->getGroupIndexIterator(); + auto sigma0 = pd->getSigma0(access::location::gpu, access::mode::readwrite).raw(); + auto density = pd->getRho(access::location::gpu, access::mode::readwrite).raw(); + auto rho0 = pd->getRho0(access::location::gpu, access::mode::readwrite).raw(); + + fluidDynamics_ns::densitySumFreeSurface_ker<<>>(ni, kernel, sortPos, + groupIndex, sigma0, + density, + np, box, + h, rho0); +} + +//density calculation using summation for gas phase +void fluidDynamics::densitySumLightPhase(real h){ + int np = pd->getNumParticles(); + Kernel kernel; + // get a NeighborContainer + auto ni = nl->getNeighbourContainer(); + auto sortPos = nl->getPositionIterator(); + auto groupIndex = nl->getGroupIndexIterator(); + auto sigma0 = pd->getSigma0(access::location::gpu, access::mode::readwrite).raw(); + auto density = pd->getRho(access::location::gpu, access::mode::readwrite).raw(); + auto rho0 = pd->getRho0(access::location::gpu, access::mode::readwrite).raw(); + auto pressure = pd->getPressure(access::location::gpu, access::mode::readwrite).raw(); + auto vol = pd->getVol(access::location::gpu, access::mode::readwrite).raw(); + auto mass = pd->getMass(access::location::gpu, access::mode::readwrite).raw(); + + fluidDynamics_ns::densitySumLightPhase_ker<<>>(ni, kernel, sortPos, + groupIndex, sigma0, + density, + np, box, + h, rho0, + pressure, c_f, + vol, mass); +} + +//calculate pressure for wall particles +void fluidDynamics::calcPressureBC(real h){ + int np = pd->getNumParticles(); + Kernel kernel; + // get a NeighborContainer + auto ni = nl->getNeighbourContainer(); + auto sortPos = nl->getPositionIterator(); + auto groupIndex = nl->getGroupIndexIterator(); + auto d_density = pd->getRho(access::location::gpu, access::mode::readwrite).raw(); + auto pressure = pd->getPressure(access::location::gpu, access::mode::readwrite).raw(); + auto rho0 = pd->getRho0(access::location::gpu, access::mode::readwrite).raw(); + + fluidDynamics_ns::calcPressureBC_ker<<>>(ni, kernel, sortPos, + groupIndex, np, box, + d_density, pressure, + h, c_f, rho0, bodyForce); +} + +//calculate forces +template +void fluidDynamics::calcForce(real h, real physicalTime){ + int np = pd->getNumParticles(); + Kernel kernel; + // get a NeighborContainer + auto ni = nl->getNeighbourContainer(); + auto sortPos = nl->getPositionIterator(); + auto groupIndex = nl->getGroupIndexIterator(); + auto vel = pd->getVel(access::location::gpu, access::mode::readwrite).raw(); + //If mass is not allocated assume all masses are 1 + real *d_mass = nullptr; + if(pd->isMassAllocated()) + d_mass = pd->getMass(access::location::gpu, access::mode::read).raw(); + auto rho = pd->getRho(access::location::gpu, access::mode::readwrite).raw(); + auto pressure = pd->getPressure(access::location::gpu, access::mode::readwrite).raw(); + auto force = pd->getForce(access::location::gpu, access::mode::readwrite).raw(); + auto vol = pd->getVol(access::location::gpu, access::mode::readwrite).raw(); +#ifdef _TRANSPORT_VELOCITY_ + real P_b = 5.0f*1.f*c_f*c_f; //the background pressure + auto vel_tv = pd->getVel_tv(access::location::gpu, access::mode::readwrite).raw(); + auto F_Pb = pd->getF_Pb(access::location::gpu, access::mode::readwrite).raw(); +#else + real P_b = 0.0f; + auto vel_tv = nullptr; + auto F_Pb = nullptr; +#endif + + if(forceOpt==forceOption::RiemannForce){ + fluidDynamics_ns::calcForceRiemann_ker<<>>(ni, kernel, sortPos, + groupIndex, np, box, + force, vel, + rho, d_mass, + pressure, + vel_tv, + F_Pb, P_b, + h, c_f, bodyForce, + physicalTime, + vol); + }else if(forceOpt==forceOption::Artificial){ + fluidDynamics_ns::calcForceArtificial_ker<<>>(ni, kernel, sortPos, + groupIndex, np, box, + force, vel, + rho, d_mass, + pressure, + h, c_f, bodyForce); + }else { + throw std::runtime_error("|fluidDynamics| \tforceOpt is not valid!"); + } +} + + +}//namespace gpu diff --git a/gpuSPHINXsys/src/fluidDynamics.cuh b/gpuSPHINXsys/src/fluidDynamics.cuh new file mode 100644 index 0000000000..2afdcce409 --- /dev/null +++ b/gpuSPHINXsys/src/fluidDynamics.cuh @@ -0,0 +1,64 @@ +/* + * Massoud Rezavand 2019. + * Technical University of Munich + * gpuSPHinxsys - An SPH solver for CUDA enabled GPUs + * + * This module collects all the functions required for + * the fluid dynamics related calculations + */ + +#ifndef FLUIDDYNAMICS_CUH +#define FLUIDDYNAMICS_CUH + +#include"System.h" +#include"ParticleData.cuh" +#include +#include + +namespace gpu{ +class fluidDynamics{ + +private: + cudaStream_t stream; + + shared_ptr pd; + shared_ptr nl; + shared_ptr sys; + + real c_f, rho0_f; + real3 bodyForce; + Box box; + +public: + struct Parameters{ + cudaStream_t stream; + real c_f, rho0_f; + real3 bodyForce; + Box box; + }; + + fluidDynamics(shared_ptr pd, + shared_ptr nl, + shared_ptr sys, + Parameters par); + ~fluidDynamics(); + + enum densityOption{RiemannDensity, Continuity}; + enum forceOption{RiemannForce, Artificial}; + + template + void calcDensity(real Dt, real h); + void calcPressureBC(real h); + template + void calcForce(real h, real physicalTime); + void calcInitNumDensity(real h); + void densitySumFreeSurface(real h); + void densitySumLightPhase(real h); + +}; + +}//namespace gpu + +#include"fluidDynamics.cu" +#endif + diff --git a/gpuSPHINXsys/src/inOut.cu b/gpuSPHINXsys/src/inOut.cu new file mode 100644 index 0000000000..d16e46808a --- /dev/null +++ b/gpuSPHINXsys/src/inOut.cu @@ -0,0 +1,294 @@ +/* + * Massoud Rezavand 2019. + * Technical University of Munich + * gpuSPHinxsys - An SPH solver for CUDA enabled GPUs + * + * This module writes the particle data on an external file + * for post-processing porpuses + * Two output formats are available, VTU as well as PLT + */ + +#include"inOut.cuh" +#include"Kernel.cuh" + +using Kernel = gpu::KernelFunction::Wendland_C4; + +namespace gpu{ + +inOut::inOut(shared_ptr pd, + shared_ptr nl, + Box box): + pd(pd), nl(nl){ + printf("|inOut| \tis called \n"); + if (fs::exists(output_folder) || fs::exists(observer_folder)){ + fs::remove_all(output_folder); + fs::remove_all(observer_folder); + } + if (!fs::exists(output_folder) || !fs::exists(observer_folder)){ + fs::create_directory(output_folder); + fs::create_directory(observer_folder); + printf("|inOut| \toutput folders created! \n"); + } +} + +inOut::~inOut(){ + printf("|inOut| \tcall ended! \n"); +} + +namespace inOut_ns{ + +//kernel to get the physical signal at the probe +template +__global__ void calcPressureSignal(NeighbourContainer ni, + Box box, + real h, + Kernel kernel, + real3 probe, + real* __restrict__ probeSignal, + real* __restrict__ mass, + real* __restrict__ rho, + real* __restrict__ p){ + real sum0 = real(); + real sum1 = real(); + auto it = ni.begin(probe); + + while(it){ + auto neigh = *it++; + const real4 posj = neigh.getPos(); + // include only Fluid neighboring particles + if(posj.w == WALL) continue; + const real3 rj = make_real3(neigh.getPos()); + const real rhoj = rho[neigh.getGroupIndex()]; + const real pj = p[neigh.getGroupIndex()]; + const real massj = mass[neigh.getGroupIndex()]; + const real3 rij = box.apply_pbc(probe-rj); + sum0 += kernel(rij, h, box.boxSize.z)*massj/rhoj; + sum1 += pj*kernel(rij, h, box.boxSize.z)*massj/rhoj; + } + *probeSignal = sum1/fmax(sum0, Eps); +} + +}//inOut_ns + + +template +inline void inOut::outputToFile(int outputCount){ + + const int np = pd->getNumParticles(); + const real4 *posType = pd->getPos(access::location::cpu, access::mode::read).raw(); + const real3 *vel = pd->getVel(access::location::cpu, access::mode::read).raw(); + const real *rho = pd->getRho(access::location::cpu, access::mode::read).raw(); + const real *press = pd->getPressure(access::location::cpu, access::mode::read).raw(); + + //seperate particles of different types + if(bodyOpt==Fluid){ + posTypeFluid.clear(); + velFluid.clear(); + rhoFluid.clear(); + pressFluid.clear(); + for (size_t i = 0; i < np; i++){ + if (posType[i].w==LIQUID){ + posTypeFluid.push_back(posType[i]); + velFluid.push_back(vel[i]); + rhoFluid.push_back(rho[i]); + pressFluid.push_back(press[i]); + } + } + posTypeBody = posTypeFluid; + velBody = velFluid; + rhoBody = rhoFluid; + pressBody = pressFluid; + bodyName = "Fluid_"; + npBody = posTypeBody.size(); + }else if(bodyOpt==Wall){ + posTypeWall.clear(); + velWall.clear(); + rhoWall.clear(); + pressWall.clear(); + for (size_t i = 0; i < np; i++){ + if (posType[i].w==WALL){ + posTypeWall.push_back(posType[i]); + velWall.push_back(vel[i]); + rhoWall.push_back(rho[i]); + pressWall.push_back(press[i]); + } + } + posTypeBody = posTypeWall; + velBody = velWall; + rhoBody = rhoWall; + pressBody = pressWall; + bodyName = "Wall_"; + npBody = posTypeBody.size(); + }else if(bodyOpt==thirdBody){ + posTypeThirdBody.clear(); + velThirdBody.clear(); + rhoThirdBody.clear(); + pressThirdBody.clear(); + for (size_t i = 0; i < np; i++){ + if (posType[i].w==THIRDBODY){ + posTypeThirdBody.push_back(posType[i]); + velThirdBody.push_back(vel[i]); + rhoThirdBody.push_back(rho[i]); + pressThirdBody.push_back(press[i]); + } + } + posTypeBody = posTypeThirdBody; + velBody = velThirdBody; + rhoBody = rhoThirdBody; + pressBody = pressThirdBody; + bodyName = "ThirdBody_"; + npBody = posTypeBody.size(); + }else{ + throw std::runtime_error("|inOut| \tBody option to output is not valid!"); + } + + // write in VTU format to use in Paraview + if(outputOpt==VTU) + { + std::string filefullpath = output_folder + "/" + bodyName + std::to_string(outputCount) + ".vtu"; + std::ofstream out(filefullpath.c_str(), std::ios::trunc); + + //beginning of the XML file + out << "\n"; + out << "\n"; + out << " \n"; + out << " \n"; + + //write position of particles + out << " \n"; + out << " \n"; + out << " "; + for (size_t i = 0; i < npBody; i++) { + out << posTypeBody[i].x << " " << posTypeBody[i].y << " " << posTypeBody[i].z << " "; + } + out << std::endl; + out << " \n"; + out << " \n"; + + //Particles data set + out << " \n"; + //wrtie density + out << " \n"; + out << " "; + for (size_t i = 0; i < npBody; i++) { + out << rhoBody[i] << " "; + } + out << std::endl; + out << " \n"; + + //wrtie type + out << " \n"; + out << " "; + for (size_t i = 0; i < npBody; i++) { + out << posTypeBody[i].w << " "; + } + out << std::endl; + out << " \n"; + + //wrtie id + out << " \n"; + out << " "; + for (size_t i = 0; i < npBody; i++) { + // out << id[i] << " "; + out << i << " "; + } + out << std::endl; + out << " \n"; + + //write pressure + out << " \n"; + out << " "; + for (size_t i = 0; i < npBody; i++) { + out << pressBody[i] << " "; + } + out << std::endl; + out << " \n"; + + //write velocity + out << " \n"; + out << " "; + for (size_t i = 0; i < npBody; i++) { + out << velBody[i].x << " " << velBody[i].y << " " << velBody[i].z << " "; + } + out << std::endl; + out << " \n"; + + //Particles data set ended + out << " \n"; + + //cells connectivity + out << " \n"; + out << " \n"; + out << " \n"; + out << " \n"; + out << " \n"; + out << " \n"; + out << " \n"; + out << " \n"; + + out << " \n"; + out << " \n"; + out << "\n"; + + out.close(); + } + + // write in PLT format to use in TecPlot + else if (outputOpt==PLT) { + std::string filefullpath = output_folder + "/" + bodyName + std::to_string(outputCount) + ".plt"; + std::ofstream out(filefullpath.c_str(), std::ios::trunc); + + out<<"VARIABLES = \"x\",\"y\",\"z\",\"type\",\"vx\",\"vy\",\"vz\",\"rho\",\"p\"\n"; + for (int i = 0; i < npBody; i++){ + out << posTypeBody[i].x << " " + << posTypeBody[i].y << " " + << posTypeBody[i].z << " " + << posTypeBody[i].w << " " + << velBody[i].x << " " << velBody[i].y << " " << velBody[i].z << " " + << rhoBody[i] << " " + << pressBody[i] << "\n"; + } + out.close(); + } + + else + { + throw std::runtime_error("|inOut| \tOutput format is not valid!"); + } +} + +//function to output the probe signal at a given probe point into a plt file +inline void inOut::probeSignalToFile(real h, real3 probe, std::string probeID, real physicalTime, int counter){ + + Kernel kernel; + + real *probeSignalTmp; + cudaMalloc(&probeSignalTmp, sizeof(real)); + + // get a NeighborContainer + auto ni = nl->getNeighbourContainer(); + + real *mass = nullptr; + if(pd->isMassAllocated()) + mass = pd->getMass(access::location::gpu, access::mode::read).raw(); + auto density = pd->getRho(access::location::gpu, access::mode::read).raw(); + auto pressure = pd->getPressure(access::location::gpu, access::mode::read).raw(); + + inOut_ns::calcPressureSignal<<<1, 1>>>(ni, box, h, kernel, probe, + probeSignalTmp, mass, density, pressure); + + real probeSignal = real(0.0); + CudaSafeCall(cudaMemcpy(&probeSignal, probeSignalTmp, sizeof(real), cudaMemcpyDeviceToHost)); + CudaSafeCall(cudaFree(probeSignalTmp)); + + std::string filefullpath = observer_folder + "/" + probeID + ".plt"; + std::ofstream out(filefullpath.c_str(), std::ios::app); + + if (counter == 1){ + out<<"VARIABLES=\"time\",\"probeSignal\"\n"; + } + out < +#include +#include +#include +#include +namespace fs = std::experimental::filesystem; + +namespace gpu{ +class inOut{ + +private: + + shared_ptr pd; + shared_ptr nl; + Box box; + + std::string output_folder = "./output_gpu"; + std::string observer_folder = "./observer_data"; + std::string bodyName; + size_t npBody; + + std::vector posTypeFluid, posTypeWall, posTypeThirdBody, posTypeBody; + std::vector velFluid, velWall, velThirdBody, velBody; + std::vector rhoFluid, rhoWall, rhoThirdBody, rhoBody, pressFluid, pressWall, pressThirdBody, pressBody; + +public: + + inOut(shared_ptr pd, shared_ptr nl, Box box); + ~inOut(); + + enum outputOption{VTU, PLT}; + enum bodyOption{Fluid, Wall, thirdBody}; + + template + void outputToFile(int outputCount); + + void probeSignalToFile(real h, real3 probe, std::string probeID, real physicalTime, int counter); + +}; + +}//namespace gpu + +#include"inOut.cu" +#endif + diff --git a/gpuSPHINXsys/src/include/nod/README.md b/gpuSPHINXsys/src/include/nod/README.md new file mode 100644 index 0000000000..0cf74f1d4f --- /dev/null +++ b/gpuSPHINXsys/src/include/nod/README.md @@ -0,0 +1,257 @@ +# Nod +[![Build Status](https://travis-ci.org/fr00b0/nod.svg?branch=master)](https://travis-ci.org/fr00b0/nod) +[![GitHub tag](https://img.shields.io/github/tag/fr00b0/nod.svg?label=version)](https://github.com/fr00b0/nod/releases) + +Dependency free, header only signals and slot library implemented with C++11. + +## Usage + +### Simple usage +The following example creates a signal and then connects a lambda as a slot. + +```cpp +// Create a signal which accepts slots with no arguments and void return value. +nod::signal signal; +// Connect a lambda slot that writes "Hello, World!" to stdout +signal.connect([](){ + std::cout << "Hello, World!" << std::endl; + }); +// Call the slots +signal(); +``` + +### Connecting multiple slots +If multiple slots are connected to the same signal, all of the slots will be +called when the signal is invoked. The slots will be called in the same order +as they where connected. + +```cpp +void endline() { + std::cout << std::endl; +} + +// Create a signal +nod::signal signal; +// Connect a lambda that prints a message +signal.connect([](){ + std::cout << "Message without endline!"; + }); +// Connect a function that prints a endline +signal.connect(endline); + +// Call the slots +signal(); +``` + +#### Slot type +The signal types in the library support connection of the same types that is +supported by `std::function`. + +### Slot arguments +When a signal calls it's connected slots, any arguments passed to the signal +are propagated to the slots. To make this work, we do need to specify the +signature of the signal to accept the arguments. + +```cpp +void print_sum( int x, int y ) { + std::cout << x << "+" << y << "=" << (x+y) << std::endl; +} +void print_product( int x, int y ) { + std::cout << x << "*" << y << "=" << (x*y) << std::endl; +} + + +// We create a signal with two integer arguments. +nod::signal signal; +// Let's connect our slot +signal.connect( print_sum ); +signal.connect( print_product ); + +// Call the slots +signal(10, 15); +signal(-5, 7); + +``` + +### Disconnecting slots +There are many circumstances where the programmer needs to diconnect a slot that +no longer want to recieve events from the signal. This can be really important +if the lifetime of the slots are shorter than the lifetime of the signal. That +could cause the signal to call slots that have been destroyed but not +disconnected, leading to undefined behaviour and probably segmentation faults. + +When a slot is connected, the return value from the `connect` method returns +an instance of the class `nod::connection`, that can be used to disconnect +that slot. + +```cpp +// Let's create a signal +nod::signal signal; +// Connect a slot, and save the connection +nod::connection connection = signal.connect([](){ + std::cout << "I'm connected!" << std::endl; + }); +// Triggering the signal will call the slot +signal(); +// Now we disconnect the slot +connection.disconnect(); +// Triggering the signal will no longer call the slot +signal(); +``` + +### Scoped connections +To assist in disconnecting slots, one can use the class `nod::scoped_connection` +to capture a slot connection. A scoped connection will automatically disconnect +the slot when the connection object goes out of scope. + +```cpp +// We create a signal +nod::signal signal; +// Let's use a scope to control lifetime +{ + // Let's save the connection in a scoped_connection + nod::scoped_connection connection = + signal.connect([](){ + std::cout << "This message should only be emitted once!" << std::endl; + }); + // If we trigger the signal, the slot will be called + signal(); +} // Our scoped connection is destructed, and disconnects the slot +// Triggering the signal now will not call the slot +signal(); +``` + +### Slot return values + +#### Accumulation of return values +It is possible for slots to have a return value. The return values can be +returned from the signal using a *accumulator*, which is a function object that +acts as a proxy object that processes the slot return values. When triggering a +signal through a accumulator, the accumulator gets called for each slot return +value, does the desired accumulation and then return the result to the code +triggering the signal. The accumulator is designed to work in a similar way as +the STL numerical algorithm `std::accumulate`. + +```cpp +// We create a singal with slots that return a value +nod::signal signal; +// Then we connect some signals +signal.connect( std::plus{} ); +signal.connect( std::multiplies{} ); +signal.connect( std::minus{} ); +// Let's say we want to calculate the sum of all the slot return values +// when triggering the singal with the parameters 10 and 100. +// We do this by accumulating the return values with the initial value 0 +// and a plus function object, like so: +std::cout << "Sum: " << signal.accumulate(0, std::plus{})(10,100) << std::endl; +// Or accumulate by multiplying (this needs 1 as initial value): +std::cout << "Product: " << signal.accumulate(1, std::multiplies{})(10,100) << std::endl; +// If we instead want to build a vector with all the return values +// we can accumulate them this way (start with a empty vector and add each value): +auto vec = signal.accumulate( std::vector{}, []( std::vector result, int value ) { + result.push_back( value ); + return result; + })(10,100); + +std::cout << "Vector: "; +for( auto const& element : vec ) { + std::cout << element << " "; +} +std::cout << std::endl; +``` +#### Aggregation +As we can see from the previous example, we can use the `accumulate` method if +we want to aggregate all the return values of the slots. Doing the aggregation +that way is not very optimal. It is both a inefficient algorithm for doing +aggreagtion to a container, and it obscures the call site as the caller needs to +express the aggregation using the verb *accumulate*. To remedy these +shortcomings we can turn to the method `aggregate` instead. This is a template +method, taking the type of container to aggregate to as a template parameter. + +```cpp +// We create a singal +nod::signal signal; +// Let's connect some slots +signal.connect( std::plus{} ); +signal.connect( std::multiplies{} ); +signal.connect( std::minus{} ); +// We can now trigger the signal and aggregate the slot return values +auto vec = signal.aggregate>(10,100); + +std::cout << "Result: "; +for( auto const& element : vec ) { + std::cout << element << " "; +} +std::cout << std::endl; +``` + +## Thread safety +There are two types of signals in the library. The first is `nod::signal` +which is safe to use in a multi threaded environment. Multiple threads can read, +write, connect slots and disconnect slots simultaneously, and the signal will +provide the nessesary synchronization. When triggering a slignal, all the +registered slots will be called and executed by the thread that triggered the +signal. + +The second type of signal is `nod::unsafe_signal` which is **not** safe to +use in a multi threaded environment. No syncronization will be performed on the +internal state of the signal. Instances of the signal should theoretically be +safe to read from multiple thread simultaneously, as long as no thread is +writing to the same object at the same time. There can be a performance gain +involved in using the unsafe version of a signal, since no syncronization +primitives will be used. + +`nod::connection` and `nod::scoped_connection` are thread safe for reading from +multiple threads, as long as no thread is writing to the same object. Writing in +this context means calling any non const member function, including destructing +the object. If an object is being written by one thread, then all reads and +writes to that object from the same or other threads needs to be prevented. +This basically means that a connection is only allowed to be disconnected from +one thread, and you should not check connection status or reassign the +connection while it is being disconnected. + +## Building the tests +The test project uses [premake5](https://premake.github.io/download.html) to +generate make files or similiar. + +### Linux +To build and run the tests using gcc and gmake on linux, execute the following +from the test directory: +```bash +premake5 gmake +make -C build/gmake +bin/gmake/debug/nod_tests +``` + +### Visual Studio 2013 +To build and run the tests, execute the following from the test directory: + +```batchfile +REM Adjust paths to suite your environment +c:\path\to\premake\premake5.exe vs2013 +"c:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\vsvars32.bat" +msbuild /m build\vs2013\nod_tests.sln +bin\vs2013\debug\nod_tests.exe +``` + +## The MIT License (MIT) + +Copyright (c) 2015 Fredrik Berggren + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/gpuSPHINXsys/src/include/nod/nod.hpp b/gpuSPHINXsys/src/include/nod/nod.hpp new file mode 100644 index 0000000000..72190094ad --- /dev/null +++ b/gpuSPHINXsys/src/include/nod/nod.hpp @@ -0,0 +1,631 @@ +#ifndef IG_NOD_INCLUDE_NOD_HPP +#define IG_NOD_INCLUDE_NOD_HPP + +#include // std::vector +#include // std::function +#include // std::mutex, std::lock_guard +#include // std::shared_ptr, std::weak_ptr +#include // std::find_if() +#include // assert() +#include // std::this_thread::yield() +#include // std::is_same +#include // std::back_inserter + +namespace nod { + // implementational details + namespace detail { + /// Interface for type erasure when disconnecting slots + struct disconnector { + virtual void operator()( std::size_t index ) const = 0; + }; + /// Deleter that doesn't delete + inline void no_delete(disconnector*){ + }; + } // namespace detail + + /// Base template for the signal class + template + class signal_type; + + + /// Connection class. + /// + /// This is used to be able to disconnect slots after they have been connected. + /// Used as return type for the connect method of the signals. + /// + /// Connections are default constructible. + /// Connections are not copy constructible or copy assignable. + /// Connections are move constructible and move assignable. + /// + class connection { + public: + /// Default constructor + connection() : + _index() + {} + + // Connection are not copy constructible or copy assignable + connection( connection const& ) = delete; + connection& operator=( connection const& ) = delete; + + /// Move constructor + /// @param other The instance to move from. + connection( connection&& other ) : + _weak_disconnector( std::move(other._weak_disconnector) ), + _index( other._index ) + {} + + /// Move assign operator. + /// @param other The instance to move from. + connection& operator=( connection&& other ) { + _weak_disconnector = std::move( other._weak_disconnector ); + _index = other._index; + return *this; + } + + /// @returns `true` if the connection is connected to a signal object, + /// and `false` otherwise. + bool connected() const { + return !_weak_disconnector.expired(); + } + + /// Disconnect the slot from the connection. + /// + /// If the connection represents a slot that is connected to a signal object, calling + /// this method will disconnect the slot from that object. The result of this operation + /// is that the slot will stop receiving calls when the signal is invoked. + void disconnect(); + + private: + /// The signal template is a friend of the connection, since it is the + /// only one allowed to create instances using the meaningful constructor. + template friend class signal_type; + + /// Create a connection. + /// @param shared_disconnector Disconnector instance that will be used to disconnect + /// the connection when the time comes. A weak pointer + /// to the disconnector will be held within the connection + /// object. + /// @param index The slot index of the connection. + connection( std::shared_ptr const& shared_disconnector, std::size_t index ) : + _weak_disconnector( shared_disconnector ), + _index( index ) + {} + + /// Weak pointer to the current disconnector functor. + std::weak_ptr _weak_disconnector; + /// Slot index of the connected slot. + std::size_t _index; + }; + + /// Scoped connection class. + /// + /// This type of connection is automatically disconnected when + /// the connection object is destructed. + /// + class scoped_connection + { + public: + /// Scoped are default constructible + scoped_connection() = default; + /// Scoped connections are not copy constructible + scoped_connection( scoped_connection const& ) = delete; + /// Scoped connections are not copy assingable + scoped_connection& operator=( scoped_connection const& ) = delete; + + /// Move constructor + scoped_connection( scoped_connection&& other ) : + _connection( std::move(other._connection) ) + {} + + /// Move assign operator. + /// @param other The instance to move from. + scoped_connection& operator=( scoped_connection&& other ) { + reset( std::move( other._connection ) ); + return *this; + } + + /// Construct a scoped connection from a connection object + /// @param connection The connection object to manage + scoped_connection( connection&& c ) : + _connection( std::forward(c) ) + {} + + /// destructor + ~scoped_connection() { + disconnect(); + } + + /// Assignment operator moving a new connection into the instance. + /// @note If the scoped_connection instance already contains a + /// connection, that connection will be disconnected as if + /// the scoped_connection was destroyed. + /// @param c New connection to manage + scoped_connection& operator=( connection&& c ) { + reset( std::forward(c) ); + return *this; + } + + /// Reset the underlying connection to another connection. + /// @note The connection currently managed by the scoped_connection + /// instance will be disconnected when resetting. + /// @param c New connection to manage + void reset( connection&& c = {} ) { + disconnect(); + _connection = std::move(c); + } + + /// Release the underlying connection, without disconnecting it. + /// @returns The newly released connection instance is returned. + connection release() { + connection c = std::move(_connection); + _connection = connection{}; + return c; + } + + /// + /// @returns `true` if the connection is connected to a signal object, + /// and `false` otherwise. + bool connected() const { + return _connection.connected(); + } + + /// Disconnect the slot from the connection. + /// + /// If the connection represents a slot that is connected to a signal object, calling + /// this method will disconnect the slot from that object. The result of this operation + /// is that the slot will stop receiving calls when the signal is invoked. + void disconnect() { + _connection.disconnect(); + } + + private: + /// Underlying connection object + connection _connection; + }; + + /// Policy for multi threaded use of signals. + /// + /// This policy provides mutex and lock types for use in + /// a multithreaded environment, where signals and slots + /// may exists in different threads. + /// + /// This policy is used in the `nod::signal` type provided + /// by the library. + struct multithread_policy + { + using mutex_type = std::mutex; + using mutex_lock_type = std::lock_guard; + /// Function that yields the current thread, allowing + /// the OS to reschedule. + static void yield_thread() { + std::this_thread::yield(); + } + }; + + /// Policy for single threaded use of signals. + /// + /// This policy provides dummy implementations for mutex + /// and lock types, resulting in that no synchronization + /// will take place. + /// + /// This policy is used in the `nod::unsafe_signal` type + /// provided by the library. + struct singlethread_policy + { + /// Dummy mutex type that doesn't do anything + struct mutex_type{}; + /// Dummy lock type, that doesn't do any locking. + struct mutex_lock_type + { + /// A lock type must be constructible from a + /// mutex type from the same thread policy. + explicit mutex_lock_type( mutex_type const& ) { + } + }; + /// Dummy implementation of thread yielding, that + /// doesn't do any actual yielding. + static void yield_thread() { + } + }; + + /// Signal accumulator class template. + /// + /// This acts sort of as a proxy for triggering a signal and + /// accumulating the slot return values. + /// + /// This class is not really intended to instantiate by client code. + /// Instances are aquired as return values of the method `accumulate()` + /// called on signals. + /// + /// @tparam S Type of signal. The signal_accumulator acts + /// as a type of proxy for a signal instance of + /// this type. + /// @tparam T Type of initial value of the accumulate algorithm. + /// This type must meet the requirements of `CopyAssignable` + /// and `CopyConstructible` + /// @tparam F Type of accumulation function. + /// @tparam A... Argument types of the underlying signal type. + /// + template + class signal_accumulator + { + public: + /// Result type when calling the accumulating function operator. + using result_type = typename std::result_of::type; + + /// Construct a signal_accumulator as a proxy to a given signal + // + /// @param signal Signal instance. + /// @param init Initial value of the accumulate algorithm. + /// @param func Binary operation function object that will be + /// applied to all slot return values. + /// The signature of the function should be + /// equivalent of the following: + /// `R func( T1 const& a, T2 const& b )` + /// - The signature does not need to have `const&`. + /// - The initial value, type `T`, must be implicitly + /// convertible to `R` + /// - The return type `R` must be implicitly convertible + /// to type `T1`. + /// - The type `R` must be `CopyAssignable`. + /// - The type `S::slot_type::result_type` (return type of + /// the signals slots) must be implicitly convertible to + /// type `T2`. + signal_accumulator( S const& signal, T init, F func ) : + _signal( signal ), + _init( init ), + _func( func ) + {} + + /// Function call operator. + /// + /// Calling this will trigger the underlying signal and accumulate + /// all of the connected slots return values with the current + /// initial value and accumulator function. + /// + /// When called, this will invoke the accumulator function will + /// be called for each return value of the slots. The semantics + /// are similar to the `std::accumulate` algorithm. + /// + /// @param args Arguments to propagate to the slots of the + /// underlying when triggering the signal. + result_type operator()( A const& ... args ) const { + return _signal.trigger_with_accumulator( _init, _func, args... ); + } + + private: + + /// Reference to the underlying signal to proxy. + S const& _signal; + /// Initial value of the accumulate algorithm. + T _init; + /// Accumulator function. + F _func; + + }; + + /// Signal template specialization. + /// + /// This is the main signal implementation, and it is used to + /// implement the observer pattern whithout the overhead + /// boilerplate code that typically comes with it. + /// + /// Any function or function object is considered a slot, and + /// can be connected to a signal instance, as long as the signature + /// of the slot matches the signature of the signal. + /// + /// @tparam P Threading policy for the signal. + /// A threading policy must provide two type definitions: + /// - P::mutex_type, this type will be used as a mutex + /// in the signal_type class template. + /// - P::mutex_lock_type, this type must implement a + /// constructor that takes a P::mutex_type as a parameter, + /// and it must have the semantics of a scoped mutex lock + /// like std::lock_guard, i.e. locking in the constructor + /// and unlocking in the destructor. + /// + /// @tparam R Return value type of the slots connected to the signal. + /// @tparam A... Argument types of the slots connected to the signal. + template + class signal_type + { + public: + /// signals are not copy constructible + signal_type( signal_type const& ) = delete; + /// signals are not copy assignable + signal_type& operator=( signal_type const& ) = delete; + + /// signals are default constructible + signal_type() : + _slot_count(0) + {} + + // Destruct the signal object. + ~signal_type() { + invalidate_disconnector(); + } + + /// Type that will be used to store the slots for this signal type. + using slot_type = std::function; + /// Type that is used for counting the slots connected to this signal. + using size_type = typename std::vector::size_type; + + + /// Connect a new slot to the signal. + /// + /// The connected slot will be called every time the signal + /// is triggered. + /// @param slot The slot to connect. This must be a callable with + /// the same signature as the signal itself. + /// @return A connection object is returned, and can be used to + /// disconnect the slot. + template + connection connect( T&& slot ) { + mutex_lock_type lock{ _mutex }; + _slots.push_back( std::forward(slot) ); + std::size_t index = _slots.size()-1; + if( _shared_disconnector == nullptr ) { + _disconnector = disconnector{ this }; + _shared_disconnector = std::shared_ptr{&_disconnector, detail::no_delete}; + } + ++_slot_count; + return connection{ _shared_disconnector, index }; + } + + /// Function call operator. + /// + /// Calling this is how the signal is triggered and the + /// connected slots are called. + /// + /// @note The slots will be called in the order they were + /// connected to the signal. + /// + /// @param args Arguments that will be propagated to the + /// connected slots when they are called. + void operator()( A const&... args ) const { + for( auto const& slot : copy_slots() ) { + if( slot ) { + slot( args... ); + } + } + } + + /// Construct a accumulator proxy object for the signal. + /// + /// The intended purpose of this function is to create a function + /// object that can be used to trigger the signal and accumulate + /// all the slot return values. + /// + /// The algorithm used to accumulate slot return values is similar + /// to `std::accumulate`. A given binary function is called for + /// each return value with the parameters consisting of the + /// return value of the accumulator function applied to the + /// previous slots return value, and the current slots return value. + /// A initial value must be provided for the first slot return type. + /// + /// @note This can only be used on signals that have slots with + /// non-void return types, since we can't accumulate void + /// values. + /// + /// @tparam T The type of the initial value given to the accumulator. + /// @tparam F The accumulator function type. + /// @param init Initial value given to the accumulator. + /// @param op Binary operator function object to apply by the accumulator. + /// The signature of the function should be + /// equivalent of the following: + /// `R func( T1 const& a, T2 const& b )` + /// - The signature does not need to have `const&`. + /// - The initial value, type `T`, must be implicitly + /// convertible to `R` + /// - The return type `R` must be implicitly convertible + /// to type `T1`. + /// - The type `R` must be `CopyAssignable`. + /// - The type `S::slot_type::result_type` (return type of + /// the signals slots) must be implicitly convertible to + /// type `T2`. + template + signal_accumulator accumulate( T init, F op ) const { + static_assert( std::is_same::value == false, "Unable to accumulate slot return values with 'void' as return type." ); + return { *this, init, op }; + } + + + /// Trigger the signal, calling the slots and aggregate all + /// the slot return values into a container. + /// + /// @tparam C The type of container. This type must be + /// `DefaultConstructible`, and usable with + /// `std::back_insert_iterator`. Additionally it + /// must be either copyable or moveable. + /// @param args The arguments to propagate to the slots. + template + C aggregate( A const&... args ) const { + static_assert( std::is_same::value == false, "Unable to aggregate slot return values with 'void' as return type." ); + C container; + auto iterator = std::back_inserter( container ); + for( auto const& slot : copy_slots() ) { + if( slot ) { + (*iterator) = slot( args... ); + } + } + return container; + } + + /// Count the number of slots connected to this signal + /// @returns The number of connected slots + size_type slot_count() const { + return _slot_count; + } + + /// Determine if the signal is empty, i.e. no slots are connected + /// to it. + /// @returns `true` is returned if the signal has no connected + /// slots, and `false` otherwise. + bool empty() const { + return slot_count() == 0; + } + + /// Disconnects all slots + /// @note This operation invalidates all scoped_connection objects + void disconnect_all_slots() { + mutex_lock_type lock{ _mutex }; + _slots.clear(); + _slot_count = 0; + invalidate_disconnector(); + } + + private: + template friend class signal_accumulator; + /// Thread policy currently in use + using thread_policy = P; + /// Type of mutex, provided by threading policy + using mutex_type = typename thread_policy::mutex_type; + /// Type of mutex lock, provided by threading policy + using mutex_lock_type = typename thread_policy::mutex_lock_type; + + /// Invalidate the internal disconnector object in a way + /// that is safe according to the current thread policy. + /// + /// This will effectively make all current connection objects to + /// to this signal incapable of disconnecting, since they keep a + /// weak pointer to the shared disconnector object. + void invalidate_disconnector() { + // If we are unlucky, some of the connected slots + // might be in the process of disconnecting from other threads. + // If this happens, we are risking to destruct the disconnector + // object managed by our shared pointer before they are done + // disconnecting. This would be bad. To solve this problem, we + // discard the shared pointer (that is pointing to the disconnector + // object within our own instance), but keep a weak pointer to that + // instance. We then stall the destruction until all other weak + // pointers have released their "lock" (indicated by the fact that + // we will get a nullptr when locking our weak pointer). + std::weak_ptr weak{_shared_disconnector}; + _shared_disconnector.reset(); + while( weak.lock() != nullptr ) { + // we just yield here, allowing the OS to reschedule. We do + // this until all threads has released the disconnector object. + thread_policy::yield_thread(); + } + } + + /// Retrieve a copy of the current slots + /// + /// It's useful and necessary to copy the slots so we don't need + /// to hold the lock while calling the slots. If we hold the lock + /// we prevent the called slots from modifying the slots vector. + /// This simple "double buffering" will allow slots to disconnect + /// themself or other slots and connect new slots. + std::vector copy_slots() const + { + mutex_lock_type lock{ _mutex }; + return _slots; + } + + /// Implementation of the signal accumulator function call + template + typename signal_accumulator::result_type trigger_with_accumulator( T value, F& func, A const&... args ) const { + for( auto const& slot : copy_slots() ) { + if( slot ) { + value = func( value, slot( args... ) ); + } + } + return value; + } + + /// Implementation of the disconnection operation. + /// + /// This is private, and only called by the connection + /// objects created when connecting slots to this signal. + /// @param index The slot index of the slot that should + /// be disconnected. + void disconnect( std::size_t index ) { + mutex_lock_type lock( _mutex ); + assert( _slots.size() > index ); + if( _slots[ index ] != nullptr ) { + --_slot_count; + } + _slots[ index ] = slot_type{}; + while( _slots.size()>0 && !_slots.back() ) { + _slots.pop_back(); + } + } + + /// Implementation of the shared disconnection state + /// used by all connection created by signal instances. + /// + /// This inherits the @ref detail::disconnector interface + /// for type erasure. + struct disconnector : + detail::disconnector + { + /// Default constructor, resulting in a no-op disconnector. + disconnector() : + _ptr(nullptr) + {} + + /// Create a disconnector that works with a given signal instance. + /// @param ptr Pointer to the signal instance that the disconnector + /// should work with. + disconnector( signal_type* ptr ) : + _ptr( ptr ) + {} + + /// Disconnect a given slot on the current signal instance. + /// @note If the instance is default constructed, or created + /// with `nullptr` as signal pointer this operation will + /// effectively be a no-op. + /// @param index The index of the slot to disconnect. + void operator()( std::size_t index ) const override { + if( _ptr ) { + _ptr->disconnect( index ); + } + } + + /// Pointer to the current signal. + signal_type* _ptr; + }; + + /// Mutex to synchronize access to the slot vector + mutable mutex_type _mutex; + /// Vector of all connected slots + std::vector _slots; + /// Number of connected slots + size_type _slot_count; + /// Disconnector operation, used for executing disconnection in a + /// type erased manner. + disconnector _disconnector; + /// Shared pointer to the disconnector. All connection objects has a + /// weak pointer to this pointer for performing disconnections. + std::shared_ptr _shared_disconnector; + }; + + // Implementation of the disconnect operation of the connection class + inline void connection::disconnect() { + auto ptr = _weak_disconnector.lock(); + if( ptr ) { + (*ptr)( _index ); + } + _weak_disconnector.reset(); + } + + /// Signal type that is safe to use in multithreaded environments, + /// where the signal and slots exists in different threads. + /// The multithreaded policy provides mutexes and locks to synchronize + /// access to the signals internals. + /// + /// This is the recommended signal type, even for single threaded + /// environments. + template using signal = signal_type; + + /// Signal type that is unsafe in multithreaded environments. + /// No synchronizations are provided to the signal_type for accessing + /// the internals. + /// + /// Only use this signal type if you are sure that your environment is + /// single threaded and performance is of importance. + template using unsafe_signal = signal_type; +} // namespace nod + +#endif // IG_NOD_INCLUDE_NOD_HPP diff --git a/gpuSPHINXsys/src/timeStepping.cu b/gpuSPHINXsys/src/timeStepping.cu new file mode 100644 index 0000000000..aef181e9be --- /dev/null +++ b/gpuSPHINXsys/src/timeStepping.cu @@ -0,0 +1,259 @@ +/* + * Massoud Rezavand 2019. + * Technical University of Munich + * gpuSPHinxsys - An SPH solver for CUDA enabled GPUs + * + * This module integrates the particles dynamics using + * different time marching algorithms + */ + +#include"timeStepping.cuh" +#include"dtSizeCalc.cuh" +#include"fluidDynamics.cuh" +#include"inOut.cuh" +#include + + +namespace gpu{ + +timeStepping::timeStepping(shared_ptr pd, + shared_ptr nl, + shared_ptr sys, + timeStepping::Parameters par): + pd(pd), nl(nl), sys(sys), U_f(par.U_f), + box(par.box), h(par.h), c_f(par.c_f), probe1(par.probe1), + probe2(par.probe2), rho0_f(par.rho0_f), bodyForce(par.bodyForce){ + printf("|timeStepping| \tis called with c_f = %.1f, rho0_f = %.1f \n", c_f, rho0_f); + CudaSafeCall(cudaStreamCreate(&stream)); +} + +timeStepping::~timeStepping(){ + cudaStreamDestroy(stream); + printf("|timeStepping| \tcall ended! \n"); +} + +namespace timeStepping_ns{ +//Kernel for time stepping +template +__global__ void integration_ker(real4* __restrict__ pos, + real3* __restrict__ vel, + real4* __restrict__ force, + real3* __restrict__ F_Pb, + real3* __restrict__ vel_tv, + const int* __restrict__ groupIndex, + int np, + real dt){ + int i = blockIdx.x*blockDim.x+threadIdx.x; + if(i>=np) return; + + vel[groupIndex[i]] += make_real3(force[groupIndex[i]])*dt*real(0.5); + + //updat positions at the first half step + if(halfSteps==timeStepping::firstHalf){ +#ifndef _TRANSPORT_VELOCITY_ + real3 newPos = make_real3(pos[groupIndex[i]]) + vel[groupIndex[i]]*dt; + pos[groupIndex[i]] = make_real4(newPos, pos[groupIndex[i]].w); +#else + vel_tv[groupIndex[i]] = vel[groupIndex[i]] + F_Pb[groupIndex[i]]*dt*real(0.5); + real3 newPos = make_real3(pos[groupIndex[i]]) + vel_tv[groupIndex[i]]*dt; + pos[groupIndex[i]] = make_real4(newPos, pos[groupIndex[i]].w); + F_Pb[groupIndex[i]] = make_real3(0); +#endif + //Reset force + force[groupIndex[i]] = make_real4(0); + } +} + +}//timeStepping_ns + +//function for time integration +template +void timeStepping::integration(real currentDt){ + int np = pd->getNumParticles(); + int Nthreads=128; + int Nblocks=np/Nthreads + ((np%Nthreads)?1:0); + auto groupIndex = nl->getGroupIndexIterator(); + auto pos = pd->getPos(access::location::gpu, access::mode::readwrite); + auto vel = pd->getVel(access::location::gpu, access::mode::readwrite); + auto force = pd->getForce(access::location::gpu, access::mode::readwrite); +#ifdef _TRANSPORT_VELOCITY_ + auto F_Pb = pd->getF_Pb(access::location::gpu, access::mode::readwrite).raw(); + auto vel_tv = pd->getVel_tv(access::location::gpu, access::mode::readwrite).raw(); +#else + auto F_Pb = nullptr; + auto vel_tv = nullptr; +#endif + timeStepping_ns::integration_ker<<>>(pos.raw(), + vel.raw(), + force.raw(), + F_Pb, + vel_tv, + groupIndex, + np, + currentDt); +} + +//velocity verlet integration +//called from the main inerface to run the simulation +void timeStepping::velVerletIntg(){ + dtSizeCalc::Parameters parDT; + parDT.stream = stream; + auto dtSizeCalc_ptr = std::make_shared(pd, nl, sys, parDT); + + fluidDynamics::Parameters parFD; + parFD.stream = stream; + parFD.box = box; + parFD.rho0_f = rho0_f; + parFD.bodyForce = bodyForce; + parFD.c_f = c_f; + auto fluidDynamics_ptr = std::make_shared(pd, nl, sys, parFD); + + auto inOut_ptr = std::make_shared(pd, nl, box); + //the observer's coordinate + real3 probe1Point = make_real3(std::get<0>(probe1), + std::get<1>(probe1), + std::get<2>(probe1)); + real3 probe2Point = make_real3(std::get<0>(probe2), + std::get<1>(probe2), + std::get<2>(probe2)); + + //output the initial configuration + inOut_ptr->outputToFile(0); + inOut_ptr->outputToFile(0); + inOut_ptr->outputToFile(0); + + real rcut = Kernel::getCutOff(h); + //TODO this updateNeighbourList can be much improved + nl->updateNeighbourList(box, rcut, stream); + //calculate initial number density: sigma0 + fluidDynamics_ptr->calcInitNumDensity(h); + + auto t1 = std::chrono::high_resolution_clock::now(); + std::chrono::duration interval; + + //computation loop starts + while (physical_time < End_time){ + + nl->updateNeighbourList(box, rcut, stream); + + fluidDynamics_ptr->densitySumFreeSurface(h); + + integration(dt); + +// fluidDynamics_ptr->densitySumFreeSurface(h); + +// fluidDynamics_ptr->densitySumLightPhase(h); + + fluidDynamics_ptr->calcDensity(dt, h); + fluidDynamics_ptr->calcPressureBC(h); + fluidDynamics_ptr->calcForce(h, physical_time); + + integration(dt); + + dt = dtSizeCalc_ptr->calcDt(h, c_f); + + physical_time += dt; + + if (iter_counter % screen_interval == 0){ + printf("Step: %d \tTime: %0.3f \tdt: %f \n", iter_counter, physical_time, dt); + } + + auto t2 = std::chrono::high_resolution_clock::now(); + //write results into a file + if (output_counter < physical_time*output_interval){ + printf("|I/O| \tWriting output to disk ... file No. %d \n", output_counter+1); + //write the simulation results into a file + inOut_ptr->outputToFile(output_counter+1); + inOut_ptr->outputToFile(output_counter+1); + //write the proble signals for a givnen obsever point into a file + inOut_ptr->probeSignalToFile(h, probe1Point, std::get<3>(probe1), physical_time, output_counter+1); + inOut_ptr->probeSignalToFile(h, probe2Point, std::get<3>(probe2), physical_time, output_counter+1); + output_counter++; + } + auto t3 = std::chrono::high_resolution_clock::now(); + interval += t3 - t2; + + //resorting particles (slightly improves the performance) + if(iter_counter%500 == 0){ + pd->sortParticles(); + } + iter_counter++; + } + auto t4 = std::chrono::high_resolution_clock::now(); + std::chrono::duration tt = t4 - t1 - interval; + printf("Total wall clock time for computation: %.3f seconds \n", tt.count()); + printf("Total number of Iterations: %d \n", iter_counter); +} + +//Dual-Criteria time integration scheme +//called from the main inerface to run the simulation +void timeStepping::dualCriteriaIntg(){ + dtSizeCalc::Parameters parDT; + parDT.stream = stream; + auto dtSizeCalc_ptr = std::make_shared(pd, nl, sys, parDT); + + fluidDynamics::Parameters parFD; + parFD.stream = stream; + parFD.box = box; + parFD.rho0_f = rho0_f; + parFD.bodyForce = bodyForce; + parFD.c_f = c_f; + auto fluidDynamics_ptr = std::make_shared(pd, nl, sys, parFD); + + auto inOut_ptr = std::make_shared(pd, nl, box); + //output the initial configuration + inOut_ptr->outputToFile(0); + inOut_ptr->outputToFile(0); + + real rcut = Kernel::getCutOff(h); + //TODO this updateNeighbourList can be much improved + nl->updateNeighbourList(box, rcut, stream); + //calculate initial number density: sigma0 + fluidDynamics_ptr->calcInitNumDensity(h); + + //computation loop starts + while (physical_time < End_time){ + + nl->updateNeighbourList(box, rcut, stream); + + Dt = dtSizeCalc_ptr->calcDtAdv(h, U_f); + fluidDynamics_ptr->densitySumFreeSurface(h); + + real relaxation_time = 0.0; + while (relaxation_time < Dt){ + + + integration(dt); + + fluidDynamics_ptr->calcDensity(dt, h); + fluidDynamics_ptr->calcPressureBC(h); + fluidDynamics_ptr->calcForce(h, physical_time); + + integration(dt); + + dt = dtSizeCalc_ptr->calcDtAcs(h, c_f); + + relaxation_time += dt; + physical_time += dt; + } + + if (iter_counter % screen_interval == 0){ + printf("Step: %d \tTime: %0.3f \tDt: %f \tdt: %f \n", iter_counter, physical_time, Dt, dt); + } + + //write results to a file + if (output_counter < physical_time*output_interval){ + printf("|I/O| \tWriting output to disk ... file No. %d \n", output_counter+1); + inOut_ptr->outputToFile(output_counter+1); + output_counter++; + } + + //resorting particles + if(iter_counter%500 == 0){ + pd->sortParticles(); + } + iter_counter++; + } +} + +}//namspace gpu diff --git a/gpuSPHINXsys/src/timeStepping.cuh b/gpuSPHINXsys/src/timeStepping.cuh new file mode 100644 index 0000000000..06c4ae81cf --- /dev/null +++ b/gpuSPHINXsys/src/timeStepping.cuh @@ -0,0 +1,74 @@ +/* + * Massoud Rezavand 2019. + * Technical University of Munich + * gpuSPHinxsys - An SPH solver for CUDA enabled GPUs + * + * This module integrates the particles dynamics using + * different time marching algorithms + */ + +#ifndef TIMESTEPPING_CUH +#define TIMESTEPPING_CUH + +#include"System.h" +#include"ParticleData.cuh" +#include"CellList.cuh" +#include + +namespace gpu{ +class timeStepping{ + +private: + shared_ptr pd; + shared_ptr nl; + shared_ptr sys; + + real U_f, h, c_f, rho0_f; + real3 bodyForce; + std::tuple probe1, probe2; + Box box; + + cudaStream_t stream; + + real dt = 1.e-5f; //acoustic time step size + real Dt = 1.e-5f; //advection time step size + real End_time = 10.0; //End point of the simulation + int screen_interval = 100; //screen info output interval + int output_interval = 5; //number of outputs per second + int output_counter = 0; //output counter + int iter_counter = 0; //number of iterations + real physical_time = 0.0; //physical simulation time + + template + void integration(real currentDt); + +public: + struct Parameters{ + real U_f, h, c_f, rho0_f; + real3 bodyForce; + std::tuple probe1, probe2; + Box box; + }; + + enum updateOption{firstHalf, secondHalf}; + + timeStepping(shared_ptr pd, + shared_ptr nl, + shared_ptr sys, + Parameters par); + + ~timeStepping(); + + void velVerletIntg(); + void dualCriteriaIntg(); + + /// TODO: this funcs will be needed when no pd, via cudaMemcpy + /*templatevoid outputToFile(InOut InOut_ptr, int outputOpt, int outputCount);*/ + +}; + +}//namespace gpu + +#include"timeStepping.cu" +#endif + diff --git a/gpuSPHINXsys/src/vector.cuh b/gpuSPHINXsys/src/vector.cuh new file mode 100644 index 0000000000..5e50085b90 --- /dev/null +++ b/gpuSPHINXsys/src/vector.cuh @@ -0,0 +1,926 @@ +#ifndef VECTOR_OVERLOADS_H +#define VECTOR_OVERLOADS_H +//Include built in ones +#include "cuda_runtime.h" + +#include +#include"defines.h" + +typedef unsigned short ushort; +typedef unsigned int uint; +typedef unsigned long long int ullint; + +#define VECATTR inline __host__ __device__ + +/////////////////////FLOAT2/////////////////////////////// + +VECATTR int2 make_int2(float2 a){return make_int2((int)a.x, (int)a.y);} +VECATTR float2 make_float2(float a){return make_float2(a, a);} + +VECATTR float2 make_float2(int2 a){return make_float2(a.x, a.y);} +VECATTR float2 make_float2(float2 a){return make_float2(a.x, a.y);} + +VECATTR float2 operator +(const float2 &a, const float2 &b){return make_float2( + a.x + b.x, + a.y + b.y); +} +VECATTR void operator +=(float2 &a, const float2 &b){ + a.x += b.x; + a.y += b.y; +} +VECATTR float2 operator +(const float2 &a, const float &b){return make_float2( + a.x + b, + a.y + b); +} +VECATTR float2 operator +(const float &b, const float2 &a){return a+b;} +VECATTR void operator +=(float2 &a, const float &b){ + a.x += b; + a.y += b; +} + +VECATTR float2 operator -(const float2 &a, const float2 &b){return make_float2( + a.x - b.x, + a.y - b.y); +} + +VECATTR void operator -=(float2 &a, const float2 &b){ + a.x -= b.x; + a.y -= b.y; +} + +VECATTR float2 operator -(const float2 &a, const float &b){return make_float2( + a.x - b, + a.y - b); +} + +VECATTR float2 operator -(const float &b, const float2 &a){return make_float2( + b-a.x, + b-a.y); +} +VECATTR void operator -=(float2 &a, const float &b){ + a.x -= b; + a.y -= b; +} +VECATTR float2 operator *(const float2 &a, const float2 &b){ + return make_float2(a.x * b.x, + a.y * b.y); +} +VECATTR void operator *=(float2 &a, const float2 &b){ + a.x *= b.x; + a.y *= b.y; +} +VECATTR float2 operator *(const float2 &a, const float &b){ + return make_float2(a.x * b, + a.y * b); +} +VECATTR float2 operator *(const float &b, const float2 &a){ + return make_float2(a.x * b, + a.y * b); +} +VECATTR void operator *=(float2 &a, const float &b){ + a.x *= b; + a.y *= b; +} +VECATTR float2 operator /(const float2 &a, const float2 &b){ + return make_float2(a.x / b.x, + a.y / b.y); +} +VECATTR void operator /=(float2 &a, const float2 &b){ + a.x /= b.x; + a.y /= b.y; +} +VECATTR float2 operator /(const float2 &a, const float &b){ + return (1.0f/b)*a; +} +VECATTR float2 operator /(const float &b, const float2 &a){ + return make_float2(b / a.x, + b / a.y); +} +VECATTR void operator /=(float2 &a, const float &b){ + a *= 1.0f/b; +} + + + +/////////////////////FLOAT3/////////////////////////////// + +VECATTR int3 make_int3(float3 a){return make_int3((int)a.x, (int)a.y, (int)a.z);} +VECATTR float3 make_float3(float a){return make_float3(a, a, a);} + +VECATTR float3 make_float3(int3 a){return make_float3(a.x, a.y, a.z);} +VECATTR float3 make_float3(float3 a){return make_float3(a.x, a.y, a.z);} +VECATTR float3 make_float3(float4 a){return make_float3(a.x, a.y, a.z);} + +VECATTR float3 operator +(const float3 &a, const float3 &b){return make_float3( + a.x + b.x, + a.y + b.y, + a.z + b.z); +} +VECATTR void operator +=(float3 &a, const float3 &b){ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +VECATTR float3 operator +(const float3 &a, const float &b){return make_float3( + a.x + b, + a.y + b, + a.z + b); +} +VECATTR float3 operator +(const float &b, const float3 &a){return a+b;} +VECATTR void operator +=(float3 &a, const float &b){ + a.x += b; + a.y += b; + a.z += b; +} + +VECATTR float3 operator -(const float3 &a, const float3 &b){return make_float3( + a.x - b.x, + a.y - b.y, + a.z - b.z); +} + +VECATTR void operator -=(float3 &a, const float3 &b){ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} + +VECATTR float3 operator -(const float3 &a, const float &b){return make_float3( + a.x - b, + a.y - b, + a.z - b); +} + +VECATTR float3 operator -(const float &b, const float3 &a){return make_float3( + b-a.x, + b-a.y, + b-a.z); +} +VECATTR void operator -=(float3 &a, const float &b){ + a.x -= b; + a.y -= b; + a.z -= b; +} +VECATTR float3 operator *(const float3 &a, const float3 &b){ + return make_float3( + a.x * b.x, + a.y * b.y, + a.z * b.z + ); +} +VECATTR void operator *=(float3 &a, const float3 &b){ + a.x *= b.x; + a.y *= b.y; + a.z *= b.z; +} +VECATTR float3 operator *(const float3 &a, const float &b){ + return make_float3( + a.x * b, + a.y * b, + a.z * b + ); +} +VECATTR float3 operator *(const float &b, const float3 &a){ + return make_float3( + a.x * b, + a.y * b, + a.z * b + ); +} +VECATTR void operator *=(float3 &a, const float &b){ + a.x *= b; + a.y *= b; + a.z *= b; +} +VECATTR float3 operator /(const float3 &a, const float3 &b){ + return make_float3( + a.x / b.x, + a.y / b.y, + a.z / b.z + ); +} +VECATTR void operator /=(float3 &a, const float3 &b){ + a.x /= b.x; + a.y /= b.y; + a.z /= b.z; +} +VECATTR float3 operator /(const float3 &a, const float &b){ + return (1.0f/b)*a; +} +VECATTR float3 operator /(const float &b, const float3 &a){ + return make_float3( + b / a.x, + b / a.y, + b / a.z + ); +} +VECATTR void operator /=(float3 &a, const float &b){ + a *= 1.0f/b; +} + + +VECATTR float3 floorf(const float3 &a){return make_float3(floorf(a.x), floorf(a.y), floorf(a.z));} + +/////////////////////FLOAT4/////////////////////////////// + + +VECATTR float4 make_float4(float a){return make_float4(a,a,a,a);} + +VECATTR float4 make_float4(float3 a){return make_float4(a.x, a.y, a.z, 0);} +VECATTR float4 make_float4(float4 a){return make_float4(a.x, a.y, a.z, a.w);} + +VECATTR float4 operator +(const float4 &a, const float4 &b){return make_float4( + a.x + b.x, + a.y + b.y, + a.z + b.z, + a.w + b.w); +} +VECATTR void operator +=(float4 &a, const float4 &b){ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +VECATTR float4 operator +(const float4 &a, const float &b){return make_float4( + a.x + b, + a.y + b, + a.z + b, + a.w + b); +} +VECATTR float4 operator +(const float &b, const float4 &a){return a+b;} +VECATTR void operator +=(float4 &a, const float &b){ + a.x += b; + a.y += b; + a.z += b; + a.w += b; +} + +VECATTR float4 operator -(const float4 &a, const float4 &b){return make_float4( + a.x - b.x, + a.y - b.y, + a.z - b.z, + a.w - b.w); +} + +VECATTR void operator -=(float4 &a, const float4 &b){ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; + a.w -= b.w; +} + +VECATTR float4 operator -(const float4 &a, const float &b){return make_float4( + a.x - b, + a.y - b, + a.z - b, + a.w - b); +} + +VECATTR float4 operator -(const float &b, const float4 &a){return make_float4( + b-a.x, + b-a.y, + b-a.z, + b-a.w); +} +VECATTR void operator -=(float4 &a, const float &b){ + a.x -= b; + a.y -= b; + a.z -= b; + a.w -= b; +} +VECATTR float4 operator *(const float4 &a, const float4 &b){ + return make_float4(a.x * b.x, + a.y * b.y, + a.z * b.z, + a.w * b.w); +} +VECATTR void operator *=(float4 &a, const float4 &b){ + a.x *= b.x; + a.y *= b.y; + a.z *= b.z; + a.w *= b.w; +} +VECATTR float4 operator *(const float4 &a, const float &b){ + return make_float4(a.x * b, + a.y * b, + a.z * b, + a.w * b); +} +VECATTR float4 operator *(const float &b, const float4 &a){ + return make_float4(a.x * b, + a.y * b, + a.z * b, + a.w * b); +} +VECATTR void operator *=(float4 &a, const float &b){ + a.x *= b; + a.y *= b; + a.z *= b; + a.w *= b; +} +VECATTR float4 operator /(const float4 &a, const float4 &b){ + return make_float4(a.x / b.x, + a.y / b.y, + a.z / b.z, + a.w / b.w); +} +VECATTR void operator /=(float4 &a, const float4 &b){ + a.x /= b.x; + a.y /= b.y; + a.z /= b.z; + a.w /= b.w; +} +VECATTR float4 operator /(const float4 &a, const float &b){ + return (1.0f/b)*a; +} +VECATTR float4 operator /(const float &b, const float4 &a){ + return make_float4(b / a.x, + b / a.y, + b / a.z, + b / a.w); +} +VECATTR void operator /=(float4 &a, const float &b){ + a *= 1.0f/b; +} + + +VECATTR float4 floorf(const float4 &a){ + return make_float4(floorf(a.x), floorf(a.y), floorf(a.z), floorf(a.w)); +} + +VECATTR float dot(float4 a, float4 b){return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w;} + + + +namespace gpu{ +/////////////////REAL4//////////////////////////////// + +VECATTR real4 make_real4(real x, real y, real z, real w){ + #ifdef SINGLE_PRECISION + return make_float4(x,y,z,w); + #else + return make_double4(x,y,z,w); + #endif +} + +VECATTR real4 make_real4(real s){return make_real4(s, s, s, s);} + VECATTR real4 make_real4(real3 a){ return make_real4(a.x, a.y, a.z, real(0.0));} +VECATTR real4 make_real4(real3 a, real w){ return make_real4(a.x, a.y, a.z, w);} + + VECATTR real4 make_real4(real2 a){ return make_real4(a.x, a.y, real(0.0), real(0.0));} +#ifdef SINGLE_PRECISION +VECATTR real4 make_real4(double3 a, real w){return make_real4(a.x, a.y, a.z, w);} +#else +VECATTR real4 make_real4(float3 a, real w){ return make_real4(a.x, a.y, a.z, w);} +#endif + +VECATTR real4 make_real4(int4 a){ return make_real4(real(a.x), real(a.y), real(a.z), real(a.w));} +VECATTR real4 make_real4(uint4 a){return make_real4(real(a.x), real(a.y), real(a.z), real(a.w));} + + +//////////////////REAL3/////////////////////////// + + +VECATTR real3 make_real3(real x, real y, real z){ +#ifdef SINGLE_PRECISION + return make_float3(x,y,z); +#else + return make_double3(x,y,z); +#endif +} + +VECATTR real3 make_real3(real s){ return make_real3(s, s, s);} +VECATTR real3 make_real3(real3 a){return make_real3(a.x, a.y, a.z);} + +#ifdef SINGLE_PRECISION +VECATTR real3 make_real3(double3 a){return make_real3(a.x, a.y, a.z);} +VECATTR real3 make_real3(double4 a){return make_real3(a.x, a.y, a.z);} +#else + template + VECATTR real3 make_real3(float2 a, T b){return make_real3(a.x, a.y, b);} +VECATTR real3 make_real3(float3 a){return make_real3(a.x, a.y, a.z);} +VECATTR real3 make_real3(float4 a){return make_real3(a.x, a.y, a.z);} + +#endif +VECATTR real3 make_real3(real4 a){ return make_real3(a.x, a.y, a.z);} + +VECATTR real3 make_real3(real2 a, real z){return make_real3(a.x, a.y, z);} +VECATTR real3 make_real3(int3 a){ return make_real3(real(a.x), real(a.y), real(a.z));} +VECATTR real3 make_real3(uint3 a){return make_real3(real(a.x), real(a.y), real(a.z));} + + + +//////////////////REAL2/////////////////////////// + + +VECATTR real2 make_real2(real x, real y){ +#ifdef SINGLE_PRECISION + return make_float2(x,y); +#else + return make_double2(x,y); +#endif +} +#ifdef SINGLE_PRECISION + VECATTR real2 make_real2(double2 a){return make_real2(a.x, a.y);} +#else + VECATTR real2 make_real2(float2 a){return make_real2(a.x, a.y);} +#endif + +VECATTR real2 make_real2(real s){ return make_real2(s, s);} +VECATTR real2 make_real2(real2 a){return make_real2(a.x, a.y);} +VECATTR real2 make_real2(real3 a){return make_real2(a.x, a.y);} +VECATTR real2 make_real2(real4 a){return make_real2(a.x, a.y);} +VECATTR real2 make_real2(int3 a){ return make_real2(real(a.x), real(a.y));} +VECATTR real2 make_real2(uint3 a){return make_real2(real(a.x), real(a.y));} + + VECATTR real dot(real2 a, real2 b){ return a.x*b.x + a.y*b.y;} + + +} +////////////////DOUBLE PRECISION////////////////////// +#ifdef SINGLE_PRECISION +VECATTR double3 make_double3(gpu::real4 a){return make_double3(a.x, a.y, a.z);} +#else +VECATTR double3 make_double3(gpu::real3 a){return make_double3(a.x, a.y, a.z);} +VECATTR double3 make_double3(gpu::real4 a){return make_double3(a.x, a.y, a.z);} +#endif +VECATTR float4 make_float4(double4 a){return make_float4(float(a.x), float(a.y), float(a.z), float(a.w));} + +VECATTR double4 make_double4(double s){ return make_double4(s, s, s, s);} +VECATTR double4 make_double4(double3 a){return make_double4(a.x, a.y, a.z, 0.0f);} +VECATTR double4 make_double4(double3 a, double w){return make_double4(a.x, a.y, a.z, w);} +VECATTR double4 make_double4(int4 a){return make_double4(double(a.x), double(a.y), double(a.z), double(a.w));} +VECATTR double4 make_double4(uint4 a){return make_double4(double(a.x), double(a.y), double(a.z), double(a.w));} +VECATTR double4 make_double4(float4 a){return make_double4(double(a.x), double(a.y), double(a.z), double(a.w));} + +//////DOUBLE4/////////////// +VECATTR double4 operator +(const double4 &a, const double4 &b){ + return make_double4(a.x + b.x, + a.y + b.y, + a.z + b.z, + a.w + b.w + ); +} +VECATTR void operator +=(double4 &a, const double4 &b){ + a.x += b.x; + a.y += b.y; + a.z += b.z; + a.w += b.w; +} +VECATTR double4 operator +(const double4 &a, const double &b){ + return make_double4( + a.x + b, + a.y + b, + a.z + b, + a.w + b + ); +} +VECATTR double4 operator +(const double &b, const double4 &a){ + return a+b; +} +VECATTR void operator +=(double4 &a, const double &b){ + a.x += b; + a.y += b; + a.z += b; + a.w += b; +} + +VECATTR double4 operator -(const double4 &a, const double4 &b){ + return make_double4( + a.x - b.x, + a.y - b.y, + a.z - b.z, + a.w - b.w + ); +} +VECATTR void operator -=(double4 &a, const double4 &b){ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; + a.w -= b.w; +} +VECATTR double4 operator -(const double4 &a, const double &b){ + return make_double4( + a.x - b, + a.y - b, + a.z - b, + a.w - b + ); +} +VECATTR double4 operator -(const double &b, const double4 &a){ + return make_double4( + b - a.x, + b - a.y, + b - a.z, + b - a.w + ); +} +VECATTR void operator -=(double4 &a, const double &b){ + a.x -= b; + a.y -= b; + a.z -= b; + a.w -= b; +} +VECATTR double4 operator *(const double4 &a, const double4 &b){ + return make_double4( + a.x * b.x, + a.y * b.y, + a.z * b.z, + a.w * b.w + ); +} +VECATTR void operator *=(double4 &a, const double4 &b){ + a.x *= b.x; + a.y *= b.y; + a.z *= b.z; + a.w *= b.w; +} +VECATTR double4 operator *(const double4 &a, const double &b){ + return make_double4( + a.x * b, + a.y * b, + a.z * b, + a.w * b + ); +} +VECATTR double4 operator *(const double &b, const double4 &a){ + return a*b; +} +VECATTR void operator *=(double4 &a, const double &b){ + a.x *= b; + a.y *= b; + a.z *= b; + a.w *= b; +} +VECATTR double4 operator /(const double4 &a, const double4 &b){ + return make_double4( + a.x / b.x, + a.y / b.y, + a.z / b.z, + a.w / b.w + ); +} +VECATTR void operator /=(double4 &a, const double4 &b){ + a.x /= b.x; + a.y /= b.y; + a.z /= b.z; + a.w /= b.w; +} +VECATTR double4 operator /(const double4 &a, const double &b){return (1.0/b)*a;} +VECATTR double4 operator /(const double &b, const double4 &a){ + return make_double4( + b / a.x, + b / a.y, + b / a.z, + b / a.w + ); +} +VECATTR void operator /=(double4 &a, const double &b){ + a *= 1.0/b; +} + +VECATTR double dot(double4 a, double4 b) +{ + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; +} +VECATTR double length(double4 v) +{ + return sqrt(dot(v, v)); +} +VECATTR double4 normalize(double4 v) +{ + double invLen = 1.0/sqrt(dot(v, v)); + return v * invLen; +} +VECATTR double4 floorf(double4 v) +{ + return make_double4(floor(v.x), floor(v.y), floor(v.z), floor(v.w)); +} + +/////////////////////DOUBLE3/////////////////////////////// + +VECATTR int3 make_int3(double3 a){ + return make_int3((int)a.x, (int)a.y, (int)a.z); +} +VECATTR double3 make_double3(double a){ + return make_double3(a, a, a); +} + +VECATTR double3 make_double3(double2 xy, double z){ + return make_double3(xy.x, xy.y, z); +} + +VECATTR double3 make_double3(float2 xy, double z){ + return make_double3(xy.x, xy.y, z); +} +VECATTR double3 make_double3(double x, double2 yz){ + return make_double3(x, yz.x, yz.y); +} +#ifdef SINGLE_PRECISION +VECATTR double3 make_double3(double3 a){ + return a; +} +#endif + + +VECATTR double3 make_double3(int3 a){ + return make_double3(a.x, a.y, a.z); +} +VECATTR double3 make_double3(float3 a){ + return make_double3(a.x, a.y, a.z); +} + +VECATTR double3 operator +(const double3 &a, const double3 &b){ + return make_double3( + a.x + b.x, + a.y + b.y, + a.z + b.z + ); +} +VECATTR void operator +=(double3 &a, const double3 &b){ + a.x += b.x; + a.y += b.y; + a.z += b.z; +} +VECATTR double3 operator +(const double3 &a, const double &b){ + return make_double3( + a.x + b, + a.y + b, + a.z + b + ); +} +VECATTR double3 operator +(const double &b, const double3 &a){ + return a+b; +} +VECATTR void operator +=(double3 &a, const double &b){ + a.x += b; + a.y += b; + a.z += b; +} + +VECATTR double3 operator -(const double3 &a, const double3 &b){ + return make_double3( + a.x - b.x, + a.y - b.y, + a.z - b.z + ); +} +VECATTR void operator -=(double3 &a, const double3 &b){ + a.x -= b.x; + a.y -= b.y; + a.z -= b.z; +} +VECATTR double3 operator -(const double3 &a, const double &b){ + return make_double3( + a.x - b, + a.y - b, + a.z - b + ); +} +VECATTR double3 operator -(const double &b, const double3 &a){ + return make_double3( + b-a.x, + b-a.y, + b-a.z + ); +} +VECATTR void operator -=(double3 &a, const double &b){ + a.x -= b; + a.y -= b; + a.z -= b; +} +VECATTR double3 operator *(const double3 &a, const double3 &b){ + return make_double3( + a.x * b.x, + a.y * b.y, + a.z * b.z + ); +} +VECATTR void operator *=(double3 &a, const double3 &b){ + a.x *= b.x; + a.y *= b.y; + a.z *= b.z; +} +VECATTR double3 operator *(const double3 &a, const double &b){ + return make_double3( + a.x * b, + a.y * b, + a.z * b + ); +} +VECATTR double3 operator *(const double &b, const double3 &a){ + return a*b; +} +VECATTR void operator *=(double3 &a, const double &b){ + a.x *= b; + a.y *= b; + a.z *= b; +} +VECATTR double3 operator /(const double3 &a, const double3 &b){ + return make_double3( + a.x / b.x, + a.y / b.y, + a.z / b.z + ); +} +VECATTR void operator /=(double3 &a, const double3 &b){ + a.x /= b.x; + a.y /= b.y; + a.z /= b.z; +} +VECATTR double3 operator /(const double3 &a, const double &b){return (1.0/b)*a;} + +VECATTR double3 operator /(const double &b, const double3 &a){ + return make_double3( + b / a.x, + b / a.y, + b / a.z + ); +} +VECATTR void operator /=(double3 &a, const double &b){ + + a *= 1.0/b; + +} + +//DOUBLE2 + + +VECATTR double2 operator -(const double2 &a, const double2 &b){ + return make_double2( + a.x - b.x, + a.y - b.y + ); +} +VECATTR void operator -=(double2 &a, const double2 &b){ + a.x -= b.x; + a.y -= b.y; +} +VECATTR double2 operator -(const double2 &a, const double &b){ + return make_double2( + a.x - b, + a.y - b + ); +} +VECATTR double2 operator -(const double &b, const double2 &a){ + return make_double2( + b - a.x, + b - a.y + ); +} +VECATTR void operator -=(double2 &a, const double &b){a.x -= b; a.y -= b;} + + + + +VECATTR double2 operator +(const double2 &a, const double2 &b){ + return make_double2( + a.x + b.x, + a.y + b.y + ); +} +VECATTR void operator +=(double2 &a, const double2 &b){ + a.x += b.x; + a.y += b.y; +} +VECATTR double2 operator +(const double2 &a, const double &b){ + return make_double2( + a.x + b, + a.y + b + ); +} +VECATTR double2 operator +(const double &b, const double2 &a){ return a+b;} +VECATTR void operator +=(double2 &a, const double &b){a.x += b; a.y += b;} + + +VECATTR double2 operator *(const double2 &a, const double2 &b){ + return make_double2(a.x * b.x, a.y * b.y); +} +VECATTR void operator *=(double2 &a, const double2 &b){ + a.x *= b.x; + a.y *= b.y; +} +VECATTR double2 operator *(const double2 &a, const double &b){ + return make_double2(a.x * b, a.y * b); +} +VECATTR double2 operator *(const double &b, const double2 &a){ + return a*b; +} +VECATTR void operator *=(double2 &a, const double &b){ + a.x *= b; + a.y *= b; +} + + + +VECATTR double2 operator /(const double2 &a, const double2 &b){ + return make_double2(a.x / b.x, a.y / b.y); +} +VECATTR void operator /=(double2 &a, const double2 &b){ + a.x /= b.x; + a.y /= b.y; +} +VECATTR double2 operator /(const double2 &a, const double &b){ + return make_double2(a.x / b, a.y / b); +} +VECATTR double2 operator /(const double &b, const double2 &a){ + return make_double2(b/a.x, b/a.y); +} +VECATTR void operator /=(double2 &a, const double &b){ + a.x /= b; + a.y /= b; +} + + + +//////////////////////////// + +VECATTR double3 floorf(double3 v){return make_double3(floor(v.x), floor(v.y), floor(v.z));} + + + +VECATTR double dot(const double3 &a, const double3 &b){return a.x * b.x + a.y * b.y + a.z * b.z;} +VECATTR float dot(const float3 &a, const float3 &b){return a.x * b.x + a.y * b.y + a.z * b.z;} +VECATTR int dot(const int3 &a, const int3 &b){return a.x * b.x + a.y * b.y + a.z * b.z;} +VECATTR int dot(const int2 &a, const int2 &b){return a.x * b.x + a.y * b.y;} + + +VECATTR double length(double3 v){return sqrt(dot(v, v));} +VECATTR double3 normalize(double3 v) +{ + double invLen = 1.0/sqrt(dot(v, v)); + return v * invLen; +} + +VECATTR double3 cross(double3 a, double3 b){ + return make_double3(a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x); +} + + +VECATTR float3 sqrt(const float3 &a){ return {sqrt(a.x), sqrt(a.y), sqrt(a.z)};} +VECATTR double3 sqrt(const double3 &a){ return {sqrt(a.x), sqrt(a.y), sqrt(a.z)};} + + +////////////////////////////////////////////////////////// + + +/****************************************************************************************/ + + + +///////////INT2///////////////// +VECATTR int2 make_int2(int3 a){return make_int2(a.x, a.y);} + +///////////INT3///////////////// + +VECATTR int3 make_int3(int a){return make_int3(a,a,a);} +VECATTR int3 make_int3(int2 a, int b){return make_int3(a.x,a.y,b);} + +VECATTR int3 operator /(int3 a, int3 b){ + return make_int3( a.x/b.x, a.y/b.y, a.z/b.z); +} + +VECATTR int3 operator /(int3 a, int b){ + return make_int3( a.x/b, a.y/b, a.z/b); +} + +VECATTR int3 operator /(int a, int3 b){ + return make_int3( a/b.x, a/b.y, a/b.z); +} + +VECATTR int3 operator +(const int3 &a, const int3 &b){ + return make_int3(a.x + b.x, a.y + b.y, a.z + b.z); +} + +VECATTR int3 operator +(const int3 &a, const int &b){ + return make_int3(a.x + b, a.y + b, a.z + b); +} + +VECATTR int3 operator -(const int3 &a, const int3 &b){ + return make_int3(a.x - b.x, a.y - b.y, a.z - b.z); +} + +VECATTR void operator -=(int3 &a, const int3 &b){ + a.x -= b.x; a.y -= b.y; a.z -= b.z; +} + +VECATTR void operator +=(int3 &a, const int3 &b){ + a.x += b.x; a.y += b.y; a.z += b.z; +} + +VECATTR void operator /=(int3 &a, const int &b){ + a.x /= b; a.y /= b; a.z /= b; +} + +VECATTR int3 operator -(const int3 &a, const int &b){ + return make_int3(a.x - b, a.y - b, a.z - b); +} + +VECATTR int3 operator -(const int &b, const int3 &a){ + return make_int3(b - a.x, b - a.y, b - a.z); +} + +VECATTR int3 operator *(const int3 &a, const int &b){return make_int3(a.x*b, a.y*b, a.z*b);} +VECATTR int3 operator *(const int &b, const int3 &a){return a*b;} + +#endif From 5e9da55cbd49525004817f51abf376aa7398107e Mon Sep 17 00:00:00 2001 From: Xiangyu Hu Date: Fri, 30 Jul 2021 16:58:59 +0200 Subject: [PATCH 03/40] replace the wrong gpuSPHinXsys --- SPHINXsys/CMakeLists.txt | 13 + SPHINXsys/cmake/Dirsearch_for_2D_build.cmake | 22 + SPHINXsys/cmake/Dirsearch_for_3D_build.cmake | 23 + .../cmake/Dirsearch_for_image_process.cmake | 22 + SPHINXsys/cmake/Dirsearch_shared.cmake | 50 + SPHINXsys/cmake/Headersearch.cmake | 8 + .../cmake/Simbody_header_directories.cmake | 15 + SPHINXsys/cmake/oneTBB_header_directory.cmake | 5 + SPHINXsys/logo-16x16.ico | Bin 0 -> 632 bytes SPHINXsys/logo-32x32.ico | Bin 0 -> 1603 bytes SPHINXsys/logo-white.png | Bin 0 -> 5113 bytes SPHINXsys/logo-with-name.png | Bin 0 -> 9614 bytes SPHINXsys/logo-with-white.png | Bin 0 -> 7305 bytes SPHINXsys/logo.png | Bin 0 -> 9484 bytes SPHINXsys/mainpage.md | 100 ++ SPHINXsys/src/CMakeLists.txt | 23 + SPHINXsys/src/Readme.txt | 5 + SPHINXsys/src/for_2D_build/CMakeLists.txt | 81 + .../src/for_2D_build/bodies/CMakeLists.txt | 7 + .../bodies/solid_body_supplementary.cpp | 55 + .../src/for_2D_build/common/CMakeLists.txt | 7 + SPHINXsys/src/for_2D_build/common/data_type.h | 39 + .../common/scalar_functions_supplementary.cpp | 14 + .../for_2D_build/geometries/CMakeLists.txt | 7 + .../src/for_2D_build/geometries/geometry.cpp | 313 ++++ .../src/for_2D_build/geometries/geometry.h | 108 ++ .../geometries/level_set_supplementary.cpp | 440 ++++++ .../src/for_2D_build/meshes/CMakeLists.txt | 7 + .../meshes/base_mesh_supplementary.cpp | 49 + .../for_2D_build/meshes/cell_linked_list.hpp | 47 + .../meshes/cell_linked_list_supplementary.cpp | 267 ++++ .../meshes/mesh_with_data_packages.hpp | 168 ++ .../particle_dynamics/CMakeLists.txt | 7 + .../solid_dynamics/CMakeLists.txt | 8 + .../solid_dynamics_supplementary.cpp | 75 + .../particle_generator/CMakeLists.txt | 7 + .../all_particle_generators.h | 8 + ...rticle_generator_lattice_supplementary.cpp | 34 + .../src/for_2D_build/particles/CMakeLists.txt | 7 + .../solid_particles_supplementary.cpp | 35 + SPHINXsys/src/for_3D_build/CMakeLists.txt | 116 ++ .../src/for_3D_build/bodies/CMakeLists.txt | 7 + .../bodies/solid_body_supplementary.cpp | 59 + .../src/for_3D_build/common/CMakeLists.txt | 7 + SPHINXsys/src/for_3D_build/common/data_type.h | 39 + .../common/scalar_functions_supplementary.cpp | 8 + .../for_3D_build/geometries/CMakeLists.txt | 7 + .../src/for_3D_build/geometries/geometry.cpp | 334 ++++ .../src/for_3D_build/geometries/geometry.h | 115 ++ .../geometries/level_set_supplementary.cpp | 476 ++++++ .../CMakeLists.txt | 7 + .../solid_structural_simulation_class.cpp | 722 +++++++++ .../solid_structural_simulation_class.h | 215 +++ .../test_structural_simulation_class.h | 52 + .../src/for_3D_build/meshes/CMakeLists.txt | 7 + .../meshes/base_mesh_supplementary.cpp | 56 + .../for_3D_build/meshes/cell_linked_list.hpp | 51 + .../meshes/cell_linked_list_supplementary.cpp | 326 ++++ .../meshes/mesh_with_data_packages.hpp | 185 +++ .../particle_dynamics/CMakeLists.txt | 7 + .../solid_dynamics/CMakeLists.txt | 8 + .../solid_dynamics/polar_decomposition_3x3.h | 68 + .../polar_decomposition_3x3_impl.h | 1373 +++++++++++++++++ .../polar_decomposition_3x3_matrix.h | 359 +++++ .../solid_dynamics_supplementary.cpp | 69 + .../particle_generator/CMakeLists.txt | 7 + .../all_particle_generators.h | 9 + ...rticle_generator_lattice_supplementary.cpp | 31 + .../particle_generator_network.cpp | 232 +++ .../particle_generator_network.h | 116 ++ .../src/for_3D_build/particles/CMakeLists.txt | 7 + .../solid_particles_supplementary.cpp | 34 + SPHINXsys/src/shared/CMakeLists.txt | 8 + SPHINXsys/src/shared/bodies/CMakeLists.txt | 8 + SPHINXsys/src/shared/bodies/all_bodies.h | 14 + SPHINXsys/src/shared/bodies/base_body.cpp | 276 ++++ SPHINXsys/src/shared/bodies/base_body.h | 314 ++++ SPHINXsys/src/shared/bodies/body_relation.cpp | 272 ++++ SPHINXsys/src/shared/bodies/body_relation.h | 311 ++++ SPHINXsys/src/shared/bodies/fluid_body.cpp | 28 + SPHINXsys/src/shared/bodies/fluid_body.h | 73 + SPHINXsys/src/shared/bodies/solid_body.cpp | 39 + SPHINXsys/src/shared/bodies/solid_body.h | 91 ++ SPHINXsys/src/shared/common/CMakeLists.txt | 8 + .../src/shared/common/array_allocation.h | 91 ++ .../src/shared/common/base_data_package.h | 34 + SPHINXsys/src/shared/common/base_data_type.h | 363 +++++ .../src/shared/common/large_data_containers.h | 58 + .../src/shared/common/scalar_functions.cpp | 15 + .../src/shared/common/scalar_functions.h | 184 +++ SPHINXsys/src/shared/common/small_vectors.cpp | 217 +++ SPHINXsys/src/shared/common/small_vectors.h | 79 + .../src/shared/common/sph_data_conainers.h | 115 ++ .../generative_structures.cpp | 267 ++++ .../generative_structures.h | 119 ++ .../src/shared/geometries/CMakeLists.txt | 8 + .../src/shared/geometries/all_geometries.h | 12 + .../src/shared/geometries/base_geometry.h | 96 ++ .../shared/geometries/geometry_level_set.cpp | 69 + .../shared/geometries/geometry_level_set.h | 43 + SPHINXsys/src/shared/geometries/level_set.cpp | 273 ++++ SPHINXsys/src/shared/geometries/level_set.h | 179 +++ SPHINXsys/src/shared/include/CMakeLists.txt | 8 + SPHINXsys/src/shared/include/sphinxsys.h | 44 + SPHINXsys/src/shared/io_system/CMakeLists.txt | 8 + SPHINXsys/src/shared/io_system/in_output.cpp | 377 +++++ SPHINXsys/src/shared/io_system/in_output.h | 654 ++++++++ .../src/shared/io_system/parameterization.cpp | 23 + .../src/shared/io_system/parameterization.h | 96 ++ SPHINXsys/src/shared/kernels/CMakeLists.txt | 8 + SPHINXsys/src/shared/kernels/all_kernels.h | 10 + SPHINXsys/src/shared/kernels/base_kernel.cpp | 180 +++ SPHINXsys/src/shared/kernels/base_kernel.h | 176 +++ .../src/shared/kernels/kernel_hyperbolic.cpp | 84 + .../src/shared/kernels/kernel_hyperbolic.h | 67 + .../src/shared/kernels/kernel_quadratic.cpp | 84 + .../src/shared/kernels/kernel_quadratic.h | 67 + .../src/shared/kernels/kernel_tabulated.hpp | 152 ++ .../src/shared/kernels/kernel_wenland_c2.cpp | 67 + .../src/shared/kernels/kernel_wenland_c2.h | 67 + SPHINXsys/src/shared/materials/CMakeLists.txt | 8 + .../src/shared/materials/all_materials.h | 16 + .../src/shared/materials/base_material.h | 188 +++ .../src/shared/materials/complex_solid.h | 56 + .../src/shared/materials/complex_solid.hpp | 41 + .../shared/materials/compressible_fluid.cpp | 22 + .../src/shared/materials/compressible_fluid.h | 70 + .../shared/materials/diffusion_reaction.cpp | 149 ++ .../src/shared/materials/diffusion_reaction.h | 338 ++++ .../src/shared/materials/elastic_solid.cpp | 269 ++++ .../src/shared/materials/elastic_solid.h | 271 ++++ .../src/shared/materials/inelastic_solid.cpp | 49 + .../src/shared/materials/inelastic_solid.h | 92 ++ .../src/shared/materials/riemann_solver.cpp | 233 +++ .../src/shared/materials/riemann_solver.h | 108 ++ .../materials/weakly_compressible_fluid.cpp | 48 + .../materials/weakly_compressible_fluid.h | 158 ++ SPHINXsys/src/shared/meshes/CMakeLists.txt | 8 + SPHINXsys/src/shared/meshes/base_mesh.cpp | 99 ++ SPHINXsys/src/shared/meshes/base_mesh.h | 193 +++ .../src/shared/meshes/cell_linked_list.cpp | 151 ++ .../src/shared/meshes/cell_linked_list.h | 187 +++ .../shared/meshes/mesh_with_data_packages.h | 255 +++ SPHINXsys/src/shared/meshes/my_memory_pool.h | 62 + .../shared/particle_dynamics/CMakeLists.txt | 8 + .../active_muscle_dynamics/CMakeLists.txt | 8 + .../active_muscle_dynamics.cpp | 57 + .../active_muscle_dynamics.h | 106 ++ .../particle_dynamics/all_particle_dynamics.h | 9 + .../particle_dynamics/all_physical_dynamics.h | 25 + .../base_particle_dynamics.cpp | 99 ++ .../base_particle_dynamics.h | 272 ++++ .../base_particle_dynamics.hpp | 95 ++ .../CMakeLists.txt | 8 + .../particle_dynamics_diffusion_reaction.h | 297 ++++ .../particle_dynamics_diffusion_reaction.hpp | 314 ++++ .../dissipation_dynamics/CMakeLists.txt | 8 + .../particle_dynamics_dissipation.h | 201 +++ .../particle_dynamics_dissipation.hpp | 418 +++++ .../electro_physiology/CMakeLists.txt | 8 + .../electro_physiology/electro_physiology.cpp | 24 + .../electro_physiology/electro_physiology.h | 149 ++ .../external_force/CMakeLists.txt | 8 + .../external_force/external_force.cpp | 26 + .../external_force/external_force.h | 69 + .../fluid_dynamics/CMakeLists.txt | 8 + .../fluid_dynamics/all_fluid_dynamics.h | 42 + .../eulerian_fluid_dynamics/CMakeLists.txt | 8 + .../all_eulerian_fluid_dynamics.h | 34 + .../eulerian_fluid_dynamics_complex.cpp | 20 + .../eulerian_fluid_dynamics_complex.h | 184 +++ .../eulerian_fluid_dynamics_complex.hpp | 232 +++ .../eulerian_fluid_dynamics_inner.cpp | 142 ++ .../eulerian_fluid_dynamics_inner.h | 188 +++ .../eulerian_fluid_dynamics_inner.hpp | 79 + .../fluid_dynamics/fluid_dynamics_complex.cpp | 299 ++++ .../fluid_dynamics/fluid_dynamics_complex.h | 365 +++++ .../fluid_dynamics/fluid_dynamics_complex.hpp | 368 +++++ .../fluid_dynamics_compound.cpp | 18 + .../fluid_dynamics/fluid_dynamics_compound.h | 103 ++ .../fluid_dynamics_compound.hpp | 21 + .../fluid_dynamics/fluid_dynamics_inner.cpp | 768 +++++++++ .../fluid_dynamics/fluid_dynamics_inner.h | 747 +++++++++ .../fluid_dynamics/fluid_dynamics_inner.hpp | 70 + .../fluid_dynamics_multi_phase.cpp | 111 ++ .../fluid_dynamics_multi_phase.h | 153 ++ .../fluid_dynamics_multi_phase.hpp | 167 ++ .../general_dynamics/CMakeLists.txt | 8 + .../general_dynamics/general_dynamics.cpp | 535 +++++++ .../general_dynamics/general_dynamics.h | 547 +++++++ .../observer_dynamics/CMakeLists.txt | 8 + .../observer_dynamics/observer_dynamics.cpp | 69 + .../observer_dynamics/observer_dynamics.h | 123 ++ .../particle_dynamics_algorithms.cpp | 148 ++ .../particle_dynamics_algorithms.h | 213 +++ .../particle_dynamics_bodypart.cpp | 161 ++ .../particle_dynamics_bodypart.h | 264 ++++ .../relax_dynamics/CMakeLists.txt | 8 + .../relax_dynamics/relax_dynamics.cpp | 239 +++ .../relax_dynamics/relax_dynamics.h | 260 ++++ .../solid_dynamics/CMakeLists.txt | 8 + .../solid_dynamics/all_solid_dynamics.h | 10 + .../fluid_structure_interaction.cpp | 150 ++ .../fluid_structure_interaction.h | 234 +++ .../solid_dynamics/inelastic_dynamics.cpp | 33 + .../solid_dynamics/inelastic_dynamics.h | 56 + .../solid_dynamics/solid_dynamics.cpp | 820 ++++++++++ .../solid_dynamics/solid_dynamics.h | 618 ++++++++ .../thin_structure_dynamics.cpp | 521 +++++++ .../solid_dynamics/thin_structure_dynamics.h | 302 ++++ .../solid_dynamics/thin_structure_math.cpp | 148 ++ .../solid_dynamics/thin_structure_math.h | 69 + .../shared/particle_generator/CMakeLists.txt | 8 + .../base_particle_generator.cpp | 60 + .../base_particle_generator.h | 86 ++ .../particle_generator_lattice.cpp | 59 + .../particle_generator_lattice.h | 82 + SPHINXsys/src/shared/particles/CMakeLists.txt | 8 + .../src/shared/particles/all_particles.h | 11 + .../src/shared/particles/base_particles.cpp | 339 ++++ .../src/shared/particles/base_particles.h | 351 +++++ .../diffusion_reaction_particles.cpp | 24 + .../particles/diffusion_reaction_particles.h | 133 ++ .../src/shared/particles/fluid_particles.cpp | 77 + .../src/shared/particles/fluid_particles.h | 96 ++ .../shared/particles/neighbor_relation.cpp | 175 +++ .../src/shared/particles/neighbor_relation.h | 162 ++ .../shared/particles/particle_adaptation.cpp | 181 +++ .../shared/particles/particle_adaptation.h | 144 ++ .../src/shared/particles/particle_sorting.cpp | 63 + .../src/shared/particles/particle_sorting.h | 265 ++++ .../src/shared/particles/solid_particles.cpp | 173 +++ .../src/shared/particles/solid_particles.h | 173 +++ .../shared/regression_testing/CMakeLists.txt | 8 + .../meanvalue_variance_method.h | 203 +++ .../meanvalue_variance_method.hpp | 663 ++++++++ .../regression_testing/regression_testing.h | 33 + .../shared/simbody_sphinxsys/CMakeLists.txt | 8 + .../shared/simbody_sphinxsys/all_simbody.h | 14 + .../src/shared/simbody_sphinxsys/array.h | 648 ++++++++ .../shared/simbody_sphinxsys/exception.cpp | 84 + .../src/shared/simbody_sphinxsys/exception.h | 142 ++ .../shared/simbody_sphinxsys/simbody_middle.h | 23 + .../shared/simbody_sphinxsys/state_engine.cpp | 363 +++++ .../shared/simbody_sphinxsys/state_engine.h | 380 +++++ .../shared/simbody_sphinxsys/xml_engine.cpp | 116 ++ .../src/shared/simbody_sphinxsys/xml_engine.h | 114 ++ .../shared/sphinxsys_system/CMakeLists.txt | 8 + .../shared/sphinxsys_system/sph_system.cpp | 139 ++ .../src/shared/sphinxsys_system/sph_system.h | 80 + SPHINXsys/src/shared/tbb/CMakeLists.txt | 8 + gpuSPHINXsys/gpuSPHinxsys.cu | 198 --- gpuSPHINXsys/gpuSPHinxsys.cuh | 45 - gpuSPHINXsys/src/Box.cuh | 67 - gpuSPHINXsys/src/CellList.cuh | 568 ------- gpuSPHINXsys/src/GPUUtils.cuh | 38 - gpuSPHINXsys/src/Grid.cuh | 167 -- gpuSPHINXsys/src/Kernel.cuh | 121 -- gpuSPHINXsys/src/Log.h | 63 - gpuSPHINXsys/src/ParticleData.cuh | 318 ---- gpuSPHINXsys/src/ParticleGroup.cuh | 471 ------ gpuSPHINXsys/src/ParticleSorter.cuh | 297 ---- gpuSPHINXsys/src/Property.cuh | 278 ---- gpuSPHINXsys/src/System.h | 89 -- gpuSPHINXsys/src/allocator.h | 211 --- gpuSPHINXsys/src/debugTools.cuh | 44 - gpuSPHINXsys/src/defines.h | 36 - gpuSPHINXsys/src/dtSizeCalc.cu | 154 -- gpuSPHINXsys/src/dtSizeCalc.cuh | 57 - gpuSPHINXsys/src/fluidDynamics.cu | 686 -------- gpuSPHINXsys/src/fluidDynamics.cuh | 64 - gpuSPHINXsys/src/inOut.cu | 294 ---- gpuSPHINXsys/src/inOut.cuh | 61 - gpuSPHINXsys/src/include/nod/README.md | 257 --- gpuSPHINXsys/src/include/nod/nod.hpp | 631 -------- gpuSPHINXsys/src/timeStepping.cu | 259 ---- gpuSPHINXsys/src/timeStepping.cuh | 74 - gpuSPHINXsys/src/vector.cuh | 926 ----------- 278 files changed, 33684 insertions(+), 6474 deletions(-) create mode 100644 SPHINXsys/CMakeLists.txt create mode 100644 SPHINXsys/cmake/Dirsearch_for_2D_build.cmake create mode 100644 SPHINXsys/cmake/Dirsearch_for_3D_build.cmake create mode 100644 SPHINXsys/cmake/Dirsearch_for_image_process.cmake create mode 100644 SPHINXsys/cmake/Dirsearch_shared.cmake create mode 100644 SPHINXsys/cmake/Headersearch.cmake create mode 100644 SPHINXsys/cmake/Simbody_header_directories.cmake create mode 100644 SPHINXsys/cmake/oneTBB_header_directory.cmake create mode 100644 SPHINXsys/logo-16x16.ico create mode 100644 SPHINXsys/logo-32x32.ico create mode 100644 SPHINXsys/logo-white.png create mode 100644 SPHINXsys/logo-with-name.png create mode 100644 SPHINXsys/logo-with-white.png create mode 100644 SPHINXsys/logo.png create mode 100644 SPHINXsys/mainpage.md create mode 100644 SPHINXsys/src/CMakeLists.txt create mode 100644 SPHINXsys/src/Readme.txt create mode 100644 SPHINXsys/src/for_2D_build/CMakeLists.txt create mode 100644 SPHINXsys/src/for_2D_build/bodies/CMakeLists.txt create mode 100644 SPHINXsys/src/for_2D_build/bodies/solid_body_supplementary.cpp create mode 100644 SPHINXsys/src/for_2D_build/common/CMakeLists.txt create mode 100644 SPHINXsys/src/for_2D_build/common/data_type.h create mode 100644 SPHINXsys/src/for_2D_build/common/scalar_functions_supplementary.cpp create mode 100644 SPHINXsys/src/for_2D_build/geometries/CMakeLists.txt create mode 100644 SPHINXsys/src/for_2D_build/geometries/geometry.cpp create mode 100644 SPHINXsys/src/for_2D_build/geometries/geometry.h create mode 100644 SPHINXsys/src/for_2D_build/geometries/level_set_supplementary.cpp create mode 100644 SPHINXsys/src/for_2D_build/meshes/CMakeLists.txt create mode 100644 SPHINXsys/src/for_2D_build/meshes/base_mesh_supplementary.cpp create mode 100644 SPHINXsys/src/for_2D_build/meshes/cell_linked_list.hpp create mode 100644 SPHINXsys/src/for_2D_build/meshes/cell_linked_list_supplementary.cpp create mode 100644 SPHINXsys/src/for_2D_build/meshes/mesh_with_data_packages.hpp create mode 100644 SPHINXsys/src/for_2D_build/particle_dynamics/CMakeLists.txt create mode 100644 SPHINXsys/src/for_2D_build/particle_dynamics/solid_dynamics/CMakeLists.txt create mode 100644 SPHINXsys/src/for_2D_build/particle_dynamics/solid_dynamics/solid_dynamics_supplementary.cpp create mode 100644 SPHINXsys/src/for_2D_build/particle_generator/CMakeLists.txt create mode 100644 SPHINXsys/src/for_2D_build/particle_generator/all_particle_generators.h create mode 100644 SPHINXsys/src/for_2D_build/particle_generator/particle_generator_lattice_supplementary.cpp create mode 100644 SPHINXsys/src/for_2D_build/particles/CMakeLists.txt create mode 100644 SPHINXsys/src/for_2D_build/particles/solid_particles_supplementary.cpp create mode 100644 SPHINXsys/src/for_3D_build/CMakeLists.txt create mode 100644 SPHINXsys/src/for_3D_build/bodies/CMakeLists.txt create mode 100644 SPHINXsys/src/for_3D_build/bodies/solid_body_supplementary.cpp create mode 100644 SPHINXsys/src/for_3D_build/common/CMakeLists.txt create mode 100644 SPHINXsys/src/for_3D_build/common/data_type.h create mode 100644 SPHINXsys/src/for_3D_build/common/scalar_functions_supplementary.cpp create mode 100644 SPHINXsys/src/for_3D_build/geometries/CMakeLists.txt create mode 100644 SPHINXsys/src/for_3D_build/geometries/geometry.cpp create mode 100644 SPHINXsys/src/for_3D_build/geometries/geometry.h create mode 100644 SPHINXsys/src/for_3D_build/geometries/level_set_supplementary.cpp create mode 100644 SPHINXsys/src/for_3D_build/high_level_simulation_class/CMakeLists.txt create mode 100644 SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp create mode 100644 SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.h create mode 100644 SPHINXsys/src/for_3D_build/high_level_simulation_class/test_structural_simulation_class.h create mode 100644 SPHINXsys/src/for_3D_build/meshes/CMakeLists.txt create mode 100644 SPHINXsys/src/for_3D_build/meshes/base_mesh_supplementary.cpp create mode 100644 SPHINXsys/src/for_3D_build/meshes/cell_linked_list.hpp create mode 100644 SPHINXsys/src/for_3D_build/meshes/cell_linked_list_supplementary.cpp create mode 100644 SPHINXsys/src/for_3D_build/meshes/mesh_with_data_packages.hpp create mode 100644 SPHINXsys/src/for_3D_build/particle_dynamics/CMakeLists.txt create mode 100644 SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/CMakeLists.txt create mode 100644 SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3.h create mode 100644 SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3_impl.h create mode 100644 SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3_matrix.h create mode 100644 SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/solid_dynamics_supplementary.cpp create mode 100644 SPHINXsys/src/for_3D_build/particle_generator/CMakeLists.txt create mode 100644 SPHINXsys/src/for_3D_build/particle_generator/all_particle_generators.h create mode 100644 SPHINXsys/src/for_3D_build/particle_generator/particle_generator_lattice_supplementary.cpp create mode 100644 SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.cpp create mode 100644 SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.h create mode 100644 SPHINXsys/src/for_3D_build/particles/CMakeLists.txt create mode 100644 SPHINXsys/src/for_3D_build/particles/solid_particles_supplementary.cpp create mode 100644 SPHINXsys/src/shared/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/bodies/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/bodies/all_bodies.h create mode 100644 SPHINXsys/src/shared/bodies/base_body.cpp create mode 100644 SPHINXsys/src/shared/bodies/base_body.h create mode 100644 SPHINXsys/src/shared/bodies/body_relation.cpp create mode 100644 SPHINXsys/src/shared/bodies/body_relation.h create mode 100644 SPHINXsys/src/shared/bodies/fluid_body.cpp create mode 100644 SPHINXsys/src/shared/bodies/fluid_body.h create mode 100644 SPHINXsys/src/shared/bodies/solid_body.cpp create mode 100644 SPHINXsys/src/shared/bodies/solid_body.h create mode 100644 SPHINXsys/src/shared/common/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/common/array_allocation.h create mode 100644 SPHINXsys/src/shared/common/base_data_package.h create mode 100644 SPHINXsys/src/shared/common/base_data_type.h create mode 100644 SPHINXsys/src/shared/common/large_data_containers.h create mode 100644 SPHINXsys/src/shared/common/scalar_functions.cpp create mode 100644 SPHINXsys/src/shared/common/scalar_functions.h create mode 100644 SPHINXsys/src/shared/common/small_vectors.cpp create mode 100644 SPHINXsys/src/shared/common/small_vectors.h create mode 100644 SPHINXsys/src/shared/common/sph_data_conainers.h create mode 100644 SPHINXsys/src/shared/generative_structures/generative_structures.cpp create mode 100644 SPHINXsys/src/shared/generative_structures/generative_structures.h create mode 100644 SPHINXsys/src/shared/geometries/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/geometries/all_geometries.h create mode 100644 SPHINXsys/src/shared/geometries/base_geometry.h create mode 100644 SPHINXsys/src/shared/geometries/geometry_level_set.cpp create mode 100644 SPHINXsys/src/shared/geometries/geometry_level_set.h create mode 100644 SPHINXsys/src/shared/geometries/level_set.cpp create mode 100644 SPHINXsys/src/shared/geometries/level_set.h create mode 100644 SPHINXsys/src/shared/include/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/include/sphinxsys.h create mode 100644 SPHINXsys/src/shared/io_system/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/io_system/in_output.cpp create mode 100644 SPHINXsys/src/shared/io_system/in_output.h create mode 100644 SPHINXsys/src/shared/io_system/parameterization.cpp create mode 100644 SPHINXsys/src/shared/io_system/parameterization.h create mode 100644 SPHINXsys/src/shared/kernels/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/kernels/all_kernels.h create mode 100644 SPHINXsys/src/shared/kernels/base_kernel.cpp create mode 100644 SPHINXsys/src/shared/kernels/base_kernel.h create mode 100644 SPHINXsys/src/shared/kernels/kernel_hyperbolic.cpp create mode 100644 SPHINXsys/src/shared/kernels/kernel_hyperbolic.h create mode 100644 SPHINXsys/src/shared/kernels/kernel_quadratic.cpp create mode 100644 SPHINXsys/src/shared/kernels/kernel_quadratic.h create mode 100644 SPHINXsys/src/shared/kernels/kernel_tabulated.hpp create mode 100644 SPHINXsys/src/shared/kernels/kernel_wenland_c2.cpp create mode 100644 SPHINXsys/src/shared/kernels/kernel_wenland_c2.h create mode 100644 SPHINXsys/src/shared/materials/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/materials/all_materials.h create mode 100644 SPHINXsys/src/shared/materials/base_material.h create mode 100644 SPHINXsys/src/shared/materials/complex_solid.h create mode 100644 SPHINXsys/src/shared/materials/complex_solid.hpp create mode 100644 SPHINXsys/src/shared/materials/compressible_fluid.cpp create mode 100644 SPHINXsys/src/shared/materials/compressible_fluid.h create mode 100644 SPHINXsys/src/shared/materials/diffusion_reaction.cpp create mode 100644 SPHINXsys/src/shared/materials/diffusion_reaction.h create mode 100644 SPHINXsys/src/shared/materials/elastic_solid.cpp create mode 100644 SPHINXsys/src/shared/materials/elastic_solid.h create mode 100644 SPHINXsys/src/shared/materials/inelastic_solid.cpp create mode 100644 SPHINXsys/src/shared/materials/inelastic_solid.h create mode 100644 SPHINXsys/src/shared/materials/riemann_solver.cpp create mode 100644 SPHINXsys/src/shared/materials/riemann_solver.h create mode 100644 SPHINXsys/src/shared/materials/weakly_compressible_fluid.cpp create mode 100644 SPHINXsys/src/shared/materials/weakly_compressible_fluid.h create mode 100644 SPHINXsys/src/shared/meshes/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/meshes/base_mesh.cpp create mode 100644 SPHINXsys/src/shared/meshes/base_mesh.h create mode 100644 SPHINXsys/src/shared/meshes/cell_linked_list.cpp create mode 100644 SPHINXsys/src/shared/meshes/cell_linked_list.h create mode 100644 SPHINXsys/src/shared/meshes/mesh_with_data_packages.h create mode 100644 SPHINXsys/src/shared/meshes/my_memory_pool.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/active_muscle_dynamics.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/active_muscle_dynamics.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/all_particle_dynamics.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/all_physical_dynamics.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.hpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/particle_dynamics_diffusion_reaction.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/particle_dynamics_diffusion_reaction.hpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/particle_dynamics_dissipation.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/particle_dynamics_dissipation.hpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/electro_physiology/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/particle_dynamics/electro_physiology/electro_physiology.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/electro_physiology/electro_physiology.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/external_force/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/particle_dynamics/external_force/external_force.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/external_force/external_force.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/all_fluid_dynamics.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/all_eulerian_fluid_dynamics.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.hpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.hpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.hpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.hpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.hpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.hpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/general_dynamics/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/particle_dynamics/general_dynamics/general_dynamics.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/general_dynamics/general_dynamics.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/observer_dynamics/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/particle_dynamics/observer_dynamics/observer_dynamics.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/observer_dynamics/observer_dynamics.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/particle_dynamics_algorithms.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/particle_dynamics_algorithms.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/particle_dynamics_bodypart.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/particle_dynamics_bodypart.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/relax_dynamics/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/particle_dynamics/relax_dynamics/relax_dynamics.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/relax_dynamics/relax_dynamics.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/all_solid_dynamics.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/fluid_structure_interaction.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/fluid_structure_interaction.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.h create mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.h create mode 100644 SPHINXsys/src/shared/particle_generator/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/particle_generator/base_particle_generator.cpp create mode 100644 SPHINXsys/src/shared/particle_generator/base_particle_generator.h create mode 100644 SPHINXsys/src/shared/particle_generator/particle_generator_lattice.cpp create mode 100644 SPHINXsys/src/shared/particle_generator/particle_generator_lattice.h create mode 100644 SPHINXsys/src/shared/particles/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/particles/all_particles.h create mode 100644 SPHINXsys/src/shared/particles/base_particles.cpp create mode 100644 SPHINXsys/src/shared/particles/base_particles.h create mode 100644 SPHINXsys/src/shared/particles/diffusion_reaction_particles.cpp create mode 100644 SPHINXsys/src/shared/particles/diffusion_reaction_particles.h create mode 100644 SPHINXsys/src/shared/particles/fluid_particles.cpp create mode 100644 SPHINXsys/src/shared/particles/fluid_particles.h create mode 100644 SPHINXsys/src/shared/particles/neighbor_relation.cpp create mode 100644 SPHINXsys/src/shared/particles/neighbor_relation.h create mode 100644 SPHINXsys/src/shared/particles/particle_adaptation.cpp create mode 100644 SPHINXsys/src/shared/particles/particle_adaptation.h create mode 100644 SPHINXsys/src/shared/particles/particle_sorting.cpp create mode 100644 SPHINXsys/src/shared/particles/particle_sorting.h create mode 100644 SPHINXsys/src/shared/particles/solid_particles.cpp create mode 100644 SPHINXsys/src/shared/particles/solid_particles.h create mode 100644 SPHINXsys/src/shared/regression_testing/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/regression_testing/meanvalue_variance_method.h create mode 100644 SPHINXsys/src/shared/regression_testing/meanvalue_variance_method.hpp create mode 100644 SPHINXsys/src/shared/regression_testing/regression_testing.h create mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/all_simbody.h create mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/array.h create mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/exception.cpp create mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/exception.h create mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/simbody_middle.h create mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/state_engine.cpp create mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/state_engine.h create mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.cpp create mode 100644 SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.h create mode 100644 SPHINXsys/src/shared/sphinxsys_system/CMakeLists.txt create mode 100644 SPHINXsys/src/shared/sphinxsys_system/sph_system.cpp create mode 100644 SPHINXsys/src/shared/sphinxsys_system/sph_system.h create mode 100644 SPHINXsys/src/shared/tbb/CMakeLists.txt delete mode 100644 gpuSPHINXsys/gpuSPHinxsys.cu delete mode 100644 gpuSPHINXsys/gpuSPHinxsys.cuh delete mode 100644 gpuSPHINXsys/src/Box.cuh delete mode 100644 gpuSPHINXsys/src/CellList.cuh delete mode 100644 gpuSPHINXsys/src/GPUUtils.cuh delete mode 100644 gpuSPHINXsys/src/Grid.cuh delete mode 100644 gpuSPHINXsys/src/Kernel.cuh delete mode 100644 gpuSPHINXsys/src/Log.h delete mode 100644 gpuSPHINXsys/src/ParticleData.cuh delete mode 100644 gpuSPHINXsys/src/ParticleGroup.cuh delete mode 100644 gpuSPHINXsys/src/ParticleSorter.cuh delete mode 100644 gpuSPHINXsys/src/Property.cuh delete mode 100644 gpuSPHINXsys/src/System.h delete mode 100644 gpuSPHINXsys/src/allocator.h delete mode 100644 gpuSPHINXsys/src/debugTools.cuh delete mode 100644 gpuSPHINXsys/src/defines.h delete mode 100644 gpuSPHINXsys/src/dtSizeCalc.cu delete mode 100644 gpuSPHINXsys/src/dtSizeCalc.cuh delete mode 100644 gpuSPHINXsys/src/fluidDynamics.cu delete mode 100644 gpuSPHINXsys/src/fluidDynamics.cuh delete mode 100644 gpuSPHINXsys/src/inOut.cu delete mode 100644 gpuSPHINXsys/src/inOut.cuh delete mode 100644 gpuSPHINXsys/src/include/nod/README.md delete mode 100644 gpuSPHINXsys/src/include/nod/nod.hpp delete mode 100644 gpuSPHINXsys/src/timeStepping.cu delete mode 100644 gpuSPHINXsys/src/timeStepping.cuh delete mode 100644 gpuSPHINXsys/src/vector.cuh diff --git a/SPHINXsys/CMakeLists.txt b/SPHINXsys/CMakeLists.txt new file mode 100644 index 0000000000..75659d7ad1 --- /dev/null +++ b/SPHINXsys/CMakeLists.txt @@ -0,0 +1,13 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # project specific cmake dir + +PROJECT("${CURRENT_FOLDER}") + +set(CMAKE_VERBOSE_MAKEFILE on) + +SET(EXECUTABLE_OUTPUT_PATH "${PROJECT_BINARY_DIR}/bin/") +SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) + +ADD_SUBDIRECTORY(src) + +INSTALL(FILES "logo.png" DESTINATION ${CMAKE_INSTALL_PREFIX}/sphinxsys_logo) diff --git a/SPHINXsys/cmake/Dirsearch_for_2D_build.cmake b/SPHINXsys/cmake/Dirsearch_for_2D_build.cmake new file mode 100644 index 0000000000..1b7000f465 --- /dev/null +++ b/SPHINXsys/cmake/Dirsearch_for_2D_build.cmake @@ -0,0 +1,22 @@ +MACRO(HEADER_DIRECTORIES_2D return_list) + FILE(GLOB_RECURSE new_list ${PROJECT_SOURCE_DIR}/src/for_2D_build/*.h) + FILE(GLOB_RECURSE new_list_hpp ${PROJECT_SOURCE_DIR}/src/for_2D_build/*.hpp) + SET(dir_list "") + FOREACH(file_path ${new_list} ${new_list_hpp}) + GET_FILENAME_COMPONENT(dir_path ${file_path} PATH) + SET(dir_list ${dir_list} ${dir_path}) + ENDFOREACH() + LIST(REMOVE_DUPLICATES dir_list) + SET(${return_list} ${dir_list}) +ENDMACRO() + +MACRO(SOURCE_DIRECTORIES_2D return_list) + FILE(GLOB_RECURSE new_list ${PROJECT_SOURCE_DIR}/src/for_2D_build/*.cpp) + SET(dir_list "") + FOREACH(file_path ${new_list}) + GET_FILENAME_COMPONENT(dir_path ${file_path} PATH) + SET(dir_list ${dir_list} ${dir_path}) + ENDFOREACH() + LIST(REMOVE_DUPLICATES dir_list) + SET(${return_list} ${dir_list}) +ENDMACRO() diff --git a/SPHINXsys/cmake/Dirsearch_for_3D_build.cmake b/SPHINXsys/cmake/Dirsearch_for_3D_build.cmake new file mode 100644 index 0000000000..06e817e586 --- /dev/null +++ b/SPHINXsys/cmake/Dirsearch_for_3D_build.cmake @@ -0,0 +1,23 @@ +MACRO(HEADER_DIRECTORIES_3D return_list) + FILE(GLOB_RECURSE new_list ${PROJECT_SOURCE_DIR}/src/for_3D_build/*.h) + FILE(GLOB_RECURSE new_list_hpp ${PROJECT_SOURCE_DIR}/src/for_3D_build/*.hpp) + SET(dir_list "") + FOREACH(file_path ${new_list} ${new_list_hpp}) + GET_FILENAME_COMPONENT(dir_path ${file_path} PATH) + SET(dir_list ${dir_list} ${dir_path}) + ENDFOREACH() + LIST(REMOVE_DUPLICATES dir_list) + SET(${return_list} ${dir_list}) +ENDMACRO() + +MACRO(SOURCE_DIRECTORIES_3D return_list) + FILE(GLOB_RECURSE new_list ${PROJECT_SOURCE_DIR}/src/for_3D_build/*.cpp) + FILE(GLOB_RECURSE new_list_c ${PROJECT_SOURCE_DIR}/src/for_3D_build/*.c) + SET(dir_list "") + FOREACH(file_path ${new_list} ${new_list_c}) + GET_FILENAME_COMPONENT(dir_path ${file_path} PATH) + SET(dir_list ${dir_list} ${dir_path}) + ENDFOREACH() + LIST(REMOVE_DUPLICATES dir_list) + SET(${return_list} ${dir_list}) +ENDMACRO() \ No newline at end of file diff --git a/SPHINXsys/cmake/Dirsearch_for_image_process.cmake b/SPHINXsys/cmake/Dirsearch_for_image_process.cmake new file mode 100644 index 0000000000..2f4c5fe28d --- /dev/null +++ b/SPHINXsys/cmake/Dirsearch_for_image_process.cmake @@ -0,0 +1,22 @@ +MACRO(HEADER_DIRECTORIES_image_process return_list) + FILE(GLOB_RECURSE new_list ${PROJECT_SOURCE_DIR}/src/image_processing/*.h) + FILE(GLOB_RECURSE new_list_hpp ${PROJECT_SOURCE_DIR}/src/image_processing/*.hpp) + SET(dir_list "") + FOREACH(file_path ${new_list} ${new_list_hpp}) + GET_FILENAME_COMPONENT(dir_path ${file_path} PATH) + SET(dir_list ${dir_list} ${dir_path}) + ENDFOREACH() + LIST(REMOVE_DUPLICATES dir_list) + SET(${return_list} ${dir_list}) +ENDMACRO() + +MACRO(SOURCE_DIRECTORIES_image_process return_list) + FILE(GLOB_RECURSE new_list ${PROJECT_SOURCE_DIR}/src/image_processing/*.cpp) + SET(dir_list "") + FOREACH(file_path ${new_list}) + GET_FILENAME_COMPONENT(dir_path ${file_path} PATH) + SET(dir_list ${dir_list} ${dir_path}) + ENDFOREACH() + LIST(REMOVE_DUPLICATES dir_list) + SET(${return_list} ${dir_list}) +ENDMACRO() diff --git a/SPHINXsys/cmake/Dirsearch_shared.cmake b/SPHINXsys/cmake/Dirsearch_shared.cmake new file mode 100644 index 0000000000..4791415043 --- /dev/null +++ b/SPHINXsys/cmake/Dirsearch_shared.cmake @@ -0,0 +1,50 @@ +if(BUILD_WITH_SIMBODY) + include(Simbody_header_directories) +endif() +if(BUILD_WITH_ONETBB) + include(oneTBB_header_directory) +endif() + +MACRO(HEADER_DIRECTORIES_SHARED return_list) + FILE(GLOB_RECURSE new_list ${PROJECT_SOURCE_DIR}/src/shared/*.h) + FILE(GLOB_RECURSE new_list_hpp ${PROJECT_SOURCE_DIR}/src/shared/*.hpp) + SET(dir_list "") + FOREACH(file_path ${new_list} ${new_list_hpp}) + GET_FILENAME_COMPONENT(dir_path ${file_path} PATH) + SET(dir_list ${dir_list} ${dir_path}) + ENDFOREACH() + LIST(REMOVE_DUPLICATES dir_list) + + if(BUILD_WITH_SIMBODY) + FOREACH(simbody_header_path ${SIMBODY_HEADER_DIRECTORIES}) + SET(dir_list ${dir_list} ${simbody_header_path}) + ENDFOREACH() + endif() + + if(BUILD_WITH_ONETBB) + SET(dir_list ${dir_list} ${ONETBB_HEADER_DIRECTORY}) + endif() + + SET(${return_list} ${dir_list}) +ENDMACRO() + +MACRO(SOURCE_DIRECTORIES_SHARED return_list) + FILE(GLOB_RECURSE new_list ${PROJECT_SOURCE_DIR}/src/shared/*.cpp) + FILE(GLOB_RECURSE new_list_c ${PROJECT_SOURCE_DIR}/src/shared/*.c) + SET(dir_list "") + FOREACH(file_path ${new_list} ${new_list_c}) + GET_FILENAME_COMPONENT(dir_path ${file_path} PATH) + + if(BUILD_WITH_SIMBODY) + SET(dir_list ${dir_list} ${dir_path}) + else() # if Simbody is not built, ignore the simbody and clapack folders + if(NOT dir_list MATCHES ("/simbody/+" OR "/clapack_for_SPHinXsys/+") ) + SET(dir_list ${dir_list} ${dir_path}) + endif() + endif() + + ENDFOREACH() + LIST(REMOVE_DUPLICATES dir_list) + SET(${return_list} ${dir_list}) +ENDMACRO() + diff --git a/SPHINXsys/cmake/Headersearch.cmake b/SPHINXsys/cmake/Headersearch.cmake new file mode 100644 index 0000000000..b18e964829 --- /dev/null +++ b/SPHINXsys/cmake/Headersearch.cmake @@ -0,0 +1,8 @@ +MACRO(DIR_INC_HEADER_NAMES input_list return_list) + SET(name_list "") + foreach(filenames ${input_list}) + get_filename_component(HEADER_NAMES ${filenames} NAME) + list(APPEND name_list ${HEADER_NAMES}) + endforeach() + SET(${return_list} ${name_list}) +ENDMACRO() \ No newline at end of file diff --git a/SPHINXsys/cmake/Simbody_header_directories.cmake b/SPHINXsys/cmake/Simbody_header_directories.cmake new file mode 100644 index 0000000000..e9e08240a1 --- /dev/null +++ b/SPHINXsys/cmake/Simbody_header_directories.cmake @@ -0,0 +1,15 @@ +set(SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS "") +set(SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS "${SPHINXSYS_PROJECT_DIR}") #SPHinXsys + +set(SIMBODY_HEADER_DIRECTORIES + "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKcommon/BigMatrix/include" + "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKcommon/Geometry/include" + "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKcommon/Mechanics/include" + "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKcommon/Polynomial/include" + "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKcommon/Random/include" + "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKcommon/Scalar/include" + "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKcommon/Simulation/include" + "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKcommon/SmallMatrix/include" + "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKmath/Geometry/include" + "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/SPHINXsys/src/shared/simbody/SimTKmath/Integrators/include" +) \ No newline at end of file diff --git a/SPHINXsys/cmake/oneTBB_header_directory.cmake b/SPHINXsys/cmake/oneTBB_header_directory.cmake new file mode 100644 index 0000000000..9b16bf949c --- /dev/null +++ b/SPHINXsys/cmake/oneTBB_header_directory.cmake @@ -0,0 +1,5 @@ +set(SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS "${SPHINXSYS_PROJECT_DIR}") #SPHinXsys + +set(ONETBB_HEADER_DIRECTORY + "${SHP_SOURCE_DIRECTORY_FOR_INCLUDE_PATHS}/oneTBB/include" +) \ No newline at end of file diff --git a/SPHINXsys/logo-16x16.ico b/SPHINXsys/logo-16x16.ico new file mode 100644 index 0000000000000000000000000000000000000000..a5de17d43c1fea419d32a4b9b2727628852a4bb6 GIT binary patch literal 632 zcmV-;0*Czo0096201yxW0000W0Ad0F02TlM0EtjeM-2)Z3IG5A4M|8uQUCw|5C8xG z5C{eU001BJ|6u?C00DDSM?wIu&K&6g000DMK}|sb0I`n?{9y$E000SaNLh0L01m?d z01m?e$8V@)0005^Nkl?3u~wDS`@Ks?@5bh;79S zRz+I{?eWq=MMV^`CaG=GBu&#K#xxgu>L>UeK8P>A_`KY+&-f4tN^!x!Vb-j*_v~3G z@!!y4#i-dD^wT=@i;vJR8*o%F?C>V?pph)-^n1|JG!ptr-RNg5e7ym)(1c01U{bA~ zCUmm|?@9r5AqN^>0v*r9=S0lHWVQpp@CnI6r>B9svj)0Wgg3SVI=2iux#$eIvK9BQ zZz8Bi2x<|MrOsC&9n`}r)N~m%xr+E2Y;ejMSomBu_z>Yx^ce%~E%=qqt}gWB8q_^D zxWNXO^6*BoKOEH!7Jm<#Thficw6V|b@qi=!h&#AjhBsMk$-#fYJ1w2I(`jd>GksBOV&aEC{%GQtiN+5#8byr>{wUe5z31HN zOexS3z?JOGWRV?u z3!qs}<;AE|(Lw5!FsU_dq{>=Il{S-F1}kZz0uzLQk3fFF5`)wnA0TrQR+l1kDnLj%h0@k&YdJzK3TS=`9ky;J`PkRDDE<0x8I@oCtpG}iF54$iZ z=F|^8Vi%qkoa)H1+S*0V>k(3~ULu7uG#WCKj6~ldc0@8ynL*W2CnCl6n^c-s;YVLGpl4!&-R3=0RrC*@3SO z#~g0wo%=v8#QkFwNLq`J$unQi$d5AVI^YwfcH>yrK)`!BVaT2aD*(rFaarhoI}KiF z9K0;MXSYX3g)oqU4Sh_-us|A?$cV60u7Ns~C^36+r1@U?5CS+4-p1F@9zSC32MLog@zSwzDuERbL%_fo`TG!x=%syjN*-{J{ABntV*xv+7T~OF z|I?I*AFxxVhOc+@9J`$b2>Cc{1OniZA{Nj!`nMgLg$aH-$M8jV9RY<^oqsqRI|6q2 z^iY%2ehA(H{78EQ9#_lu6lBgpI(hy*iaHV~Hdx5*a@JZnlV;a(}{7j!5_*tWd zr~~nD{0CF>W#FBL+c+3D=TRs44mqBr!o%?=?5n#6a|U$-Ra2vu*hhBb@2!Fnba3jxK0d^??~Tc{9Nyihozy}g)6uwx_ePo)4ej` zb^&oN*QW=r3Nbe-Ts@B@Kew+1YFF$ZK6=?Av@P=OQ(HQpw6AB5#Rmo-*2fZ`2$^R% zYTP@p%lYWpp#mJcS|R5GG^q?b8`@7z+bb#AhwPLLaF5@EYx64CaK6L2o^wKJzR_}xpY4cUwbQPF zsR+-Jopb5+$i4dSbwOxC)H6+_0uA5WX{;P>P#U^2ojR zi@RX{7Ff#UjUvDV>ND$cwsPDQTBkfQ?3rBK*7cW6xCtcY>~<#iT5u-EcR((=DS`k~ zdGT>MDw`kzv6C3G!v>)v17?dzB8BMOovMV%MG)oE4$M z$!?!a?)uL|!0M1IvEolAP8t@&F=Rm^Dojh(w?Asvoe>I48fzwEel2-MKq&;UyEs|+ zlZnN6g4VP)FDMV@UAZ%b-yfQ<1NC1`Meh2}(SKKE?_NFL-f{o{002ovPDHLkV1h7d B>ze=o literal 0 HcmV?d00001 diff --git a/SPHINXsys/logo-white.png b/SPHINXsys/logo-white.png new file mode 100644 index 0000000000000000000000000000000000000000..660e947c3a304384b7b61d3e050d362fc0aa01a2 GIT binary patch literal 5113 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 literal 0 HcmV?d00001 diff --git a/SPHINXsys/logo-with-name.png b/SPHINXsys/logo-with-name.png new file mode 100644 index 0000000000000000000000000000000000000000..ac561518c9ac56fa937d93427c11e79e949ec487 GIT binary patch literal 9614 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

A`5`35ZS=5Mc`o?QWKY+i#4%p{I1w z`UtX%h>^h?9dz@1U<)1lz7n?$o!pQz_}YPY|Zwv1;r!BNqC z#;++z{hOyJO>DM7vD36`!HZMI42Na(rb^EJ zKDi=dXwJmC#uE~qD)Q`!=(Ml4yDs6qyQPdDAYHT-QhqrYNP-$32n>T293dWpKvOQj z4ZXB^2ndOJG-KN)!(<))LeFKDG-@3Z5ZZ4{(sLR4J91x$4{0!PTyjOXfUfmRj}~6< zYSt5^wYxLsFO}lr`bj(ZbD&EB$IHCDAbS7ugsCXP4!0$)>F7Pv$Yc4+c)P=p)gC&CIdRqAxM$gV8J z6DH5)mtHdaq2U+hP0!Ci`wM(17)MA)M1qk}pnLpdmY8%CT=!f<^{G<6CBUT6+i$Scg;* z1F|ea2}+G@(Kv&Zg*-1j5fk3$DrvlJ5!5r_mGp7QOd2+h69y*Gl(ymqqjN~iz1j?? zx^VB0;pTS>P_lI1BDtXDoAgXFE(84RkIhlnc`GyKW!H1PD<~@IxvX7<8}na(YFv~$ zG1QbUdSssI3bOBzgsC!pTDFP!vA^J5W`|$=9fLhZ%SN_L@HDxC6h-5JW>*#zt(t)? zESx=$H^@^T9&vGhNh3U@`ztSw^S}HOW~^9o&aY?`ljuF1g^^b%L4p3SJR2Kl50hpu9>PPrzxsdU!a`^rBI834 zF+|t4My(!hcr+SOym)SIeREk`P4Tj&+TFe2u)aUW2~*>6u5#I&H1&K6KC-UgAnzP8 z^%?D^UcvkY#)o-NR+M>&sJ-q@P7)PbmaRZvpyz(Lt5nUbBBtKyav`DVE!Q)-g4Cna z8z>E~)s9IGx_lQk=8+G1@s5tTf?y*|j@a#X#uaK1b|V)IR$ z+fZ(jb-|DFAtQT7pr z7tg`52z0F_+)y}K_-^L2(EeU*9LIp z&3{qdq4yiR@NxG$HG$)Qm8=fGcG-)Wh5Ev{w(yM^u|tOpzjyNV-!GW;@Z<@jhYZ&4 ziiS*kAjUQ1cD+537wA=M^7xw79Z7e2`oC>*%P=R`|K?@;zFoQcGx>oF;|mt(WJ?CQ zIc9vr^uU{f{QZ2xuWc`}qLC=CIO24kDJpht~ml4cuzuu@Xo6k3SWwv^d_t3Ms_a zU2(tbnVfKP{cpxhUnA8*f4w~a5ePA+*EqPqHzGOXZ%?Vm3r#3_W9)?2v_EBHW?^cK zD{5L9MQx?5YH*k5=*BOw@HRk3;9@|<^y3M)$w)6K-LArUwAvyYn;KRznh zw=>msM`iYeyVLkfV!roh?wTXdS((uEmMwZ%_(@<-=BP;-G))Sgxgzf|FTLl(ZE0@7 z!jBKNo8QTlB}8z1jQ8GwW6{IHj~|t}+7}S<8AR5+0gE0Me%v^*C->PW)B+}CTB^6+ zeL=9+9u|I-FkBMQ5HfXH_Uu9RT?c3mtmt9kCjq~xnJc`^4S+=t3qMYDl`n{Q4H+`- z>5TkMS&#MeGB*I$VeJn|9WROb(xzn{Qv*}07*qoM6N<$ Ef?E-K#Q*>R literal 0 HcmV?d00001 diff --git a/SPHINXsys/logo-with-white.png b/SPHINXsys/logo-with-white.png new file mode 100644 index 0000000000000000000000000000000000000000..81f84b8d755ab03e50eec3a0a7f279771a68911d GIT binary patch literal 7305 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�RhNaM%H897@0 zP65Ah+4*a|dxerRg2v~z#?hgDf7C*53$!!nxrER>yDX)ZYz3cWVeUqkC(O8nSnSET zezu>L2!u@-5Rn#e_y&vo*vV4VheB63LtMlwyDkxq0do>)0-6X(TEg2Fm7~I?3Y-gI zlC*B(?Y4Zi8Cy4fd-{sg+S$5`%oCk36xmUBC!Sekp+MlDT=pWd!JSHtGI)Fggl~PY z8T)?@uYF>dEU_~MG<{h^6OE0w+xEGjsHUwcKd#R#9adwfyHlQB12KO09rLSj`$OpBh(Go*v(X#Gh z1Vh+ATliNNw_dJ%2b@Hj_Rqs4wXpyYDG5rF`Mu<$j&?GtBqXhyb2*$n_xTL041D?e zj0~>$90E$NOmFCvnz+ay!2(_Ie%lHy2-7SKBIf^m#n$=_W4er;Q$Rx}GV{{>Dw=vM zEy(64>GQ*xovvaHl8-5=yc}r>;LUNM-)=8U+d)_GMc`Ws ziJ+kJ4i_*O?_9^JOKO8?Sp>=~XT-F_`+zncG@s_pTzM|mE56T}J98vUjKCf6t%GQ| z;|!Ah5WcJoUvhO?lnBt4(`rz{9-6SBCVoMcChA;}8mZc95j#q45Ej*Dh3hJ#wNxH< z$$gwt)?T^8e%dy)7!7wL3upra+K#fjad2vi!vLN^dS_d&c5t>m(Us+@%IbK-qb$2fb zY`a7$b(y`!FELxW$=0Y;M|v2TGKAEkOs^b=x(8H?s6 zF3Os=vwjajQm37u4i>riKazwkK}bT##)r#x5_sn$46-#*NsLD`ma~-t=PfY|hXrKO z(+Vb-K;g!U_~K!b(s)UIT@@i-gOADtmuE@BlbyLE4!u9m=6klyYv#ST5aXm|3a@-v zk=&zImU^q<(VHYB7@!98aTmgMi9-OaV_676`k`_P9q{!uKfoBtw?49GeZqcdvIdjv{C$ z@d_r8dHeEV$Ptq?AfrSa&+joRq9HKCG$kEAZOB0y<+1Ek+@5GJ7#LVw+I*;@8LE?1 zpvN#m{vhemC(_1LwN1C{NeS~*{*q9Wb;2-B=F}iEe@W$RvQnw|F5=Nq+?Vuw8?JdXoT zQHWtNiEaB7yJSs8rukLr;BY8{4TFnJ#Z!6uMDm zf*5_Z*H(I^QJaBxxXc3@xIQJaM03LVVD6Gi7jXb2*?Vth`r^RUnE$xx4XyUuJ48bz c02me^8}$voVkAxpFQR~*j(aIh_Vkqh0dOTFvj6}9 literal 0 HcmV?d00001 diff --git a/SPHINXsys/mainpage.md b/SPHINXsys/mainpage.md new file mode 100644 index 0000000000..01ae0075f5 --- /dev/null +++ b/SPHINXsys/mainpage.md @@ -0,0 +1,100 @@ +SPHinXsys (pronunciation: s'finksis) +is an acronym from Smoothed Particle +Hydrodynamics for industrial compleX systems. +It provides C++ APIs for physical accurate simulation and aims to model coupled +industrial dynamic systems including fluid, solid, multi-body dynamics and +beyond with SPH (smoothed particle hydrodynamics), +a meshless computational method using particle discretization. + +Included physics +----------------- +Fluid dynamics, solid dynamics, fluid-structure interactions (FSI), +and their coupling to multi-body dynamics (with SIMBody library https://simtk.org) + +SPH method and algorithms +----------------- +SPH is a fully Lagrangian particle method, +in which the continuum media is discretized into Lagrangian particles +and the mechanics is approximated as the interaction between them +with the help of a kernel, usually a Gaussian-like function. +SPH is a mesh free method, which does not require a mesh to define +the neighboring configuration of particles, +but construct of update it according to the distance between particles. +A remarkable feature of this method is that its computational algorithm +involves a large number of common abstractions +which link to many physical systems inherently. +Due to such unique feature, +SPH have been used here for unified modeling of both fluid and solid mechanics. + +The SPH algorithms are based on the published work of the authors. +The algorithms for the discretization of the fluid dynamics equations +are based on a weakly compressible fluid formulation, +which is suitable for the problems with incompressible flows, +and compressible flows with low Mach number (less than 0.3). +The solid dynamics equations are discretized by a total Lagrangian formulation, +which is suitable to study the problems involving linear and non-linear elastic materials. +The FSI coupling algorithm is implemented in a kinematic-force fashion, +in which the solid structure surface describes the phase-interface and, +at the same time, experiences the surface forces imposed +by the fluid pressure and friction. + +Geometric models +----------------- +2D models can be built using basic shapes (polygon and circle) and full version of binary operations. +3D models can be generated by simple shapes (brick and sphere), +imported from external STL files and processed by applying simple binary operations, e.g. add and substract. + +Material models +----------------- +Newtonian fluids with isothermal linear equation of state. Non-newtonian fluids with Oldroyd-B model. +Linear elastic solid, non-linear elastic solid with Neo-Hookian model and anisotropic muscle model. + +Multi-resolution modeling +----------------- +Uniform resolution is used within each fluid or solid bodies. +However, it is allowed to use different resolutions for different bodies. +For example, one is able to using higher resolution for a solid body +which is interacting with a fluid body with lower resolution. + +Parallel Computing +----------------- +Intel Threading Building Blocks (TBB) is used for the multi-core parallelism. + +Authors +----------------- +Xiangyu Hu, Luhui Han, Chi Zhang, Shuoguo Zhang, Massoud Rezavand, Yongchuan Yu + +Project Principle Investigator +----------------- +Xiangyu Hu (xiangyu.hu@tum.de), Department of Mechanical Engineering, +Technical University of Munich + +Acknowledgements +----------------- +German Research Foundation (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 and HU1527/12-1. + +Please cite +----------------- +1. Luhui Han and Xiangyu Hu, +"SPH modeling of fluid-structure interaction", +Journal of Hydrodynamics, 2018: 30(1):62-69. + +2. Chi Zhang and Massoud Rezavand and Xiangyu Hu, +"Dual-criteria time stepping for weakly compressible smoothed particle hydrodynamics", +Journal of Computational Physics 404 (2020) 109135 + +3. Chi Zhang et al. +"SPHinXsys: An open-source meshless, multi-resolution and multi-physics library", +Software Impacts, 6 (2020) 100033 + +4. Chi Zhang, Massoud Rezavand, Xiangyu Hu, +"A multi-resolution SPH method for fluid-structure interactions", +Journal of Computational Physics, in press (2021) + +5. Chi Zhang, Yanji Wei, Frederic Dias, Xiangyu Hu, +"An efficient fully Lagrangian solver for modeling wave interaction with oscillating wave energy converter", +arXiv:2012.05323 + +6. Chi Zhang, Jianhang Wang, Massoud Rezavand, Dong Wu, Xiangyu Hu, +"An integrative smoothed particle hydrodynamics framework for modeling cardiac function", + arXiv:2009.03759 diff --git a/SPHINXsys/src/CMakeLists.txt b/SPHINXsys/src/CMakeLists.txt new file mode 100644 index 0000000000..4779204823 --- /dev/null +++ b/SPHINXsys/src/CMakeLists.txt @@ -0,0 +1,23 @@ +# shared build +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Dirsearch_shared) + +## prepare dirctoriesfor head and source files +HEADER_DIRECTORIES_SHARED(headdirs_shared) +SOURCE_DIRECTORIES_SHARED(sourcedirs_shared) + +SET(usefuldirs ${headdirs_shared} ${sourcedirs_shared}) +LIST(REMOVE_DUPLICATES usefuldirs) + +SET(usefulsubdirs ${usefuldirs}) +LIST(REMOVE_ITEM usefulsubdirs ${CMAKE_CURRENT_SOURCE_DIR}) + +if(DEFINED BOOST_AVAILABLE) + ADD_SUBDIRECTORY(for_2D_build) +endif() +ADD_SUBDIRECTORY(for_3D_build) +ADD_SUBDIRECTORY(shared) + +if(BUILD_WITH_IMAGE_PROCESS) +add_subdirectory(image_processing) +endif() \ No newline at end of file diff --git a/SPHINXsys/src/Readme.txt b/SPHINXsys/src/Readme.txt new file mode 100644 index 0000000000..9849a16c94 --- /dev/null +++ b/SPHINXsys/src/Readme.txt @@ -0,0 +1,5 @@ +Here, the source codes shared for 2d and 3d builds are in folder /shared. +The source codes for 2d build are in folder /for_2d_build. +The source codes for 3d build are in folder /for_3d_build. +Note that, in order to build both 2d and 3d codes at the same project, the make files should be arranged very carefully. + diff --git a/SPHINXsys/src/for_2D_build/CMakeLists.txt b/SPHINXsys/src/for_2D_build/CMakeLists.txt new file mode 100644 index 0000000000..df42e6d71c --- /dev/null +++ b/SPHINXsys/src/for_2D_build/CMakeLists.txt @@ -0,0 +1,81 @@ +## 2D build +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir + +## prepare dirctories for head and source files +HEADER_DIRECTORIES_SHARED(headdirs_shared) +SOURCE_DIRECTORIES_SHARED(sourcedirs_shared) + +include(Dirsearch_for_2D_build) +HEADER_DIRECTORIES_2D(headdirs_2D) +SOURCE_DIRECTORIES_2D(sourcedirs_2D) + +SET(usefuldirs ${headdirs_2D} ${sourcedirs_2D}) +LIST(REMOVE_DUPLICATES usefuldirs) + +SET(usefulsubdirs ${usefuldirs}) +LIST(REMOVE_ITEM usefulsubdirs ${CMAKE_CURRENT_SOURCE_DIR}) + +##Add all useful subdirectories +FOREACH(subdir_path ${usefulsubdirs}) + #message(STATUS ${subdir_path}) + ADD_SUBDIRECTORY(${subdir_path}) +ENDFOREACH() + +if(BUILD_WITH_IMAGE_PROCESS) + include(Dirsearch_for_image_process) + HEADER_DIRECTORIES_IMAGE_PROCESS(headdirs_image_process) + SOURCE_DIRECTORIES_IMAGE_PROCESS(sourcedirs_image_process) + ## combin head and souce directories + SET(headdirs ${headdirs_shared} ${headdirs_2D} ${headdirs_image_process}) + SET(sourcedirs ${sourcedirs_shared} ${sourcedirs_2D} ${sourcedirs_image_process}) +else(BUILD_WITH_IMAGE_PROCESS) + ## combin head and souce directories + SET(headdirs ${headdirs_shared} ${headdirs_2D}) + SET(sourcedirs ${sourcedirs_shared} ${sourcedirs_2D}) +endif(BUILD_WITH_IMAGE_PROCESS) + + +##Add all header dirs +FOREACH(headdir_path ${headdirs}) + #message(STATUS ${headdir_path}) + INCLUDE_DIRECTORIES("${headdir_path}") +ENDFOREACH() + +##Add all source files +set(SCR_FILES "") +FOREACH(srcdir_path ${sourcedirs}) + #message(STATUS ${srcdir_path}) + set(DIR_scrs "") + AUX_SOURCE_DIRECTORY(${srcdir_path} DIR_scrs) + list(APPEND SCR_FILES ${DIR_scrs}) +ENDFOREACH() + +#FOREACH(file1 ${SCR_FILES}) + #message(STATUS ${file1}) +#ENDFOREACH() + +ADD_LIBRARY(sphinxsys_2d SHARED ${SCR_FILES}) +ADD_LIBRARY(sphinxsys_static_2d STATIC ${SCR_FILES}) + +SET_TARGET_PROPERTIES(sphinxsys_static_2d PROPERTIES OUTPUT_NAME "sphinxsys_2d") +#SET_TARGET_PROPERTIES(sphinxsys PROPERTIES VERSION 1.0 SOVERSION 0) + +SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) + +if(MSVC) + target_link_libraries(sphinxsys_2d ${TBB_LIBRARYS} ${Simbody_LIBRARIES}) +else(MSVC) + if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + target_link_libraries(sphinxsys_2d ${TBB_LIBRARYS} ${Simbody_LIBRARIES} ${Boost_LIBRARIES} stdc++) + else(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + target_link_libraries(sphinxsys_2d ${TBB_LIBRARYS} ${Simbody_LIBRARIES} stdc++ stdc++fs) + endif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") +endif(MSVC) + +INSTALL(TARGETS sphinxsys_2d sphinxsys_static_2d +RUNTIME DESTINATION 2d_code/bin +LIBRARY DESTINATION 2d_code/lib +ARCHIVE DESTINATION 2d_code/lib) + +FILE(GLOB_RECURSE hpp_headers ${PROJECT_SOURCE_DIR}/src/shared/*.hpp ${PROJECT_SOURCE_DIR}/src/for_2D_build/*.hpp) +INSTALL(FILES ${hpp_headers} DESTINATION 2d_code/include) diff --git a/SPHINXsys/src/for_2D_build/bodies/CMakeLists.txt b/SPHINXsys/src/for_2D_build/bodies/CMakeLists.txt new file mode 100644 index 0000000000..9ef366a60f --- /dev/null +++ b/SPHINXsys/src/for_2D_build/bodies/CMakeLists.txt @@ -0,0 +1,7 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/bodies/solid_body_supplementary.cpp b/SPHINXsys/src/for_2D_build/bodies/solid_body_supplementary.cpp new file mode 100644 index 0000000000..5729e7e3e8 --- /dev/null +++ b/SPHINXsys/src/for_2D_build/bodies/solid_body_supplementary.cpp @@ -0,0 +1,55 @@ +/** + * @file base_body_supplementary.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#include "solid_body.h" +#include "solid_particles.h" + +namespace SPH +{ + //=================================================================================================// + void SolidBodyPartForSimbody::tagBodyPart() + { + BodyPartByParticle::tagBodyPart(); + + Real body_part_volume(0); + Vecd mass_center = Vecd(0); + for (size_t i = 0; i < body_part_particles_.size(); ++i) + { + size_t index_i = body_part_particles_[i]; + Real particle_volume = solid_particles_->Vol_[index_i]; + mass_center += particle_volume * solid_particles_->pos_0_[index_i]; + body_part_volume += particle_volume; + } + + mass_center /= body_part_volume; + initial_mass_center_ = Vec3d(mass_center[0], mass_center[1], 0.0); + + //computing unit intertia + Real Ix = 0.0; + Real Iy = 0.0; + Real Iz = 0.0; + for (size_t i = 0; i < body_part_particles_.size(); ++i) + { + size_t index_i = body_part_particles_[i]; + Vecd particle_position = solid_particles_->pos_0_[index_i]; + Real particle_volume = solid_particles_->Vol_[index_i]; + + Real r_x = (particle_position[1] - mass_center[1]); + Ix += particle_volume * r_x * r_x; + Real r_y = (particle_position[0] - mass_center[0]); + Iy += particle_volume * r_y * r_y; + Iz += particle_volume + * (particle_position - mass_center).normSqr(); + } + Ix /= body_part_volume; + Iy /= body_part_volume; + Iz /= body_part_volume; + + body_part_mass_properties_ + = new SimTK::MassProperties(body_part_volume * solid_body_density_, + Vec3d(0), SimTK::UnitInertia(Ix, Iy, Iz)); + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/for_2D_build/common/CMakeLists.txt b/SPHINXsys/src/for_2D_build/common/CMakeLists.txt new file mode 100644 index 0000000000..9ef366a60f --- /dev/null +++ b/SPHINXsys/src/for_2D_build/common/CMakeLists.txt @@ -0,0 +1,7 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/common/data_type.h b/SPHINXsys/src/for_2D_build/common/data_type.h new file mode 100644 index 0000000000..ede09d2796 --- /dev/null +++ b/SPHINXsys/src/for_2D_build/common/data_type.h @@ -0,0 +1,39 @@ + +#ifndef DATA_TYPE_2D_H +#define DATA_TYPE_2D_H + + +#include "base_data_type.h" + +namespace SPH { + + //for 2d build + using Veci = Vec2i; + using Vecu = Vec2u; + using Vecd = Vec2d; + using Matd = Mat2d; + using SymMatd = SymMat2d; + using AngularVecd = Real; + const int indexAngularVector = 0; + + + using Transformd = Transform2d; + + template + using PackageDataMatrix = std::array, ARRAY_SIZE>; + + template + using MeshDataMatrix = DataType**; + + /** only works for smoothing length ratio less or equal than 1.3*/ + constexpr int MaximumNeighborhoodSize = int(M_PI * 9); + const int Dimensions = 2; + + /** correction matrix, only works for thin structure dynamics. */ + const Matd reduced_unit_matrix = { 1, 0, 0, 0 }; + + /** initial local normal, only works for thin structure dynamics. */ + const Vecd local_pseudo_n_0 = Vecd(0.0, 1.0); +} + +#endif //DATA_TYPE_2D_H \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/common/scalar_functions_supplementary.cpp b/SPHINXsys/src/for_2D_build/common/scalar_functions_supplementary.cpp new file mode 100644 index 0000000000..96d76c90f1 --- /dev/null +++ b/SPHINXsys/src/for_2D_build/common/scalar_functions_supplementary.cpp @@ -0,0 +1,14 @@ +/** + * @file scalar_functions_supplementary.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + * @version 0.1 + */ + +#include "scalar_functions.h" + +namespace SPH { + //=================================================================================================// + int SecondAxis(int axis_direction) { + return axis_direction == 1 ? 0 : 1; + } +} diff --git a/SPHINXsys/src/for_2D_build/geometries/CMakeLists.txt b/SPHINXsys/src/for_2D_build/geometries/CMakeLists.txt new file mode 100644 index 0000000000..9ef366a60f --- /dev/null +++ b/SPHINXsys/src/for_2D_build/geometries/CMakeLists.txt @@ -0,0 +1,7 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/geometries/geometry.cpp b/SPHINXsys/src/for_2D_build/geometries/geometry.cpp new file mode 100644 index 0000000000..e187c6e83a --- /dev/null +++ b/SPHINXsys/src/for_2D_build/geometries/geometry.cpp @@ -0,0 +1,313 @@ +/** + * @file geometry.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#include "geometry.h" + +using namespace boost::geometry; + +namespace SPH +{ + //=================================================================================================// + boost_multi_poly MultiPolygon:: + MultiPolygonByBooleanOps(boost_multi_poly multi_poly_in, + boost_multi_poly multi_poly_op, ShapeBooleanOps boolean_op) + { + boost_multi_poly multi_poly_tmp_in = multi_poly_in; + //out multi-poly need to be emtpy + //otherwise the operation is not valid + boost_multi_poly multi_poly_tmp_out; + + switch (boolean_op) + { + case ShapeBooleanOps::add: + { + boost::geometry::union_(multi_poly_tmp_in, multi_poly_op, multi_poly_tmp_out); + break; + } + + case ShapeBooleanOps::sub: + { + boost::geometry::difference(multi_poly_tmp_in, multi_poly_op, multi_poly_tmp_out); + break; + } + case ShapeBooleanOps::sym_diff: + { + boost::geometry::sym_difference(multi_poly_tmp_in, multi_poly_op, multi_poly_tmp_out); + break; + } + case ShapeBooleanOps::intersect: + { + boost::geometry::intersection(multi_poly_tmp_in, multi_poly_op, multi_poly_tmp_out); + break; + } + default: + { + std::cout << "\n FAILURE: the type of boolean operation is undefined!" << std::endl; + std::cout << "\n Please check the boost libraray reference." << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + throw; + } + } + return multi_poly_tmp_out; + } + //=================================================================================================// + void MultiPolygon::addAMultiPolygon(MultiPolygon &multi_polygon_op, ShapeBooleanOps op) + { + multi_poly_ = MultiPolygonByBooleanOps(multi_poly_, multi_polygon_op.getBoostMultiPoly(), op); + } + //=================================================================================================// + void MultiPolygon::addABoostMultiPoly(boost_multi_poly &boost_multi_poly_op, ShapeBooleanOps op) + { + multi_poly_ = MultiPolygonByBooleanOps(multi_poly_, boost_multi_poly_op, op); + } + //=================================================================================================// + void MultiPolygon::addACircle(Vec2d center, Real radius, int resolution, ShapeBooleanOps op) + { + Vec2d buffer_center = center; + Real buffer_radius = radius; + int buffer_res = resolution; + + // Declare the point_circle strategy + strategy::buffer::join_round join_strategy; + strategy::buffer::end_round end_strategy; + strategy::buffer::side_straight side_strategy; + strategy::buffer::point_circle circle_strategy(buffer_res); + strategy::buffer::distance_symmetric circle_dist_strategy(buffer_radius); + + // Create the buffer of a multi point + model::d2::point_xy circle_center_pnt; + + boost::geometry::set<0>(circle_center_pnt, buffer_center[0]); + boost::geometry::set<1>(circle_center_pnt, buffer_center[1]); + + boost_multi_poly multi_poly_circle; + buffer(circle_center_pnt, multi_poly_circle, + circle_dist_strategy, side_strategy, + join_strategy, end_strategy, circle_strategy); + + if (!is_valid(multi_poly_circle)) + { + std::cout << "\n Error: the multi ploygen is not valid." << std::endl; + std::cout << "\n The points must be in clockwise. Please check the boost libraray reference." << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + throw; + } + + multi_poly_ = MultiPolygonByBooleanOps(multi_poly_, multi_poly_circle, op); + } + //=================================================================================================// + void MultiPolygon::addAPolygon(std::vector &points, ShapeBooleanOps op) + { + std::vector> pts; + for (const Vecd &pnt : points) + { + pts.push_back(model::d2::point_xy(pnt[0], pnt[1])); + } + + boost_poly poly; + append(poly, pts); + if (!is_valid(poly)) + { + std::cout << "\n Try to reverse the points to clockwise." << std::endl; + poly.clear(); + std::vector> pts_reverse(pts.rbegin(), pts.rend()); + append(poly, pts_reverse); + if (!is_valid(poly)) + { + std::cout << "\n Error: the multi ploygen is still not valid. Please check the boost libraray reference." << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + throw; + } + } + + boost_multi_poly multi_poly_polygen; + convert(poly, multi_poly_polygen); + + multi_poly_ = MultiPolygonByBooleanOps(multi_poly_, multi_poly_polygen, op); + } + //=================================================================================================// + bool MultiPolygon::checkContain(const Vec2d &pnt, bool BOUNDARY_INCLUDED /*= true*/) + { + if (BOUNDARY_INCLUDED) + { + return covered_by(model::d2::point_xy(pnt[0], pnt[1]), multi_poly_); + } + else + { + return within(model::d2::point_xy(pnt[0], pnt[1]), multi_poly_); + } + } + //=================================================================================================// + Vec2d MultiPolygon::findClosestPoint(const Vec2d &input_pnt) + { + typedef model::d2::point_xy pnt_type; + typedef model::referring_segment> seg_type; + /* + typedef model::segment> seg_type; + From the documentation on segment and referring_segment, the only difference between the two is that + referring_segment holds a reference to the points. + This is what is needed in a for each that modifies the segment since the points modified should be + reflected in the linestring. In a for each that does not modify the points, it should still take a + reference (most likely a const reference) since it reduces the amount of copying. + */ + pnt_type input_p(input_pnt[0], input_pnt[1]); + model::segment> closest_seg; + Real closest_dist_2seg = boost::numeric::bounds::highest(); + std::function findclosestsegment = [&closest_seg, &closest_dist_2seg, &input_p](seg_type seg) + { + Real dist = boost::geometry::distance(input_p, seg); + if (dist < closest_dist_2seg) + { + closest_dist_2seg = dist; + //closest_seg.append(seg); + Real x0 = boost::geometry::get<0, 0>(seg); + Real y0 = boost::geometry::get<0, 1>(seg); + Real x1 = boost::geometry::get<1, 0>(seg); + Real y1 = boost::geometry::get<1, 1>(seg); + boost::geometry::set<0, 0>(closest_seg, x0); + boost::geometry::set<0, 1>(closest_seg, y0); + boost::geometry::set<1, 0>(closest_seg, x1); + boost::geometry::set<1, 1>(closest_seg, y1); + } + }; + boost::geometry::for_each_segment(multi_poly_, findclosestsegment); + + Vec2d p_find(0, 0); + + Real x0 = boost::geometry::get<0, 0>(closest_seg); + Real y0 = boost::geometry::get<0, 1>(closest_seg); + Real x1 = boost::geometry::get<1, 0>(closest_seg); + Real y1 = boost::geometry::get<1, 1>(closest_seg); + Vec2d p_0(x0, y0); + Vec2d p_1(x1, y1); + Vec2d vec_v = p_1 - p_0; + Vec2d vec_w = input_pnt - p_0; + + Real c1 = dot(vec_v, vec_w); + if (c1 <= 0) + { + p_find = p_0; + } + else + { + Real c2 = dot(vec_v, vec_v); + if (c2 <= c1) + { + p_find = p_1; + } + else + { + p_find = p_0 + vec_v * c1 / c2; + } + } + + return p_find; + } + //=================================================================================================// + BoundingBox MultiPolygon::findBounds() + { + Vec2d lower_bound(0), upper_bound(0); + typedef boost::geometry::model::box> box; + lower_bound[0] = boost::geometry::return_envelope(multi_poly_).min_corner().get<0>(); + lower_bound[1] = boost::geometry::return_envelope(multi_poly_).min_corner().get<1>(); + upper_bound[0] = boost::geometry::return_envelope(multi_poly_).max_corner().get<0>(); + upper_bound[1] = boost::geometry::return_envelope(multi_poly_).max_corner().get<1>(); + return BoundingBox(lower_bound, upper_bound); + } + //=================================================================================================// + bool ComplexShape::checkContain(const Vecd &input_pnt, bool BOUNDARY_INCLUDED) + { + return multi_ploygen_.checkContain(input_pnt, BOUNDARY_INCLUDED); + } + //=================================================================================================// + Vec2d ComplexShape::findClosestPoint(const Vec2d &input_pnt) + { + return multi_ploygen_.findClosestPoint(input_pnt); + } + //=================================================================================================// + bool ComplexShape::checkNotFar(const Vec2d &input_pnt, Real threshold) + { + return multi_ploygen_.checkContain(input_pnt) || checkNearSurface(input_pnt, threshold) ? true : false; + } + //=================================================================================================// + bool ComplexShape::checkNearSurface(const Vec2d &input_pnt, Real threshold) + { + return getMaxAbsoluteElement(input_pnt - multi_ploygen_.findClosestPoint(input_pnt)) < threshold ? true : false; + } + //=================================================================================================// + Real ComplexShape::findSignedDistance(const Vec2d &input_pnt) + { + Real distance_to_surface = (findClosestPoint(input_pnt) - input_pnt).norm(); + return checkContain(input_pnt) ? -distance_to_surface : distance_to_surface; + } + //=================================================================================================// + Vec2d ComplexShape::findNormalDirection(const Vec2d &input_pnt) + { + bool is_contain = checkContain(input_pnt); + Vecd displacement_to_surface = findClosestPoint(input_pnt) - input_pnt; + while (displacement_to_surface.norm() < Eps) + { + Vecd jittered = input_pnt; //jittering + for (int l = 0; l != input_pnt.size(); ++l) + jittered[l] = input_pnt[l] + (((Real)rand() / (RAND_MAX)) - 0.5) * 100.0 * Eps; + if (checkContain(jittered) == is_contain) + displacement_to_surface = findClosestPoint(jittered) - jittered; + } + Vecd direction_to_surface = displacement_to_surface.normalize(); + return is_contain ? direction_to_surface : -1.0 * direction_to_surface; + } + //=================================================================================================// + BoundingBox ComplexShape::findBounds() + { + return multi_ploygen_.findBounds(); + } + //=================================================================================================// + void ComplexShape::addAMultiPolygon(MultiPolygon &multi_polygon, ShapeBooleanOps op) + { + multi_ploygen_.addAMultiPolygon(multi_polygon, op); + } + //=================================================================================================// + void ComplexShape::addABoostMultiPoly(boost_multi_poly &boost_multi_poly, ShapeBooleanOps op) + { + multi_ploygen_.addABoostMultiPoly(boost_multi_poly, op); + } + //=================================================================================================// + void ComplexShape::addAPolygon(std::vector &points, ShapeBooleanOps op) + { + multi_ploygen_.addAPolygon(points, op); + } + //=================================================================================================// + void ComplexShape:: + addAPolygonFromFile(std::string file_path_name, ShapeBooleanOps op, Vec2d translation, Real scale_factor) + { + std::fstream dataFile(file_path_name); + Vecd temp_point; + std::vector coordinates; + double temp1 = 0.0, temp2 = 0.0; + if (dataFile.fail()) + { + std::cout << "File can not open.\n" + << std::endl; + ; + } + + while (!dataFile.fail() && !dataFile.eof()) + { + dataFile >> temp1 >> temp2; + temp_point[0] = temp1 * scale_factor + translation[0]; + temp_point[1] = temp2 * scale_factor + translation[1]; + coordinates.push_back(temp_point); + } + dataFile.close(); + + multi_ploygen_.addAPolygon(coordinates, op); + } + //=================================================================================================// + void ComplexShape::addACircle(Vec2d center, Real radius, int resolution, ShapeBooleanOps op) + { + multi_ploygen_.addACircle(center, radius, resolution, op); + } + //=================================================================================================// +} \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/geometries/geometry.h b/SPHINXsys/src/for_2D_build/geometries/geometry.h new file mode 100644 index 0000000000..33880357be --- /dev/null +++ b/SPHINXsys/src/for_2D_build/geometries/geometry.h @@ -0,0 +1,108 @@ +/** +* @file geometry.h +* @brief Here, we define the 2D geometric algortihms. they are based on the boost library. +* @details The idea is to define complex geometry based on shapes, usually +* multi-polygon using boost library. we propose only very simple combinaton +* that the region is composed of shapes without intersection. +* That is, the shapes are those contain each other or without overlap. +* This strict requirement suggests that complex shapes should be finished +* already in modeling using related binary operations before it is included. +* @author Luhui Han, Chi ZHang and Xiangyu Hu +*/ + +#ifndef GEOMETRY_2D_H +#define GEOMETRY_2D_H + +#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING + +//boost library +#include +#include +#include +#include +#include +#include + +#include "base_data_package.h" +#include "base_geometry.h" + +#include +#include +#include + +BOOST_GEOMETRY_REGISTER_BOOST_TUPLE_CS(cs::cartesian) + +using namespace boost::geometry; + +namespace SPH +{ + + /** + * @brief preclaimed classes. + */ + class Kernel; + + typedef model::polygon> boost_poly; + typedef model::multi_polygon boost_multi_poly; + + /** + * @class MultiPolygon + * @brief used to define a closed region + */ + class MultiPolygon : public Shape + { + public: + MultiPolygon() : Shape("MultiPolygon"){}; + boost_multi_poly &getBoostMultiPoly() { return multi_poly_; }; + bool checkContain(const Vec2d &pnt, bool BOUNDARY_INCLUDED = true); + Vec2d findClosestPoint(const Vec2d &input_pnt); + virtual BoundingBox findBounds() override; + + void addAMultiPolygon(MultiPolygon &multi_polygon, ShapeBooleanOps op); + void addABoostMultiPoly(boost_multi_poly &boost_multi_poly, ShapeBooleanOps op); + void addAPolygon(std::vector &points, ShapeBooleanOps op); + void addACircle(Vec2d center, Real radius, int resolution, ShapeBooleanOps op); + + protected: + boost_multi_poly multi_poly_; + boost_multi_poly MultiPolygonByBooleanOps(boost_multi_poly multi_poly_in, + boost_multi_poly multi_poly_op, + ShapeBooleanOps boolean_op); + }; + + /** + * @class ComplexShape + * @brief gives the final geomtrical definition of the SPHBody + */ + class ComplexShape : public Shape + { + Vec2d findClosestPoint(const Vec2d &input_pnt); + public: + /** Default constructor. */ + ComplexShape() : Shape("ComplexShape"), multi_ploygen_(){}; + ComplexShape(std::string complex_shape_name) : Shape(complex_shape_name), multi_ploygen_(){}; + virtual ~ComplexShape(){}; + virtual BoundingBox findBounds() override; + void addAMultiPolygon(MultiPolygon &multi_polygon, ShapeBooleanOps op); + void addABoostMultiPoly(boost_multi_poly &boost_multi_poly, ShapeBooleanOps op); + void addAPolygon(std::vector &points, ShapeBooleanOps op); + void addACircle(Vec2d center, Real radius, int resolution, ShapeBooleanOps op); + void addAPolygonFromFile(std::string file_path_name, + ShapeBooleanOps op, + Vec2d translation = Vecd(0), + Real scale_factor = 1.0); + + virtual bool checkContain(const Vec2d &input_pnt, bool BOUNDARY_INCLUDED = true); + virtual bool checkNotFar(const Vec2d &input_pnt, Real threshold); + virtual bool checkNearSurface(const Vec2d &input_pnt, Real threshold); + /** Signed distance is negative for point within the complex shape. */ + virtual Real findSignedDistance(const Vec2d &input_pnt); + /** Normal direction point toward outside of the complex shape. */ + virtual Vec2d findNormalDirection(const Vec2d &input_pnt); + + protected: + MultiPolygon multi_ploygen_; + }; +} + +#endif //GEOMETRY_2D_H \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/geometries/level_set_supplementary.cpp b/SPHINXsys/src/for_2D_build/geometries/level_set_supplementary.cpp new file mode 100644 index 0000000000..65896b3219 --- /dev/null +++ b/SPHINXsys/src/for_2D_build/geometries/level_set_supplementary.cpp @@ -0,0 +1,440 @@ +/** + * @file level_set_supplementary.cpp + * @author Luhui Han, Chi ZHang, Yongchuan Yu and Xiangyu Hu + */ + +#include "level_set.h" + +#include "particle_adaptation.h" +#include "base_kernel.h" +#include "base_particles.h" +#include "base_body.h" + + +//=================================================================================================// +namespace SPH { + //=============================================================================================// + void LevelSetDataPackage::initializeWithUniformData(Real level_set) + { + for (int i = 0; i != PackageSize(); ++i) + for (int j = 0; j != PackageSize(); ++j) { + phi_[i][j] = level_set; + n_[i][j] = Vecd(1.0); + kernel_weight_[i][j] = level_set < 0.0 ? 0 : 1.0; + kernel_gradient_[i][j] = Vecd(0.0); + near_interface_id_[i][j] = level_set < 0.0 ? -2 : 2; + } + } + //=================================================================================================// + void LevelSetDataPackage::initializeBasicData(ComplexShape& complex_shape) + { + for (int i = 0; i != PackageSize(); ++i) + for (int j = 0; j != PackageSize(); ++j) + { + Vec2d position = data_lower_bound_ + Vec2d((Real)i * grid_spacing_, (Real)j * grid_spacing_); + phi_[i][j] = complex_shape.findSignedDistance(position); + near_interface_id_[i][j] = phi_[i][j] < 0.0 ? -2 : 2; + } + } + //=================================================================================================// + void LevelSetDataPackage::computeKernelIntegrals(LevelSet& level_set) + { + for (int i = 0; i != PackageSize(); ++i) + for (int j = 0; j != PackageSize(); ++j) + { + Vec2d position = data_lower_bound_ + Vec2d((Real)i * grid_spacing_, (Real)j * grid_spacing_); + kernel_weight_[i][j] = level_set.computeKernelIntegral(position); + kernel_gradient_[i][j] = level_set.computeKernelGradientIntegral(position); + } + } + //=================================================================================================// + void LevelSetDataPackage::stepReinitialization() + { + for (int i = AddressBufferWidth(); i != OperationUpperBound(); ++i) + for (int j = AddressBufferWidth(); j != OperationUpperBound(); ++j) + { + //only reinitialize non cut cells + if (*near_interface_id_addrs_[i][j] != 0) + { + Real phi_0 = *phi_addrs_[i][j]; + Real s = phi_0 / sqrt(phi_0 * phi_0 + grid_spacing_ * grid_spacing_); + //x direction + Real dv_xp = (*phi_addrs_[i + 1][j] - phi_0); + Real dv_xn = (phi_0 - *phi_addrs_[i - 1][j]); + Real dv_x = dv_xp; + if (s * dv_xp >= 0.0 && s * dv_xn >= 0.0) dv_x = dv_xn; + if (s * dv_xp <= 0.0 && s * dv_xn <= 0.0) dv_x = dv_xp; + if (s * dv_xp > 0.0 && s * dv_xn < 0.0) dv_x = 0.0; + if (s * dv_xp < 0.0 && s * dv_xn > 0.0) + { + Real ss = s * (fabs(dv_xp) - fabs(dv_xn)) / (dv_xp - dv_xn); + if (ss > 0.0) dv_x = dv_xn; + } + //y direction + Real dv_yp = (*phi_addrs_[i][j + 1] - phi_0); + Real dv_yn = (phi_0 - *phi_addrs_[i][j - 1]); + Real dv_y = dv_yp; + if (s * dv_yp >= 0.0 && s * dv_yn >= 0.0) dv_y = dv_yn; + if (s * dv_yp <= 0.0 && s * dv_yn <= 0.0) dv_y = dv_yp; + if (s * dv_yp > 0.0 && s * dv_yn < 0.0) dv_y = 0.0; + if (s * dv_yp < 0.0 && s * dv_yn > 0.0) + { + Real ss = s * (fabs(dv_yp) - fabs(dv_yn)) / (dv_yp - dv_yn); + if (ss > 0.0) dv_y = dv_yn; + } + //time stepping + *phi_addrs_[i][j] -= 0.5 * s * (sqrt(dv_x * dv_x + dv_y * dv_y) - grid_spacing_); + } + } + } + //=================================================================================================// + void LevelSetDataPackage::markNearInterface() + { + Real small_shift = 0.75 * grid_spacing_; + //corner averages, note that the first row and first column are not used + PackageTemporaryData corner_averages; + for (int i = 1; i != AddressSize(); ++i) + for (int j = 1; j != AddressSize(); ++j) + { + corner_averages[i][j] = CornerAverage(phi_addrs_, Veci(i, j), Veci(-1, -1)); + } + + for (int i = AddressBufferWidth(); i != OperationUpperBound(); ++i) + for (int j = AddressBufferWidth(); j != OperationUpperBound(); ++j) + { + //first assume far cells + Real phi_0 = *phi_addrs_[i][j]; + int near_interface_id = phi_0 > 0.0 ? 2 : -2; + + Real phi_average_0 = corner_averages[i][j]; + //find outer cut cells by comparing the sign of corner averages + for (int l = 0; l != 2; ++l) + for (int m = 0; m != 2; ++m) + { + int index_x = i + l; + int index_y = j + m; + Real phi_average = corner_averages[index_x][index_y]; + if ((phi_average_0 - small_shift) * (phi_average - small_shift) < 0.0) near_interface_id = 1; + if ((phi_average_0 + small_shift) * (phi_average + small_shift) < 0.0) near_interface_id = -1; + } + + //find zero cut cells by comparing the sign of corner averages + for (int l = 0; l != 2; ++l) + for (int m = 0; m != 2; ++m) + { + int index_x = i + l; + int index_y = j + m; + Real phi_average = corner_averages[index_x][index_y]; + if (phi_average_0 * phi_average < 0.0) near_interface_id = 0; + } + + //find cells between cut cells + if (fabs(phi_0) < small_shift && abs(near_interface_id) != 1) near_interface_id = 0; + + //assign this to package + *near_interface_id_addrs_[i][j] = near_interface_id; + } + } + //=================================================================================================// + bool LevelSet::isWithinCorePackage(Vecd position) + { + Vecu cell_index = CellIndexFromPosition(position); + return data_pkg_addrs_[cell_index[0]][cell_index[1]]->is_core_pkg_; + } + //=============================================================================================// + void LevelSet::initializeDataInACell(const Vecu &cell_index, Real dt) + { + int i = (int)cell_index[0]; + int j = (int)cell_index[1]; + + Vecd cell_position = CellPositionFromIndex(cell_index); + Real signed_distance = complex_shape_.findSignedDistance(cell_position); + Vecd normal_direction = complex_shape_.findNormalDirection(cell_position); + Real measure = getMaxAbsoluteElement(normal_direction * signed_distance); + if (measure < grid_spacing_) { + mutex_my_pool.lock(); + LevelSetDataPackage* new_data_pkg = data_pkg_pool_.malloc(); + mutex_my_pool.unlock(); + Vecd pkg_lower_bound = GridPositionFromCellPosition(cell_position); + new_data_pkg->initializePackageGeometry(pkg_lower_bound, data_spacing_); + new_data_pkg->initializeBasicData(complex_shape_); + core_data_pkgs_.push_back(new_data_pkg); + new_data_pkg->pkg_index_ = Vecu(i, j); + new_data_pkg->is_core_pkg_ = true; + data_pkg_addrs_[i][j] = new_data_pkg; + } + else { + data_pkg_addrs_[i][j] = complex_shape_.checkContain(cell_position) ? + singular_data_pkgs_addrs[0] : singular_data_pkgs_addrs[1]; + } + } + //=============================================================================================// + void LevelSet::tagACellIsInnerPackage(const Vecu &cell_index, Real dt) + { + int i = (int)cell_index[0]; + int j = (int)cell_index[1]; + + bool is_inner_pkg = false; + for (int l = SMAX(i - 1, 0); l <= SMIN(i + 1, int(number_of_cells_[0]) - 1); ++l) + for (int m = SMAX(j - 1, 0); m <= SMIN(j + 1, int(number_of_cells_[1]) - 1); ++m) + if (data_pkg_addrs_[l][m]->is_core_pkg_) is_inner_pkg = true; + + if (is_inner_pkg) + { + LevelSetDataPackage* current_data_pkg = data_pkg_addrs_[i][j]; + if (current_data_pkg->is_core_pkg_) { + current_data_pkg->is_inner_pkg_ = true; + inner_data_pkgs_.push_back(current_data_pkg); + } + else { + mutex_my_pool.lock(); + LevelSetDataPackage* new_data_pkg = data_pkg_pool_.malloc(); + mutex_my_pool.unlock(); + Vecd cell_position = CellPositionFromIndex(cell_index); + Vecd pkg_lower_bound = GridPositionFromCellPosition(cell_position); + new_data_pkg->initializePackageGeometry(pkg_lower_bound, data_spacing_); + new_data_pkg->initializeBasicData(complex_shape_); + new_data_pkg->pkg_index_ = Vecu(i, j); + new_data_pkg->is_inner_pkg_ = true; + inner_data_pkgs_.push_back(new_data_pkg); + data_pkg_addrs_[i][j] = new_data_pkg; + } + } + } + //=================================================================================================// + void LevelSet::redistanceInterfaceForAPackage(LevelSetDataPackage* core_data_pkg, Real dt) + { + int l = (int)core_data_pkg->pkg_index_[0]; + int m = (int)core_data_pkg->pkg_index_[1]; + + for (int i = pkg_addrs_buffer_; i != pkg_operations_; ++i) + for (int j = pkg_addrs_buffer_; j != pkg_operations_; ++j) + { + int near_interface_id = *core_data_pkg->near_interface_id_addrs_[i][j]; + if (near_interface_id == 0) + { + bool positive_band = false; + bool negative_band = false; + for (int s = -1; s < 2; ++s) + for (int t = -1; t < 2; ++t) + { + int neighbor_near_interface_id = + *core_data_pkg->near_interface_id_addrs_[i + s][j + t]; + if (neighbor_near_interface_id >= 1) positive_band = true; + if (neighbor_near_interface_id <= -1) negative_band = true; + } + if (positive_band == false) + { + Real min_distance_p = 5.0 * data_spacing_; + for (int x = -4; x != 5; ++x) + for (int y = -4; y != 5; ++y) + { + std::pair x_pair = CellShiftAndDataIndex(i + x); + std::pair y_pair = CellShiftAndDataIndex(j + y); + LevelSetDataPackage* neighbor_pkg + = data_pkg_addrs_[l + x_pair.first][m + y_pair.first]; + int neighbor_near_interface_id + = neighbor_pkg->near_interface_id_[x_pair.second][y_pair.second]; + if (neighbor_near_interface_id >= 1) + { + Real phi_p_ = neighbor_pkg->phi_[x_pair.second][y_pair.second]; + Vecd norm_to_face = neighbor_pkg->n_[x_pair.second][y_pair.second]; + min_distance_p = SMIN(min_distance_p, (Vecd((Real)x, (Real)y) * data_spacing_ + phi_p_ * norm_to_face).norm()); + } + } + *core_data_pkg->phi_addrs_[i][j] = -min_distance_p; + // this immediate switch of near interface id + // does not intervenning with the identification of unresolved interface + // based on the assumption that positive false_and negative bands are not close to each other + *core_data_pkg->near_interface_id_addrs_[i][j] = -1; + } + if (negative_band == false) + { + Real min_distance_n = 5.0 * data_spacing_; + for (int x = -4; x != 5; ++x) + for (int y = -4; y != 5; ++y) + { + std::pair x_pair = CellShiftAndDataIndex(i + x); + std::pair y_pair = CellShiftAndDataIndex(j + y); + LevelSetDataPackage* neighbor_pkg + = data_pkg_addrs_[l + x_pair.first][m + y_pair.first]; + int neighbor_near_interface_id + = neighbor_pkg->near_interface_id_[x_pair.second][y_pair.second]; + if (neighbor_near_interface_id <= -1) + { + Real phi_n_ = neighbor_pkg->phi_[x_pair.second][y_pair.second]; + Vecd norm_to_face = neighbor_pkg->n_[x_pair.second][y_pair.second]; + min_distance_n = SMIN(min_distance_n, (Vecd((Real)x, (Real)y) * data_spacing_ - phi_n_ * norm_to_face).norm()); + } + } + *core_data_pkg->phi_addrs_[i][j] = min_distance_n; + // this immediate switch of near interface id + // does not intervenning with the identification of unresolved interface + // based on the assumption that positive false_and negative bands are not close to each other + *core_data_pkg->near_interface_id_addrs_[i][j] = 1; + } + } + } + } + //=============================================================================================// + void LevelSet::writeMeshFieldToPlt(std::ofstream& output_file) + { + Vecu number_of_operation = global_mesh_.NumberOfGridPoints(); + + output_file << "\n"; + output_file << "title='View'" << "\n"; + output_file << "variables= " << "x, " << "y, " << "phi, " << "n_x, " << "n_y " << "near_interface_id "; + output_file << "kernel_weight, " << "kernel_gradient_x, " << "kernel_gradient_y " << "\n"; + output_file << "zone i=" << number_of_operation[0] << " j=" << number_of_operation[1] << " k=" << 1 + << " DATAPACKING=BLOCK SOLUTIONTIME=" << 0 << "\n"; + + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + Vecd data_position = global_mesh_.GridPositionFromIndex(Vecu(i, j)); + output_file << data_position[0] << " "; + } + output_file << " \n"; + } + + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + Vecd data_position = global_mesh_.GridPositionFromIndex(Vecu(i, j)); + output_file << data_position[1]<< " "; + } + output_file << " \n"; + } + + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + output_file << DataValueFromGlobalIndex, + &LevelSetDataPackage::phi_>(Vecu(i, j)) << " "; + } + output_file << " \n"; + } + + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + output_file << DataValueFromGlobalIndex, + &LevelSetDataPackage::n_>(Vecu(i, j))[0] << " "; + } + output_file << " \n"; + } + + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + output_file << DataValueFromGlobalIndex, + &LevelSetDataPackage::n_>(Vecu(i, j))[1] << " "; + } + output_file << " \n"; + } + + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + output_file << DataValueFromGlobalIndex, + &LevelSetDataPackage::near_interface_id_>(Vecu(i, j)) << " "; + } + output_file << " \n"; + } + + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + output_file << DataValueFromGlobalIndex, + &LevelSetDataPackage::kernel_weight_>(Vecu(i, j)) << " "; + } + output_file << " \n"; + } + + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + output_file << DataValueFromGlobalIndex, + &LevelSetDataPackage::kernel_gradient_>(Vecu(i, j))[0] << " "; + } + output_file << " \n"; + } + + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + output_file << DataValueFromGlobalIndex, + &LevelSetDataPackage::kernel_gradient_>(Vecu(i, j))[1] << " "; + } + output_file << " \n"; + } + } + //=============================================================================================// + Real LevelSet::computeKernelIntegral(const Vecd& position) + { + Real phi = probeSignedDistance(position); + Real cutoff_radius = kernel_.CutOffRadius(global_h_ratio_); + Real threshold = cutoff_radius + data_spacing_; //consider that interface's half width is the data spacing + + Real integral(0.0); + if (fabs(phi) < threshold) + { + Vecu global_index_ = global_mesh_.CellIndexFromPosition(position); + for (int i = -3; i != 4; ++i) + for (int j = -3; j != 4; ++j) + { + Vecu neighbor_index = Vecu(global_index_[0] + i, global_index_[1] + j); + Real phi_neighbor = DataValueFromGlobalIndex, + &LevelSetDataPackage::phi_>(neighbor_index) - 0.5 * data_spacing_;; + if (phi_neighbor > -data_spacing_) { + Vecd displacement = position - global_mesh_.GridPositionFromIndex(neighbor_index); + Real distance = displacement.norm(); + if (distance < cutoff_radius) + integral += kernel_.W(global_h_ratio_, distance, displacement) + * computeHeaviside(phi_neighbor, data_spacing_); + } + } + } + return phi > threshold ? 1.0 : integral * data_spacing_* data_spacing_; + } + //=============================================================================================// + Vecd LevelSet::computeKernelGradientIntegral(const Vecd& position) + { + Real phi = probeSignedDistance(position); + Real cutoff_radius = kernel_.CutOffRadius(global_h_ratio_); + Real threshold = cutoff_radius + data_spacing_; + + Vecd integral(0.0); + if (fabs(phi) < threshold) + { + Vecu global_index_ = global_mesh_.CellIndexFromPosition(position); + for (int i = -3; i != 4; ++i) + for (int j = -3; j != 4; ++j) + { + Vecu neighbor_index = Vecu(global_index_[0] + i, global_index_[1] + j); + Real phi_neighbor = DataValueFromGlobalIndex, + &LevelSetDataPackage::phi_>(neighbor_index); + if (phi_neighbor > -data_spacing_) { + Vecd displacement = position - global_mesh_.GridPositionFromIndex(neighbor_index); + Real distance = displacement.norm(); + if (distance < cutoff_radius) + integral += kernel_.dW(global_h_ratio_, distance, displacement) + * computeHeaviside(phi_neighbor, data_spacing_) * displacement / (distance + TinyReal); + } + } + } + + return integral* data_spacing_ * data_spacing_; + } + //=============================================================================================// +} +//=============================================================================================// diff --git a/SPHINXsys/src/for_2D_build/meshes/CMakeLists.txt b/SPHINXsys/src/for_2D_build/meshes/CMakeLists.txt new file mode 100644 index 0000000000..9ef366a60f --- /dev/null +++ b/SPHINXsys/src/for_2D_build/meshes/CMakeLists.txt @@ -0,0 +1,7 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/meshes/base_mesh_supplementary.cpp b/SPHINXsys/src/for_2D_build/meshes/base_mesh_supplementary.cpp new file mode 100644 index 0000000000..c94ffa7dd6 --- /dev/null +++ b/SPHINXsys/src/for_2D_build/meshes/base_mesh_supplementary.cpp @@ -0,0 +1,49 @@ +/** + * @file base_mesh_supplementary.cpp + * @author Luhui Han, Chi ZHang, Yongchuan Yu and Xiangyu Hu + */ + +#include "base_mesh.h" + +//=================================================================================================// +namespace SPH { + //=============================================================================================// + void MeshIterator(const Vecu &index_begin, const Vecu &index_end, MeshFunctor& mesh_functor, Real dt) + { + for (size_t i = index_begin[0]; i != index_end[0]; ++i) + for (size_t j = index_begin[1]; j != index_end[1]; ++j) { + mesh_functor(Vecu(i, j), dt); + } + } + //=============================================================================================// + void MeshIterator_parallel(const Vecu &index_begin, const Vecu &index_end, MeshFunctor& mesh_functor, Real dt) + { + parallel_for(blocked_range2d + (index_begin[0], index_end[0], index_begin[1], index_end[1]), + [&](const blocked_range2d& r) { + for (size_t i = r.rows().begin(); i != r.rows().end(); ++i) + for (size_t j = r.cols().begin(); j != r.cols().end(); ++j) + { + mesh_functor(Vecu(i, j), dt); + } + }, ap); + } + //=============================================================================================// + Vecu BaseMesh::transfer1DtoMeshIndex(const Vecu &number_of_grid_points, size_t i) + { + size_t row_size = number_of_grid_points[1]; + size_t column = i / row_size; + return Vec2u(column, i - column * row_size); + } + //=============================================================================================// + size_t BaseMesh::transferMeshIndexTo1D(const Vecu &number_of_grid_points, const Vecu &grid_index) + { + return grid_index[0] * number_of_grid_points[1] + grid_index[1]; + } + //=============================================================================================// + size_t BaseMesh::transferMeshIndexToMortonOrder(const Vecu &grid_index) + { + return MortonCode(grid_index[0]) | (MortonCode(grid_index[1]) << 1); + } +} +//=============================================================================================// diff --git a/SPHINXsys/src/for_2D_build/meshes/cell_linked_list.hpp b/SPHINXsys/src/for_2D_build/meshes/cell_linked_list.hpp new file mode 100644 index 0000000000..92fb6b59e0 --- /dev/null +++ b/SPHINXsys/src/for_2D_build/meshes/cell_linked_list.hpp @@ -0,0 +1,47 @@ +/** + * @file body_relation.hpp + * @brief Here, Functions belong to BaseBody, RealBody and FictitiousBody are given. + * @author hi ZHang and Xiangyu Hu + */ + +#pragma once + +#include "base_particles.h" +#include "cell_linked_list.h" + +namespace SPH +{ + //=================================================================================================// + template + void CellLinkedList::searchNeighborsByParticles(size_t total_real_particles, BaseParticles& source_particles, + ParticleConfiguration& particle_configuration, GetParticleIndex& get_particle_index, + GetSearchDepth& get_search_depth, GetNeighborRelation& get_neighbor_relation) + { + parallel_for(blocked_range(0, total_real_particles), + [&](const blocked_range& r) { + StdLargeVec& pos_n = source_particles.pos_n_; + for (size_t num = r.begin(); num != r.end(); ++num) { + size_t index_i = get_particle_index(num); + Vecd& particle_position = pos_n[index_i]; + int search_depth = get_search_depth(index_i); + Vecu target_cell_index = CellIndexFromPosition(particle_position); + int i = (int)target_cell_index[0]; + int j = (int)target_cell_index[1]; + + Neighborhood& neighborhood = particle_configuration[index_i]; + for (int l = SMAX(i - search_depth, 0); l <= SMIN(i + search_depth, int(number_of_cells_[0]) - 1); ++l) + for (int m = SMAX(j - search_depth, 0); m <= SMIN(j + search_depth, int(number_of_cells_[1]) - 1); ++m) + { + ListDataVector& target_particles = cell_linked_lists_[l][m].cell_list_data_; + for (const ListData& list_data : target_particles) + { + //displacement pointing from neighboring particle to origin particle + Vecd displacement = particle_position - list_data.second; + get_neighbor_relation(neighborhood, displacement, index_i, list_data.first); + } + } + } + }, ap); + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/for_2D_build/meshes/cell_linked_list_supplementary.cpp b/SPHINXsys/src/for_2D_build/meshes/cell_linked_list_supplementary.cpp new file mode 100644 index 0000000000..14eaa921d1 --- /dev/null +++ b/SPHINXsys/src/for_2D_build/meshes/cell_linked_list_supplementary.cpp @@ -0,0 +1,267 @@ +/** + * @file cell_linked_list.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#include "cell_linked_list.h" +#include "base_kernel.h" +#include "base_body.h" +#include "base_particles.h" +#include "neighbor_relation.h" + +namespace SPH +{ + //=================================================================================================// + CellList::CellList() + { + concurrent_particle_indexes_.reserve(12); + } + //=================================================================================================// + void CellLinkedList::allocateMeshDataMatrix() + { + Allocate2dArray(cell_linked_lists_, number_of_cells_); + } + //=================================================================================================// + void CellLinkedList::deleteMeshDataMatrix() + { + Delete2dArray(cell_linked_lists_, number_of_cells_); + } + //=================================================================================================// + void CellLinkedList::clearCellLists() + { + parallel_for( + blocked_range2d(0, number_of_cells_[0], 0, number_of_cells_[1]), + [&](const blocked_range2d &r) + { + for (size_t i = r.rows().begin(); i != r.rows().end(); ++i) + for (size_t j = r.cols().begin(); j != r.cols().end(); ++j) + { + cell_linked_lists_[i][j].concurrent_particle_indexes_.clear(); + cell_linked_lists_[i][j].real_particle_indexes_.clear(); + } + }, + ap); + } + //=================================================================================================// + void CellLinkedList::UpdateCellListData() + { + StdLargeVec &pos_n = base_particles_->pos_n_; + parallel_for( + blocked_range2d(0, number_of_cells_[0], 0, number_of_cells_[1]), + [&](const blocked_range2d &r) + { + for (size_t i = r.rows().begin(); i != r.rows().end(); ++i) + for (size_t j = r.cols().begin(); j != r.cols().end(); ++j) + { + CellList &cell_list = cell_linked_lists_[i][j]; + cell_list.cell_list_data_.clear(); + for (size_t s = 0; s != cell_list.concurrent_particle_indexes_.size(); ++s) + { + size_t particle_index = cell_list.concurrent_particle_indexes_[s]; + cell_list.cell_list_data_.emplace_back(std::make_pair(particle_index, pos_n[particle_index])); + } + } + }, + ap); + } + //=================================================================================================// + void CellLinkedList::updateSplitCellLists(SplitCellLists &split_cell_lists) + { + //clear the data + clearSplitCellLists(split_cell_lists); + + parallel_for( + blocked_range2d(0, number_of_cells_[0], 0, number_of_cells_[1]), + [&](const blocked_range2d &r) + { + for (size_t i = r.rows().begin(); i != r.rows().end(); ++i) + for (size_t j = r.cols().begin(); j != r.cols().end(); ++j) + { + CellList &cell_list = cell_linked_lists_[i][j]; + size_t real_particles_in_cell = cell_list.concurrent_particle_indexes_.size(); + if (real_particles_in_cell != 0) + { + for (size_t s = 0; s != real_particles_in_cell; ++s) + cell_list.real_particle_indexes_.push_back(cell_list.concurrent_particle_indexes_[s]); + split_cell_lists[transferMeshIndexTo1D(Vecu(3), Vecu(i % 3, j % 3))].push_back(&cell_linked_lists_[i][j]); + } + } + }, + ap); + } + //=================================================================================================// + void CellLinkedList ::insertACellLinkedParticleIndex(size_t particle_index, const Vecd &particle_position) + { + Vecu cellpos = CellIndexFromPosition(particle_position); + cell_linked_lists_[cellpos[0]][cellpos[1]].concurrent_particle_indexes_.emplace_back(particle_index); + } + //=================================================================================================// + void CellLinkedList ::InsertACellLinkedListDataEntry(size_t particle_index, const Vecd &particle_position) + { + Vecu cellpos = CellIndexFromPosition(particle_position); + cell_linked_lists_[cellpos[0]][cellpos[1]].cell_list_data_.emplace_back(std::make_pair(particle_index, particle_position)); + } + //=================================================================================================// + ListData CellLinkedList::findNearestListDataEntry(const Vecd &position) + { + Real min_distance = Infinity; + ListData nearest_entry = std::make_pair(MaxSize_t, Vecd(Infinity)); + + Vecu cell_location = CellIndexFromPosition(position); + int i = (int)cell_location[0]; + int j = (int)cell_location[1]; + + for (int l = SMAX(i - 1, 0); l <= SMIN(i + 1, int(number_of_cells_[0]) - 1); ++l) + { + for (int m = SMAX(j - 1, 0); m <= SMIN(j + 1, int(number_of_cells_[1]) - 1); ++m) + { + ListDataVector &target_particles = cell_linked_lists_[l][m].cell_list_data_; + for (const ListData &list_data : target_particles) + { + Real distance = (position - list_data.second).norm(); + if (distance < min_distance) + { + min_distance = distance; + nearest_entry = list_data; + } + } + } + } + return nearest_entry; + } + //=================================================================================================// + void CellLinkedList:: + tagBodyPartByCell(CellLists &cell_lists, std::function &check_included) + { + for (int i = 0; i < (int)number_of_cells_[0]; ++i) + for (int j = 0; j < (int)number_of_cells_[1]; ++j) + { + bool is_included = false; + for (int k = SMAX(i - 1, 0); k <= SMIN(i + 1, int(number_of_cells_[0]) - 1); ++k) + for (int l = SMAX(j - 1, 0); l <= SMIN(j + 1, int(number_of_cells_[1]) - 1); ++l) + { + if (check_included(CellPositionFromIndex(Vecu(k, l)), grid_spacing_)) + { + is_included = true; + } + } + if (is_included == true) + cell_lists.push_back(&cell_linked_lists_[i][j]); + } + } + //=================================================================================================// + void CellLinkedList:: + tagBodyDomainBoundingCells(StdVec &cell_lists, BoundingBox &body_domain_bounds, int axis) + { + int second_axis = SecondAxis(axis); + Vecu body_lower_bound_cell_ = CellIndexFromPosition(body_domain_bounds.first); + Vecu body_upper_bound_cell_ = CellIndexFromPosition(body_domain_bounds.second); + + //lower bound cells + for (size_t j = SMAX(int(body_lower_bound_cell_[second_axis]) - 1, 0); + j <= (size_t)SMIN(int(body_upper_bound_cell_[second_axis] + 1), int(number_of_cells_[second_axis] - 1)); ++j) + for (size_t i = SMAX(int(body_lower_bound_cell_[axis]) - 1, 0); + i <= (size_t)SMIN(int(body_lower_bound_cell_[axis] + 1), int(number_of_cells_[axis] - 1)); ++i) + { + Vecu cell_position(0); + cell_position[axis] = i; + cell_position[second_axis] = j; + cell_lists[0].push_back(&cell_linked_lists_[cell_position[0]][cell_position[1]]); + } + + //upper bound cells + for (size_t j = SMAX(int(body_lower_bound_cell_[second_axis]) - 1, 0); + j <= (size_t)SMIN(int(body_upper_bound_cell_[second_axis] + 1), int(number_of_cells_[second_axis] - 1)); ++j) + for (size_t i = SMAX(int(body_upper_bound_cell_[axis]) - 1, 0); + i <= (size_t)SMIN(int(body_upper_bound_cell_[axis] + 1), int(number_of_cells_[axis] - 1)); ++i) + { + Vecu cell_position(0); + cell_position[axis] = i; + cell_position[second_axis] = j; + cell_lists[1].push_back(&cell_linked_lists_[cell_position[0]][cell_position[1]]); + } + } + //=================================================================================================// + void CellLinkedList:: + tagMirrorBoundingCells(CellLists &cell_lists, BoundingBox &body_domain_bounds, int axis, bool positive) + { + int second_axis = SecondAxis(axis); + Vecu body_lower_bound_cell_ = CellIndexFromPosition(body_domain_bounds.first); + Vecu body_upper_bound_cell_ = CellIndexFromPosition(body_domain_bounds.second); + + if (positive) + { + //upper bound cells + for (size_t j = SMAX(int(body_lower_bound_cell_[second_axis]) - 1, 0); + j < (size_t)SMIN(int(body_upper_bound_cell_[second_axis] + 2), int(number_of_cells_[second_axis])); ++j) + for (size_t i = SMAX(int(body_upper_bound_cell_[axis]) - 1, 0); + i <= (size_t)SMIN(int(body_upper_bound_cell_[axis] + 1), int(number_of_cells_[axis] - 1)); ++i) + { + Vecu cell_position(0); + cell_position[axis] = i; + cell_position[second_axis] = j; + cell_lists.push_back(&cell_linked_lists_[cell_position[0]][cell_position[1]]); + } + } + else + { + //lower bound cells + for (size_t j = SMAX(int(body_lower_bound_cell_[second_axis]) - 1, 0); + j < (size_t)SMIN(int(body_upper_bound_cell_[second_axis] + 2), int(number_of_cells_[second_axis])); ++j) + for (size_t i = SMAX(int(body_lower_bound_cell_[axis]) - 1, 0); + i <= (size_t)SMIN(int(body_lower_bound_cell_[axis] + 1), int(number_of_cells_[axis] - 1)); ++i) + { + Vecu cell_position(0); + cell_position[axis] = i; + cell_position[second_axis] = j; + cell_lists.push_back(&cell_linked_lists_[cell_position[0]][cell_position[1]]); + } + } + } + //=============================================================================================// + void CellLinkedList::writeMeshFieldToPlt(std::ofstream &output_file) + { + Vecu number_of_operation = number_of_cells_; + + output_file << "\n"; + output_file << "title='View'" + << "\n"; + output_file << "variables= " + << "x, " + << "y, " + << "particles_in_cell " + << "\n"; + output_file << "zone i=" << number_of_operation[0] << " j=" << number_of_operation[1] << " k=" << 1 + << " DATAPACKING=BLOCK SOLUTIONTIME=" << 0 << "\n"; + + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + Vecd data_position = CellPositionFromIndex(Vecu(i, j)); + output_file << data_position[0] << " "; + } + output_file << " \n"; + } + + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + Vecd data_position = CellPositionFromIndex(Vecu(i, j)); + output_file << data_position[1] << " "; + } + output_file << " \n"; + } + + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + output_file << cell_linked_lists_[i][j].concurrent_particle_indexes_.size() << " "; + } + output_file << " \n"; + } + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/for_2D_build/meshes/mesh_with_data_packages.hpp b/SPHINXsys/src/for_2D_build/meshes/mesh_with_data_packages.hpp new file mode 100644 index 0000000000..0937e473d6 --- /dev/null +++ b/SPHINXsys/src/for_2D_build/meshes/mesh_with_data_packages.hpp @@ -0,0 +1,168 @@ +/** +* @file base_mesh.hpp +* @brief This is the implementation of the template function and class for base mesh +* @author Chi ZHang and Xiangyu Hu +*/ + +#ifndef MESH_WITH_DATA_PACKAGES_2D_HPP +#define MESH_WITH_DATA_PACKAGES_2D_HPP + +#include "mesh_with_data_packages.h" + +//=================================================================================================// +namespace SPH { + //=================================================================================================// + template + template + DataType BaseDataPackage + ::probeDataPackage(PackageDataAddress& pkg_data_addrs, const Vecd& position) + { + Vecu grid_idx = CellIndexFromPosition(position); + Vecd grid_pos = GridPositionFromIndex(grid_idx); + Vecd alpha = (position - grid_pos) / grid_spacing_; + Vecd beta = Vec2d(1.0) - alpha; + + DataType bilinear + = *pkg_data_addrs[grid_idx[0]][grid_idx[1]] * beta[0] * beta[1] + + *pkg_data_addrs[grid_idx[0] + 1][grid_idx[1]] * alpha[0] * beta[1] + + *pkg_data_addrs[grid_idx[0]][grid_idx[1] + 1] * beta[0] * alpha[1] + + *pkg_data_addrs[grid_idx[0] + 1][grid_idx[1] + 1] * alpha[0] * alpha[1]; + + return bilinear; + } + //=================================================================================================// + template + template + void BaseDataPackage:: + computeGradient(PackageDataAddress& in_pkg_data_addrs, + PackageDataAddress out_pkg_data_addrs, Real dt) + { + for (int i = 1; i != PKG_SIZE + 1; ++i) + for (int j = 1; j != PKG_SIZE + 1; ++j) + { + Real dphidx = (*in_pkg_data_addrs[i + 1][j] - *in_pkg_data_addrs[i - 1][j]); + Real dphidy = (*in_pkg_data_addrs[i][j + 1] - *in_pkg_data_addrs[i][j - 1]); + *out_pkg_data_addrs[i][j] = Vecd(dphidx, dphidy); + } + } + //=================================================================================================// + template + template + void BaseDataPackage:: + computeNormalizedGradient(PackageDataAddress& in_pkg_data_addrs, + PackageDataAddress out_pkg_data_addrs, Real dt) + { + for (int i = 1; i != PKG_SIZE + 1; ++i) + for (int j = 1; j != PKG_SIZE + 1; ++j) + { + Real dphidx = (*in_pkg_data_addrs[i + 1][j] - *in_pkg_data_addrs[i - 1][j]); + Real dphidy = (*in_pkg_data_addrs[i][j + 1] - *in_pkg_data_addrs[i][j - 1]); + Vecd normal = Vecd(dphidx, dphidy); + *out_pkg_data_addrs[i][j] = normal / (normal.norm() + TinyReal); + } + } + //=================================================================================================// + template + template + void BaseDataPackage:: + initializePackageDataAddress(PackageData& pkg_data, + PackageDataAddress& pkg_data_addrs) + { + for (int i = 0; i != ADDRS_SIZE; ++i) + for (int j = 0; j != ADDRS_SIZE; ++j) + { + pkg_data_addrs[i][j] = &pkg_data[0][0]; + } + } + //=================================================================================================// + template + template + DataType BaseDataPackage:: + CornerAverage(PackageDataAddress& pkg_data_addrs, Veci addrs_index, Veci corner_direction) + { + DataType average(0); + for (int i = 0; i != 2; ++i) + for (int j = 0; j != 2; ++j) + { + int x_index = addrs_index[0] + i * corner_direction[0]; + int y_index = addrs_index[1] + j * corner_direction[1]; + average += *pkg_data_addrs[x_index][y_index]; + } + return average * 0.25; + } + //=================================================================================================// + template + template + void BaseDataPackage:: + assignPackageDataAddress(PackageDataAddress& pkg_data_addrs, Vecu& addrs_index, + PackageData& pkg_data, Vecu& data_index) + { + pkg_data_addrs[addrs_index[0]][addrs_index[1]] = &pkg_data[data_index[0]][data_index[1]]; + } + //=================================================================================================// + template + template + DataType MeshWithDataPackages:: + DataValueFromGlobalIndex(Vecu global_grid_index) + { + Vecu pkg_index_(0); + Vecu local_data_index(0); + for (int n = 0; n != 2; n++) + { + size_t cell_index_in_this_direction = global_grid_index[n] / pkg_size_; + pkg_index_[n] = cell_index_in_this_direction; + local_data_index[n] = global_grid_index[n] - cell_index_in_this_direction * pkg_size_; + } + PackageDataType& data = data_pkg_addrs_[pkg_index_[0]][pkg_index_[1]]->*MemPtr; + return data[local_data_index[0]][local_data_index[1]]; + } + //=================================================================================================// + template + void MeshWithDataPackages::initializePackageAddressesInACell(Vecu cell_index) + { + int i = (int)cell_index[0]; + int j = (int)cell_index[1]; + + DataPackageType* data_pkg = data_pkg_addrs_[i][j]; + if (data_pkg->is_inner_pkg_) { + for (int l = 0; l != pkg_addrs_size_; ++l) + for (int m = 0; m != pkg_addrs_size_; ++m) { + std::pair x_pair = CellShiftAndDataIndex(l); + std::pair y_pair = CellShiftAndDataIndex(m); + data_pkg->assignAllPackageDataAddress(Vecu(l, m), + data_pkg_addrs_[i + x_pair.first][j + y_pair.first], + Vecu(x_pair.second, y_pair.second)); + } + } + } + //=================================================================================================// + template + void MeshWithDataPackages::allocateMeshDataMatrix() + { + Allocate2dArray(data_pkg_addrs_, number_of_cells_); + } + //=================================================================================================// + template + void MeshWithDataPackages::deleteMeshDataMatrix() + { + Delete2dArray(data_pkg_addrs_, number_of_cells_); + } + //=================================================================================================// + template + template + DataType MeshWithDataPackages::probeMesh(const Vecd& position) + { + Vecu grid_index =CellIndexFromPosition(position); + size_t i = grid_index[0]; + size_t j = grid_index[1]; + + DataPackageType* data_pkg = data_pkg_addrs_[i][j]; + PackageDataAddressType& pkg_data_addrs = data_pkg->*MemPtr; + return data_pkg->is_inner_pkg_ ? + data_pkg->DataPackageType::template probeDataPackage(pkg_data_addrs, position) + : *pkg_data_addrs[0][0]; + } + //=================================================================================================// +} +//=================================================================================================// +#endif //MESH_WITH_DATA_PACKAGES_2D_HPP \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/particle_dynamics/CMakeLists.txt b/SPHINXsys/src/for_2D_build/particle_dynamics/CMakeLists.txt new file mode 100644 index 0000000000..9ef366a60f --- /dev/null +++ b/SPHINXsys/src/for_2D_build/particle_dynamics/CMakeLists.txt @@ -0,0 +1,7 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/particle_dynamics/solid_dynamics/CMakeLists.txt b/SPHINXsys/src/for_2D_build/particle_dynamics/solid_dynamics/CMakeLists.txt new file mode 100644 index 0000000000..9d7cea4b86 --- /dev/null +++ b/SPHINXsys/src/for_2D_build/particle_dynamics/solid_dynamics/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) + diff --git a/SPHINXsys/src/for_2D_build/particle_dynamics/solid_dynamics/solid_dynamics_supplementary.cpp b/SPHINXsys/src/for_2D_build/particle_dynamics/solid_dynamics/solid_dynamics_supplementary.cpp new file mode 100644 index 0000000000..4d1d1bd518 --- /dev/null +++ b/SPHINXsys/src/for_2D_build/particle_dynamics/solid_dynamics/solid_dynamics_supplementary.cpp @@ -0,0 +1,75 @@ +/** + * @file solid_dynamics_supplementary.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#include "all_solid_dynamics.h" +#include "solid_body.h" +#include "solid_particles.h" +#include "neighbor_relation.h" +#include "base_kernel.h" +#include "base_data_package.h" +#include "elastic_solid.h" +#include "external_force.h" +#include "cell_linked_list.h" +#include "fluid_particles.h" +#include "weakly_compressible_fluid.h" + +using namespace SimTK; + +namespace SPH +{ + namespace solid_dynamics + { + //=========================================================================================// + void UpdateElasticNormalDirection::Update(size_t index_i, Real dt) + { + Matd& F = F_[index_i]; + //deformation tensor in 2D + Real F00 = F(0, 0); + Real F01 = F(0, 1); + Real F10 = F(1, 0); + Real F11 = F(1, 1); + + //polar decomposition + Mat2d R = Mat2d(F00 + F11, F01 - F10, F10 - F01, F00 + F11); + R = R / sqrt(pow(F00 + F11, 2) + pow(F01 - F10, 2)); + + n_[index_i] = R * n_0_[index_i]; + } + //=========================================================================================// + void ConstrainSolidBodyPartBySimBody::Update(size_t index_i, Real dt) + { + Vecd& pos_0_i = pos_0_[index_i]; + Vec3 rr, pos, vel, acc; + rr(0) = pos_0_i[0] - initial_mobod_origin_location_[0]; + rr(1) = pos_0_i[1] - initial_mobod_origin_location_[1]; + rr(2) = 0.0; + mobod_.findStationLocationVelocityAndAccelerationInGround(*simbody_state_, rr, pos, vel, acc); + /** this is how we calculate the particle position in after transform of MBbody. + * const SimTK::Rotation& R_GB = mobod_.getBodyRotation(simbody_state); + * const SimTK::Vec3& p_GB = mobod_.getBodyOriginLocation(simbody_state); + * const SimTK::Vec3 r = R_GB * rr; // re-express station vector p_BS in G (15 flops) + * base_particle_data_i.pos_n_ = (p_GB + r).getSubVec<2>(0); + */ + pos_n_[index_i] = pos.getSubVec<2>(0); + vel_n_[index_i] = vel.getSubVec<2>(0); + dvel_dt_[index_i] = acc.getSubVec<2>(0); + n_[index_i] = (mobod_.getBodyRotation(*simbody_state_) + * upgradeToVector3D(n_0_[index_i])).getSubVec<2>(0); + } + //=========================================================================================// + SimTK::SpatialVec TotalForceOnSolidBodyPartForSimBody::ReduceFunction(size_t index_i, Real dt) + { + Vec3 force_from_particle(0); + force_from_particle.updSubVec<2>(0) = force_from_fluid_[index_i] + contact_force_[index_i]; + Vec3 displacement(0); + displacement.updSubVec<2>(0) = pos_n_[index_i] + - current_mobod_origin_location_.getSubVec<2>(0); + Vec3 torque_from_particle = cross(displacement, force_from_particle); + + return SimTK::SpatialVec(torque_from_particle, force_from_particle); + } + //=================================================================================================// + } +} diff --git a/SPHINXsys/src/for_2D_build/particle_generator/CMakeLists.txt b/SPHINXsys/src/for_2D_build/particle_generator/CMakeLists.txt new file mode 100644 index 0000000000..9ef366a60f --- /dev/null +++ b/SPHINXsys/src/for_2D_build/particle_generator/CMakeLists.txt @@ -0,0 +1,7 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/particle_generator/all_particle_generators.h b/SPHINXsys/src/for_2D_build/particle_generator/all_particle_generators.h new file mode 100644 index 0000000000..8e6229123b --- /dev/null +++ b/SPHINXsys/src/for_2D_build/particle_generator/all_particle_generators.h @@ -0,0 +1,8 @@ + +#ifndef ALL_PARTICLE_GENERATORS_2D_H +#define ALL_PARTICLE_GENERATORS_2D_H + + + +#include "particle_generator_lattice.h" +#endif //ALL_PARTICLE_GENERATORS_2D_H \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/particle_generator/particle_generator_lattice_supplementary.cpp b/SPHINXsys/src/for_2D_build/particle_generator/particle_generator_lattice_supplementary.cpp new file mode 100644 index 0000000000..40363e3c77 --- /dev/null +++ b/SPHINXsys/src/for_2D_build/particle_generator/particle_generator_lattice_supplementary.cpp @@ -0,0 +1,34 @@ +/** + * @file particle_generator_lattic_supplementary.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#include "particle_generator_lattice.h" + +#include "geometry.h" +#include "base_mesh.h" +#include "base_body.h" +#include "base_particles.h" + +namespace SPH { + //=================================================================================================// + void ParticleGeneratorLattice::createBaseParticles(BaseParticles* base_particles) + { + std::unique_ptr mesh(new BaseMesh(domain_bounds_, lattice_spacing_, 0)); + Real particle_volume = lattice_spacing_ * lattice_spacing_; + Vecu number_of_lattices = mesh->NumberOfCellsFromNumberOfGridPoints(mesh->NumberOfGridPoints()); + for (size_t i = 0; i < number_of_lattices[0]; ++i) + for (size_t j = 0; j < number_of_lattices[1]; ++j) + { + Vecd particle_position = mesh->CellPositionFromIndex(Vecu(i,j)); + if (body_shape_->checkNotFar(particle_position, lattice_spacing_)) + { + if (body_shape_->checkContain(particle_position)) + { + createABaseParticle(base_particles, particle_position, particle_volume); + } + } + } + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/for_2D_build/particles/CMakeLists.txt b/SPHINXsys/src/for_2D_build/particles/CMakeLists.txt new file mode 100644 index 0000000000..9ef366a60f --- /dev/null +++ b/SPHINXsys/src/for_2D_build/particles/CMakeLists.txt @@ -0,0 +1,7 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_2D_build/particles/solid_particles_supplementary.cpp b/SPHINXsys/src/for_2D_build/particles/solid_particles_supplementary.cpp new file mode 100644 index 0000000000..6e1f01da34 --- /dev/null +++ b/SPHINXsys/src/for_2D_build/particles/solid_particles_supplementary.cpp @@ -0,0 +1,35 @@ +/** + * @file solid_particles_supplementary.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#include "solid_particles.h" +#include "base_body.h" + +namespace SPH { + //=============================================================================================// + void SolidParticles::ParticleTranslationAndRotation(Transformd& transform) + { + for (size_t i = 0; i != total_real_particles_; ++i) + { + pos_n_[i] = transform.imposeTransform(pos_n_[i]); + pos_0_[i] = transform.imposeTransform(pos_0_[i]); + } + } + //=================================================================================================// + Real ElasticSolidParticles::von_Mises_stress(size_t particle_i) + { + Real J = rho0_ / rho_n_[particle_i]; + Mat2d F = F_[particle_i]; + Mat2d stress = stress_PK1_[particle_i]; + Mat2d sigma = (stress * ~F) / J; + + Real sigmaxx = sigma(0, 0); + Real sigmayy = sigma(1, 1); + Real sigmaxy = sigma(0, 1); + + return sqrt(sigmaxx * sigmaxx + sigmayy * sigmayy - sigmaxx * sigmayy + + 3.0 * sigmaxy * sigmaxy); + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/for_3D_build/CMakeLists.txt b/SPHINXsys/src/for_3D_build/CMakeLists.txt new file mode 100644 index 0000000000..ff8c447b75 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/CMakeLists.txt @@ -0,0 +1,116 @@ +## 3D build +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Dirsearch_for_3D_build) + +## prepare dirctoriesfor head and source files +HEADER_DIRECTORIES_SHARED(headdirs_shared) +SOURCE_DIRECTORIES_SHARED(sourcedirs_shared) +HEADER_DIRECTORIES_3D(headdirs_3D) +SOURCE_DIRECTORIES_3D(sourcedirs_3D) + +SET(usefuldirs ${headdirs_3D} ${sourcedirs_3D}) +LIST(REMOVE_DUPLICATES usefuldirs) + +SET(usefulsubdirs ${usefuldirs}) +LIST(REMOVE_ITEM usefulsubdirs ${CMAKE_CURRENT_SOURCE_DIR}) + +#Add all useful subdirectories +FOREACH(subdir_path ${usefulsubdirs}) + #message(STATUS ${subdir_path}) + ADD_SUBDIRECTORY(${subdir_path}) +ENDFOREACH() + +# combin head and souce directories +SET(headdirs ${headdirs_shared} ${headdirs_3D}) +SET(sourcedirs ${sourcedirs_shared} ${sourcedirs_3D}) + +##Add all header dirs +FOREACH(headdir_path ${headdirs}) + #message(STATUS ${headdir_path}) + INCLUDE_DIRECTORIES("${headdir_path}") +ENDFOREACH() + +##Add all source files +set(SCR_FILES "") +FOREACH(srcdir_path ${sourcedirs}) + #message(STATUS ${srcdir_path}) + set(DIR_scrs "") + AUX_SOURCE_DIRECTORY(${srcdir_path} DIR_scrs) + list(APPEND SCR_FILES ${DIR_scrs}) +ENDFOREACH() + +SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) + +if(NOT SPH_ONLY_STATIC_BUILD) + ### SPHinXsys dynamic lib ### + ADD_LIBRARY(sphinxsys_3d SHARED ${SCR_FILES}) + + ADD_LIBRARY(sphinxsys_static_3d STATIC ${SCR_FILES}) + SET_TARGET_PROPERTIES(sphinxsys_static_3d PROPERTIES OUTPUT_NAME "sphinxsys_3d") + + if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + #target_link_libraries(sphinxsys_3d) + else(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + target_link_libraries(sphinxsys_3d stdc++) + else(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + target_link_libraries(sphinxsys_3d stdc++ stdc++fs) + endif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + + if(DEFINED BOOST_AVAILABLE) # link Boost if available (not for Windows) + target_link_libraries(sphinxsys_3d ${Boost_LIBRARIES}) + endif() + endif(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + + if(NOT BUILD_WITH_ONETBB) # link TBB if not built by the project + target_link_libraries(sphinxsys_3d ${TBB_LIBRARYS}) + else() + target_link_libraries(sphinxsys_3d TBB::tbb TBB::tbbmalloc) # TBB::tbbmalloc_proxy is not needed + endif() + if(NOT BUILD_WITH_SIMBODY) # link Simbody if not built by the project + target_link_libraries(sphinxsys_3d ${Simbody_LIBRARIES}) + endif() + ### SPHinXsys dynamic lib ### +else() + ### SPHinXsys static lib ### + ADD_LIBRARY(sphinxsys_static_3d STATIC ${SCR_FILES}) + + if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + #target_link_libraries(sphinxsys_static_3d) + else(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + target_link_libraries(sphinxsys_static_3d stdc++) + else(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + target_link_libraries(sphinxsys_static_3d stdc++ stdc++fs) + endif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + + if(DEFINED BOOST_AVAILABLE) # link Boost if available (not for Windows) + target_link_libraries(sphinxsys_static_3d ${Boost_LIBRARIES}) + endif() + endif(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + + if(NOT BUILD_WITH_ONETBB) # link TBB if not built by the project + target_link_libraries(sphinxsys_static_3d ${TBB_LIBRARYS}) + else() + target_link_libraries(sphinxsys_static_3d TBB::tbb TBB::tbbmalloc) # TBB::tbbmalloc_proxy is not needed + endif() + if(NOT BUILD_WITH_SIMBODY) # link Simbody if not built by the project + target_link_libraries(sphinxsys_static_3d ${Simbody_LIBRARIES}) + endif() + ### SPHinXsys static lib ### +endif() + +if(NOT SPH_ONLY_STATIC_BUILD) + INSTALL(TARGETS sphinxsys_3d sphinxsys_static_3d + RUNTIME DESTINATION 3d_code/bin + LIBRARY DESTINATION 3d_code/lib + ARCHIVE DESTINATION 3d_code/lib) +else() + INSTALL(TARGETS sphinxsys_static_3d + RUNTIME DESTINATION 3d_code/bin + LIBRARY DESTINATION 3d_code/lib + ARCHIVE DESTINATION 3d_code/lib) +endif() + +FILE(GLOB_RECURSE hpp_headers ${PROJECT_SOURCE_DIR}/src/shared/*.hpp ${PROJECT_SOURCE_DIR}/src/for_3D_build/*.hpp) +INSTALL(FILES ${hpp_headers} DESTINATION 3d_code/include) diff --git a/SPHINXsys/src/for_3D_build/bodies/CMakeLists.txt b/SPHINXsys/src/for_3D_build/bodies/CMakeLists.txt new file mode 100644 index 0000000000..fd5eae60fd --- /dev/null +++ b/SPHINXsys/src/for_3D_build/bodies/CMakeLists.txt @@ -0,0 +1,7 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/bodies/solid_body_supplementary.cpp b/SPHINXsys/src/for_3D_build/bodies/solid_body_supplementary.cpp new file mode 100644 index 0000000000..75fe6b28b6 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/bodies/solid_body_supplementary.cpp @@ -0,0 +1,59 @@ +/** + * @file base_body_supplementary.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#include "solid_body.h" +#include "solid_particles.h" + +namespace SPH +{ + //=================================================================================================// + void SolidBodyPartForSimbody::tagBodyPart() + { + BodyPartByParticle::tagBodyPart(); + + Real body_part_volume(0); + initial_mass_center_ = Vec3d(0); + for (size_t i = 0; i < body_part_particles_.size(); ++i) + { + size_t index_i = body_part_particles_[i]; + Vecd particle_position = solid_particles_->pos_0_[index_i]; + Real particle_volume = solid_particles_->Vol_[index_i]; + + initial_mass_center_ += particle_volume * particle_position; + body_part_volume += particle_volume; + } + + initial_mass_center_ /= body_part_volume; + + //computing unit intertia + Vec3d intertia_moments(0); + Vec3d intertia_products(0); + for (size_t i = 0; i < body_part_particles_.size(); ++i) + { + size_t index_i = body_part_particles_[i]; + Vecd particle_position = solid_particles_->pos_0_[index_i]; + Real particle_volume = solid_particles_->Vol_[index_i]; + + Vec3d displacement = (particle_position - initial_mass_center_); + intertia_moments[0] += particle_volume + * (displacement[1] * displacement[1] + displacement[2] * displacement[2]); + intertia_moments[1] += particle_volume + * (displacement[0] * displacement[0] + displacement[2] * displacement[2]); + intertia_moments[2] += particle_volume + * (displacement[0] * displacement[0] + displacement[1] * displacement[1]); + intertia_products[0] -= particle_volume * displacement[0] * displacement[1]; + intertia_products[1] -= particle_volume * displacement[0] * displacement[2]; + intertia_products[2] -= particle_volume * displacement[1] * displacement[2]; + + } + intertia_moments /= body_part_volume; + intertia_products /= body_part_volume; + + body_part_mass_properties_ + = new SimTK::MassProperties(body_part_volume * solid_body_density_, + Vec3d(0), SimTK::UnitInertia(intertia_moments, intertia_products)); + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/for_3D_build/common/CMakeLists.txt b/SPHINXsys/src/for_3D_build/common/CMakeLists.txt new file mode 100644 index 0000000000..fd5eae60fd --- /dev/null +++ b/SPHINXsys/src/for_3D_build/common/CMakeLists.txt @@ -0,0 +1,7 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/common/data_type.h b/SPHINXsys/src/for_3D_build/common/data_type.h new file mode 100644 index 0000000000..175169e779 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/common/data_type.h @@ -0,0 +1,39 @@ + +#ifndef DATA_TYPE_3D_H +#define DATA_TYPE_3D_H + + + +#include "base_data_type.h" + +namespace SPH { + + //for 3d build + using Veci = Vec3i; + using Vecu = Vec3u; + using Vecd = Vec3d; + using Matd = Mat3d; + using SymMatd = SymMat3d; + using AngularVecd = Vec3d; + const int indexAngularVector = 1; + + using Transformd = Transform3d; + + template + using PackageDataMatrix = std::array, ARRAY_SIZE>, ARRAY_SIZE>; + + template + using MeshDataMatrix = DataType***; + + /** only works for smoothing length ratio less or equal than 1.3*/ + constexpr int MaximumNeighborhoodSize = int(1.33 * M_PI * 27); + + const int Dimensions = 3; + + /** correction matrix, only works for thin structure dynamics. */ + const Matd reduced_unit_matrix = { 1, 0, 0, 0, 1, 0, 0, 0, 0 }; + + /** initial local normal, only works for thin structure dynamics. */ + const Vecd local_pseudo_n_0 = Vecd(0.0, 0.0, 1.0); +} +#endif //DATA_TYPE_3D_H \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/common/scalar_functions_supplementary.cpp b/SPHINXsys/src/for_3D_build/common/scalar_functions_supplementary.cpp new file mode 100644 index 0000000000..c079f5efe5 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/common/scalar_functions_supplementary.cpp @@ -0,0 +1,8 @@ +#include "scalar_functions.h" + +namespace SPH { + //=================================================================================================// + int SecondAxis(int axis_direction) { + return axis_direction == 2 ? 0 : axis_direction + 1; + } +} diff --git a/SPHINXsys/src/for_3D_build/geometries/CMakeLists.txt b/SPHINXsys/src/for_3D_build/geometries/CMakeLists.txt new file mode 100644 index 0000000000..fd5eae60fd --- /dev/null +++ b/SPHINXsys/src/for_3D_build/geometries/CMakeLists.txt @@ -0,0 +1,7 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/geometries/geometry.cpp b/SPHINXsys/src/for_3D_build/geometries/geometry.cpp new file mode 100644 index 0000000000..4bc0e06b42 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/geometries/geometry.cpp @@ -0,0 +1,334 @@ +#include "geometry.h" + +namespace SPH +{ + //=================================================================================================// + TriangleMeshShape::TriangleMeshShape(std::string filepathname, Vec3d translation, Real scale_factor) + : Shape("TriangleMeshShape") + { + if (!fs::exists(filepathname)) + { + std::cout << "\n Error: the input file:" << filepathname << " is not exists" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + throw; + } + SimTK::PolygonalMesh polymesh; + polymesh.loadStlFile(filepathname); + polymesh.scaleMesh(scale_factor); + triangle_mesh_ = generateTriangleMesh(polymesh.transformMesh(translation)); + } + //=================================================================================================// + TriangleMeshShape::TriangleMeshShape(Vec3d halfsize, int resolution, Vec3d translation) + : Shape("TriangleMeshShape") + { + SimTK::PolygonalMesh polymesh = SimTK::PolygonalMesh::createBrickMesh(halfsize, resolution); + triangle_mesh_ = generateTriangleMesh(polymesh.transformMesh(translation)); + } + //=================================================================================================// + TriangleMeshShape::TriangleMeshShape(Real radius, int resolution, Vec3d translation) + : Shape("TriangleMeshShape") + { + SimTK::PolygonalMesh polymesh = SimTK::PolygonalMesh::createSphereMesh(radius, resolution); + triangle_mesh_ = generateTriangleMesh(polymesh.transformMesh(translation)); + } + //=================================================================================================// + TriangleMeshShape:: + TriangleMeshShape(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation) + : Shape("TriangleMeshShape") + { + SimTK::PolygonalMesh polymesh = + SimTK::PolygonalMesh::createCylinderMesh(axis, radius, halflength, resolution); + triangle_mesh_ = generateTriangleMesh(polymesh.transformMesh(translation)); + } + //=================================================================================================// + SimTK::ContactGeometry::TriangleMesh *TriangleMeshShape:: + generateTriangleMesh(SimTK::PolygonalMesh &ploy_mesh) + { + SimTK::ContactGeometry::TriangleMesh *triangle_mesh; + triangle_mesh = new SimTK::ContactGeometry::TriangleMesh(ploy_mesh); + if (!SimTK::ContactGeometry::TriangleMesh::isInstance(*triangle_mesh)) + { + std::cout << "\n Error the triangle mesh is not valid" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + throw; + } + std::cout << "num of faces:" << triangle_mesh->getNumFaces() << std::endl; + + return triangle_mesh; + } + //=================================================================================================// + bool TriangleMeshShape::checkContain(const Vec3d &pnt, bool BOUNDARY_INCLUDED) + { + + SimTK::Vec2 uv_coordinate; + bool inside = false; + int face_id; + Vec3d closest_pnt = triangle_mesh_->findNearestPoint(pnt, inside, face_id, uv_coordinate); + + StdVec neighbor_face(4); + neighbor_face[0] = face_id; + /** go throught the neighbor faces. */ + for (int i = 1; i < 4; i++) + { + int edge = triangle_mesh_->getFaceEdge(face_id, i - 1); + int face = triangle_mesh_->getEdgeFace(edge, 0); + neighbor_face[i] = face != face_id ? face : triangle_mesh_->getEdgeFace(edge, 1); + } + + Vec3d from_face_to_pnt = pnt - closest_pnt; + Real sum_weights = 0.0; + Real weighted_dot_product = 0.0; + for (int i = 0; i < 4; i++) + { + SimTK::UnitVec3 normal_direction = triangle_mesh_->getFaceNormal(neighbor_face[i]); + Real dot_product = dot(normal_direction, from_face_to_pnt); + Real weight = dot_product * dot_product; + weighted_dot_product += weight * dot_product; + sum_weights += weight; + } + + weighted_dot_product /= sum_weights; + + bool weighted_inside = false; + if (weighted_dot_product < 0.0) + weighted_inside = true; + + if (face_id < 0 && face_id > triangle_mesh_->getNumFaces()) + { + std::cout << "\n Error the nearest point is not valid" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + throw; + } + + return weighted_inside; + } + //=================================================================================================// + Vec3d TriangleMeshShape::findClosestPoint(const Vec3d &input_pnt) + { + bool inside = false; + int face_id; + SimTK::Vec2 normal; + Vec3d closest_pnt; + closest_pnt = triangle_mesh_->findNearestPoint(input_pnt, inside, face_id, normal); + if (face_id < 0 && face_id > triangle_mesh_->getNumFaces()) + { + std::cout << "\n Error the nearest point is not valid" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + throw; + } + return closest_pnt; + } + //=================================================================================================// + BoundingBox TriangleMeshShape::findBounds() + { + int number_of_vertices = triangle_mesh_->getNumVertices(); + //initial reference values + Vec3d lower_bound = Vec3d(Infinity); + Vec3d upper_bound = Vec3d(-Infinity); + + for (int i = 0; i != number_of_vertices; ++i) + { + Vec3d vertex_position = triangle_mesh_->getVertexPosition(i); + for (int j = 0; j != 3; ++j) + { + lower_bound[j] = SMIN(lower_bound[j], vertex_position[j]); + upper_bound[j] = SMAX(upper_bound[j], vertex_position[j]); + } + } + return BoundingBox(lower_bound, upper_bound); + } + //=================================================================================================// + bool ComplexShape::checkContain(const Vec3d &input_pnt, bool BOUNDARY_INCLUDED) + { + bool exist = false; + bool inside = false; + + for (auto &each_shape : triangle_mesh_shapes_) + { + TriangleMeshShape *sp = each_shape.first; + ShapeBooleanOps operation_string = each_shape.second; + + switch (operation_string) + { + case ShapeBooleanOps::add: + { + inside = sp->checkContain(input_pnt); + exist = exist || inside; + break; + } + case ShapeBooleanOps::sub: + { + inside = sp->checkContain(input_pnt); + exist = exist && (!inside); + break; + } + default: + { + std::cout << "\n FAILURE: the boolean operation is not applicable!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + throw; + } + } + } + return exist; + } + //=================================================================================================// + Vec3d ComplexShape::findClosestPoint(const Vec3d &input_pnt) + { + //a big positive number + Real large_number(Infinity); + Real dist_min = large_number; + Vec3d pnt_closest(0); + Vec3d pnt_found(0); + + for (auto &each_shape : triangle_mesh_shapes_) + { + TriangleMeshShape *sp = each_shape.first; + pnt_found = sp->findClosestPoint(input_pnt); + Real dist = (input_pnt - pnt_found).norm(); + + if (dist <= dist_min) + { + dist_min = dist; + pnt_closest = pnt_found; + } + } + + return pnt_closest; + } + //=================================================================================================// + Real ComplexShape::findSignedDistance(const Vec3d &input_pnt) + { + Real distance_to_surface = (input_pnt - findClosestPoint(input_pnt)).norm(); + return checkContain(input_pnt) ? -distance_to_surface : distance_to_surface; + } + //=================================================================================================// + Vec3d ComplexShape::findNormalDirection(const Vec3d &input_pnt) + { + bool is_contain = checkContain(input_pnt); + Vecd displacement_to_surface = findClosestPoint(input_pnt) - input_pnt; + while (displacement_to_surface.norm() < Eps) + { + Vecd jittered = input_pnt; //jittering + for (int l = 0; l != input_pnt.size(); ++l) + jittered[l] = input_pnt[l] + (((Real)rand() / (RAND_MAX)) - 0.5) * 100.0 * Eps; + if (checkContain(jittered) == is_contain) + displacement_to_surface = findClosestPoint(jittered) - jittered; + } + Vecd direction_to_surface = displacement_to_surface.normalize(); + return is_contain ? direction_to_surface : -1.0 * direction_to_surface; + } + //=================================================================================================// + void ComplexShape::addTriangleMeshShape(TriangleMeshShape *triangle_mesh_shape, ShapeBooleanOps op) + { + std::pair shape_and_op(triangle_mesh_shape, op); + triangle_mesh_shapes_.push_back(shape_and_op); + } + //=================================================================================================// + void ComplexShape::addBrick(Vec3d halfsize, int resolution, Vec3d translation, ShapeBooleanOps op) + { + TriangleMeshShape *triangle_mesh_shape = new TriangleMeshShape(halfsize, resolution, translation); + std::pair shape_and_op(triangle_mesh_shape, op); + triangle_mesh_shapes_.push_back(shape_and_op); + } + //=================================================================================================// + void ComplexShape::addSphere(Real radius, int resolution, Vec3d translation, ShapeBooleanOps op) + { + TriangleMeshShape *triangle_mesh_shape = new TriangleMeshShape(radius, resolution, translation); + std::pair shape_and_op(triangle_mesh_shape, op); + triangle_mesh_shapes_.push_back(shape_and_op); + } + //=================================================================================================// + void ComplexShape::addCylinder(SimTK::UnitVec3 axis, Real radius, Real halflength, + int resolution, Vec3d translation, ShapeBooleanOps op) + { + /** Here SimTK::UnitVec3 give the direction of the cylinder, viz. SimTK::UnitVec3(0,0,1) create a cylinder in z-axis.*/ + TriangleMeshShape *triangle_mesh_shape = + new TriangleMeshShape(axis, radius, halflength, resolution, translation); + std::pair shape_and_op(triangle_mesh_shape, op); + triangle_mesh_shapes_.push_back(shape_and_op); + } + //=================================================================================================// + void ComplexShape::addFormSTLFile(std::string file_path_name, Vec3d translation, + Real scale_factor, ShapeBooleanOps op) + { + TriangleMeshShape *triangle_mesh_shape = + new TriangleMeshShape(file_path_name, translation, scale_factor); + std::pair shape_and_op(triangle_mesh_shape, op); + triangle_mesh_shapes_.push_back(shape_and_op); + } + //=================================================================================================// + void ComplexShape::addComplexShape(ComplexShape *complex_shape, ShapeBooleanOps op) + { + switch (op) + { + case ShapeBooleanOps::add: + { + for (auto &shape_and_op : complex_shape->triangle_mesh_shapes_) + { + triangle_mesh_shapes_.push_back(shape_and_op); + } + break; + } + case ShapeBooleanOps::sub: + { + for (auto &shape_and_op : complex_shape->triangle_mesh_shapes_) + { + TriangleMeshShape *sp = shape_and_op.first; + ShapeBooleanOps operation_string = + shape_and_op.second == ShapeBooleanOps::add ? ShapeBooleanOps::sub : ShapeBooleanOps::add; + std::pair substract_shape_and_op(sp, operation_string); + triangle_mesh_shapes_.push_back(substract_shape_and_op); + } + break; + } + case ShapeBooleanOps::sym_diff: + { + std::cout << "\n FAILURE: the boolean operation: ShapeBooleanOps::sym_diff is not applicable for 3D geometry!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + throw; + } + case ShapeBooleanOps::intersect: + { + std::cout << "\n FAILURE: the boolean operation: ShapeBooleanOps::intersect is not applicable for 3D geometry!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + throw; + } + default: + { + std::cout << "\n FAILURE: the boolean operation is not applicable!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + throw; + } + } + } + //=================================================================================================// + bool ComplexShape::checkNotFar(const Vec3d &input_pnt, Real threshold) + { + return checkContain(input_pnt) || checkNearSurface(input_pnt, threshold) ? true : false; + } + //=================================================================================================// + bool ComplexShape::checkNearSurface(const Vec3d &input_pnt, Real threshold) + { + return getMaxAbsoluteElement(input_pnt - findClosestPoint(input_pnt)) < threshold ? true : false; + } + //=================================================================================================// + BoundingBox ComplexShape::findBounds() + { + //initial reference values + Vec3d lower_bound = Vec3d(Infinity); + Vec3d upper_bound = Vec3d(-Infinity); + + for (size_t i = 0; i < triangle_mesh_shapes_.size(); i++) + { + BoundingBox shape_bounds = triangle_mesh_shapes_[i].first->findBounds(); + for (int j = 0; j != 3; ++j) + { + lower_bound[j] = SMIN(lower_bound[j], shape_bounds.first[j]); + upper_bound[j] = SMAX(upper_bound[j], shape_bounds.second[j]); + } + } + return BoundingBox(lower_bound, upper_bound); + } + //=================================================================================================// +} \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/geometries/geometry.h b/SPHINXsys/src/for_3D_build/geometries/geometry.h new file mode 100644 index 0000000000..7a63cf1c3c --- /dev/null +++ b/SPHINXsys/src/for_3D_build/geometries/geometry.h @@ -0,0 +1,115 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file geometry.h +* @brief Here, we define the 3D geometric algortihms. they are based on the polymesh. +* @details The idea is to define complex geometry by passing stl, obj or other +* polymesh files. +* @author Chi ZHang and Xiangyu Hu +*/ + +#ifndef GEOMETRY_3D_H +#define GEOMETRY_3D_H + +#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING + +#include "base_geometry.h" +#include "simbody_middle.h" + +#include +#include +#include + +/** Macro for APPLE compilers*/ +#ifdef __APPLE__ +#include +namespace fs = boost::filesystem; +#else +#include +namespace fs = std::experimental::filesystem; +#endif + +namespace SPH +{ + + /** + * @brief preclaimed classes. + */ + class Kernel; + + class TriangleMeshShape : public Shape + { + public: + //constructor for load stl file from out side + TriangleMeshShape(std::string file_path_name, Vec3d translation, Real scale_factor); + // constructor for brick geometry + TriangleMeshShape(Vec3d halfsize, int resolution, Vec3d translation); + // constructor for sphere geometry + TriangleMeshShape(Real radius, int resolution, Vec3d translation); + //constructor for cylinder geometry + TriangleMeshShape(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation); + + SimTK::ContactGeometry::TriangleMesh *getTriangleMesh() { return triangle_mesh_; }; + bool checkContain(const Vec3d &pnt, bool BOUNDARY_INCLUDED = true); + Vec3d findClosestPoint(const Vec3d &input_pnt); + virtual BoundingBox findBounds() override; + + protected: + SimTK::ContactGeometry::TriangleMesh *triangle_mesh_; + + //generate triangle mesh from polymesh + SimTK::ContactGeometry::TriangleMesh *generateTriangleMesh(SimTK::PolygonalMesh &ploy_mesh); + }; + + class ComplexShape : public Shape + { + Vec3d findClosestPoint(const Vec3d &input_pnt); + + public: + ComplexShape() : Shape("ComplexShape"){}; + ComplexShape(std::string complex_shape_name) : Shape(complex_shape_name){}; + virtual ~ComplexShape(){}; + virtual BoundingBox findBounds() override; + + void addTriangleMeshShape(TriangleMeshShape *triangle_mesh_shape, ShapeBooleanOps op); + void addComplexShape(ComplexShape *complex_shape, ShapeBooleanOps op); + void addBrick(Vec3d halfsize, int resolution, Vec3d translation, ShapeBooleanOps op); + void addSphere(Real radius, int resolution, Vec3d translation, ShapeBooleanOps op); + void addCylinder(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation, ShapeBooleanOps op); + void addFormSTLFile(std::string file_path_name, Vec3d translation, Real scale_factor, ShapeBooleanOps op); + + virtual bool checkContain(const Vec3d &input_pnt, bool BOUNDARY_INCLUDED = true); + virtual bool checkNotFar(const Vec3d &input_pnt, Real threshold); + virtual bool checkNearSurface(const Vec3d &input_pnt, Real threshold); + /** Signed distance is negative for point within the complex shape. */ + virtual Real findSignedDistance(const Vec3d &input_pnt); + /** Normal direction point toward outside of the complex shape. */ + virtual Vec3d findNormalDirection(const Vec3d &input_pnt); + + protected: + /** shape container */ + std::vector> triangle_mesh_shapes_; + }; +} + +#endif //GEOMETRY_3D_H \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/geometries/level_set_supplementary.cpp b/SPHINXsys/src/for_3D_build/geometries/level_set_supplementary.cpp new file mode 100644 index 0000000000..9ebf6c21dc --- /dev/null +++ b/SPHINXsys/src/for_3D_build/geometries/level_set_supplementary.cpp @@ -0,0 +1,476 @@ +/** + * @file level_set_supplementary.cpp + * @author Luhui Han, Chi ZHang Yongchuan YU and Xiangyu Hu + */ + +#include "level_set.h" + +#include "base_kernel.h" +#include "base_particles.h" +#include "base_body.h" +#include "particle_adaptation.h" + +namespace SPH { + //=================================================================================================// + void LevelSetDataPackage::initializeWithUniformData(Real level_set) + { + for (int i = 0; i != PackageSize(); ++i) + for (int j = 0; j != PackageSize(); ++j) + for (int k = 0; k != PackageSize(); ++k) + { + phi_[i][j][k] = level_set; + n_[i][j][k] = Vecd(1.0); + kernel_weight_[i][j][k] = level_set < 0.0 ? 0 : 1.0; + kernel_gradient_[i][j][k] = Vecd(0.0); + near_interface_id_[i][j][k] = level_set < 0.0 ? -2 : 2; + } + } + //=================================================================================================// + void LevelSetDataPackage::initializeBasicData(ComplexShape& complex_shape) + { + for (int i = 0; i != PackageSize(); ++i) + for (int j = 0; j != PackageSize(); ++j) + for (int k = 0; k != PackageSize(); ++k) + { + Vec3d position = data_lower_bound_ + + Vec3d((Real)i * grid_spacing_, (Real)j * grid_spacing_, (Real)k * grid_spacing_); + phi_[i][j][k] = complex_shape.findSignedDistance(position); + near_interface_id_[i][j][k] = phi_[i][j][k] < 0.0 ? -2 : 2; + } + } + //=================================================================================================// + void LevelSetDataPackage::computeKernelIntegrals(LevelSet& level_set) + { + for (int i = 0; i != PackageSize(); ++i) + for (int j = 0; j != PackageSize(); ++j) + for (int k = 0; k != PackageSize(); ++k) + { + Vec3d position = data_lower_bound_ + + Vec3d((Real)i * grid_spacing_, (Real)j * grid_spacing_, (Real)k * grid_spacing_); + kernel_weight_[i][j][k] = level_set.computeKernelIntegral(position); + kernel_gradient_[i][j][k] = level_set.computeKernelGradientIntegral(position); + } + } + //=================================================================================================// + void LevelSetDataPackage::stepReinitialization() + { + for (int i = AddressBufferWidth(); i != OperationUpperBound(); ++i) + for (int j = AddressBufferWidth(); j != OperationUpperBound(); ++j) + for (int k = AddressBufferWidth(); k != OperationUpperBound(); ++k) + { + //only reinitialize non cut cells + if (*near_interface_id_addrs_[i][j][k] != 0) + { + Real phi_0 = *phi_addrs_[i][j][k]; + Real s = phi_0 / sqrt(phi_0 * phi_0 + grid_spacing_ * grid_spacing_); + //x direction + Real dv_xp = (*phi_addrs_[i + 1][j][k] - phi_0); + Real dv_xn = (phi_0 - *phi_addrs_[i - 1][j][k]); + Real dv_x = dv_xp; + if (s * dv_xp >= 0.0 && s * dv_xn >= 0.0) dv_x = dv_xn; + if (s * dv_xp <= 0.0 && s * dv_xn <= 0.0) dv_x = dv_xp; + if (s * dv_xp > 0.0 && s * dv_xn < 0.0) dv_x = 0.0; + if (s * dv_xp < 0.0 && s * dv_xn > 0.0) + { + Real ss = s * (fabs(dv_xp) - fabs(dv_xn)) / (dv_xp - dv_xn); + if (ss > 0.0) dv_x = dv_xn; + } + //y direction + Real dv_yp = (*phi_addrs_[i][j + 1][k] - phi_0); + Real dv_yn = (phi_0 - *phi_addrs_[i][j - 1][k]); + Real dv_y = dv_yp; + if (s * dv_yp >= 0.0 && s * dv_yn >= 0.0) dv_y = dv_yn; + if (s * dv_yp <= 0.0 && s * dv_yn <= 0.0) dv_y = dv_yp; + if (s * dv_yp > 0.0 && s * dv_yn < 0.0) dv_y = 0.0; + if (s * dv_yp < 0.0 && s * dv_yn > 0.0) + { + Real ss = s * (fabs(dv_yp) - fabs(dv_yn)) / (dv_yp - dv_yn); + if (ss > 0.0) dv_y = dv_yn; + } + //z direction + Real dv_zp = (*phi_addrs_[i][j][k + 1] - phi_0); + Real dv_zn = (phi_0 - *phi_addrs_[i][j][k - 1]); + Real dv_z = dv_zp; + if (s * dv_zp >= 0.0 && s * dv_zn >= 0.0) dv_z = dv_zn; + if (s * dv_zp <= 0.0 && s * dv_zn <= 0.0) dv_z = dv_zp; + if (s * dv_zp > 0.0 && s * dv_zn < 0.0) dv_z = 0.0; + if (s * dv_zp < 0.0 && s * dv_zn > 0.0) + { + Real ss = s * (fabs(dv_zp) - fabs(dv_zn)) / (dv_zp - dv_zn); + if (ss > 0.0) dv_z = dv_zn; + } + //time stepping + *phi_addrs_[i][j][k] -= + 0.3 * s * (sqrt(dv_x * dv_x + dv_y * dv_y + dv_z * dv_z) - grid_spacing_); + } + } + } + //=================================================================================================// + void LevelSetDataPackage::markNearInterface() + { + Real small_shift = 0.75 * grid_spacing_; + //corner averages, note that the first row and first column are not used + PackageTemporaryData corner_averages; + for (int i = 1; i != AddressSize(); ++i) + for (int j = 1; j != AddressSize(); ++j) + for (int k = 1; k != AddressSize(); ++k) + { + corner_averages[i][j][k] = CornerAverage(phi_addrs_, Veci(i, j, k), Veci(-1, -1, -1)); + } + + for (int i = AddressBufferWidth(); i != OperationUpperBound(); ++i) + for (int j = AddressBufferWidth(); j != OperationUpperBound(); ++j) + for (int k = AddressBufferWidth(); k != OperationUpperBound(); ++k) + { + //first assume far cells + Real phi_0 = *phi_addrs_[i][j][k]; + int near_interface_id = phi_0 > 0.0 ? 2 : -2; + + Real phi_average_0 = corner_averages[i][j][k]; + //find inner and outer cut cells + for (int l = 0; l != 2; ++l) + for (int m = 0; m != 2; ++m) + for (int n = 0; n != 2; ++n) + { + int index_x = i + l; + int index_y = j + m; + int index_z = k + n; + Real phi_average = corner_averages[index_x][index_y][index_z]; + if ((phi_average_0 - small_shift) * (phi_average - small_shift) < 0.0) near_interface_id = 1; + if ((phi_average_0 + small_shift) * (phi_average + small_shift) < 0.0) near_interface_id = -1; + } + //find zero cut cells + for (int l = 0; l != 2; ++l) + for (int m = 0; m != 2; ++m) + for (int n = 0; n != 2; ++n) + { + int index_x = i + l; + int index_y = j + m; + int index_z = k + n; + Real phi_average = corner_averages[index_x][index_y][index_z]; + if (phi_average_0 * phi_average < 0.0) near_interface_id = 0; + } + //find cells between cut cells + if (fabs(phi_0) < small_shift && abs(near_interface_id) != 1) near_interface_id = 0; + + //assign this is to package + *near_interface_id_addrs_[i][j][k] = near_interface_id; + } + } + //=================================================================================================// + bool LevelSet::isWithinCorePackage(Vecd position) + { + Vecu cell_index = CellIndexFromPosition(position); + return data_pkg_addrs_[cell_index[0]][cell_index[1]][cell_index[2]]->is_core_pkg_; + } + //=================================================================================================// + void LevelSet::initializeDataInACell(const Vecu &cell_index, Real dt) + { + int i = (int)cell_index[0]; + int j = (int)cell_index[1]; + int k = (int)cell_index[2]; + + Vecd cell_position = CellPositionFromIndex(cell_index); + Real signed_distance = complex_shape_.findSignedDistance(cell_position); + Vecd normal_direction = complex_shape_.findNormalDirection(cell_position); + Real measure = getMaxAbsoluteElement(normal_direction * signed_distance); + if (measure < grid_spacing_) { + mutex_my_pool.lock(); + LevelSetDataPackage* new_data_pkg = data_pkg_pool_.malloc(); + mutex_my_pool.unlock(); + Vecd pkg_lower_bound = GridPositionFromCellPosition(cell_position); + new_data_pkg->initializePackageGeometry(pkg_lower_bound, data_spacing_); + new_data_pkg->initializeBasicData(complex_shape_); + core_data_pkgs_.push_back(new_data_pkg); + new_data_pkg->pkg_index_ = Vecu(i, j, k); + new_data_pkg->is_core_pkg_ = true; + data_pkg_addrs_[i][j][k] = new_data_pkg; + } + else { + data_pkg_addrs_[i][j][k] = complex_shape_.checkContain(cell_position) ? + singular_data_pkgs_addrs[0] : singular_data_pkgs_addrs[1]; + } + } + //=================================================================================================// + void LevelSet::tagACellIsInnerPackage(const Vecu &cell_index, Real dt) + { + int i = (int)cell_index[0]; + int j = (int)cell_index[1]; + int k = (int)cell_index[2]; + + bool is_inner_pkg = false; + + for (int l = SMAX(i - 1, 0); l <= SMIN(i + 1, int(number_of_cells_[0]) - 1); ++l) + for (int m = SMAX(j - 1, 0); m <= SMIN(j + 1, int(number_of_cells_[1]) - 1); ++m) + for (int n = SMAX(k - 1, 0); n <= SMIN(k + 1, int(number_of_cells_[2]) - 1); ++n) + if (data_pkg_addrs_[l][m][n]->is_core_pkg_) is_inner_pkg = true; + + if (is_inner_pkg) + { + LevelSetDataPackage* current_data_pkg = data_pkg_addrs_[i][j][k]; + if (current_data_pkg->is_core_pkg_) + { + current_data_pkg->is_inner_pkg_ = true; + inner_data_pkgs_.push_back(current_data_pkg); + } + else { + mutex_my_pool.lock(); + LevelSetDataPackage* new_data_pkg = data_pkg_pool_.malloc(); + mutex_my_pool.unlock(); + Vecd cell_position = CellPositionFromIndex(cell_index); + Vecd pkg_lower_bound = GridPositionFromCellPosition(cell_position); + new_data_pkg->initializePackageGeometry(pkg_lower_bound, data_spacing_); + new_data_pkg->initializeBasicData(complex_shape_); + new_data_pkg->is_inner_pkg_ = true; + new_data_pkg->pkg_index_ = Vecu(i, j, k); + inner_data_pkgs_.push_back(new_data_pkg); + data_pkg_addrs_[i][j][k] = new_data_pkg; + } + } + } + //=================================================================================================// + void LevelSet::redistanceInterfaceForAPackage(LevelSetDataPackage* core_data_pkg, Real dt) + { + int l = (int)core_data_pkg->pkg_index_[0]; + int m = (int)core_data_pkg->pkg_index_[1]; + int n = (int)core_data_pkg->pkg_index_[2]; + + for (int i = pkg_addrs_buffer_; i != pkg_operations_; ++i) + for (int j = pkg_addrs_buffer_; j != pkg_operations_; ++j) + for (int k = pkg_addrs_buffer_; k != pkg_operations_; ++k) + { + int near_interface_id = *core_data_pkg->near_interface_id_addrs_[i][j][k]; + if (near_interface_id == 0) + { + bool positive_band = false; + bool negative_band = false; + for (int r = -1; r < 2; ++r) + for (int s = -1; s < 2; ++s) + for (int t = -1; t < 2; ++t) + { + int neighbor_near_interface_id = + *core_data_pkg->near_interface_id_addrs_[i + r][j + s][k + t]; + if (neighbor_near_interface_id >= 1) positive_band = true; + if (neighbor_near_interface_id <= -1) negative_band = true; + } + if (positive_band == false) + { + Real min_distance_p = 5.0 * data_spacing_; + for (int x = -4; x != 5; ++x) + for (int y = -4; y != 5; ++y) + for (int z = -4; z != 5; ++z) + { + std::pair x_pair = CellShiftAndDataIndex(i + x); + std::pair y_pair = CellShiftAndDataIndex(j + y); + std::pair z_pair = CellShiftAndDataIndex(k + z); + LevelSetDataPackage* neighbor_pkg + = data_pkg_addrs_[l + x_pair.first][m + y_pair.first][n + z_pair.first]; + int neighbor_near_interface_id + = neighbor_pkg->near_interface_id_[x_pair.second][y_pair.second][z_pair.second]; + if (neighbor_near_interface_id >= 1) + { + Real phi_p_ = neighbor_pkg->phi_[x_pair.second][y_pair.second][z_pair.second]; + Vecd norm_to_face = neighbor_pkg->n_[x_pair.second][y_pair.second][z_pair.second]; + min_distance_p = SMIN(min_distance_p, (Vecd((Real)x, (Real)y, Real(z)) * data_spacing_ + phi_p_ * norm_to_face).norm()); + } + } + *core_data_pkg->phi_addrs_[i][j][k] = -min_distance_p; + // this immediate switch of near interface id + // does not intervenning with the identification of unresolved interface + // based on the assumption that positive false_and negative bands are not close to each other + *core_data_pkg->near_interface_id_addrs_[i][j][k] = -1; + } + if (negative_band == false) + { + Real min_distance_n = 5.0 * data_spacing_; + for (int x = -4; x != 5; ++x) + for (int y = -4; y != 5; ++y) + for (int z = -4; z != 5; ++z) + { + std::pair x_pair = CellShiftAndDataIndex(i + x); + std::pair y_pair = CellShiftAndDataIndex(j + y); + std::pair z_pair = CellShiftAndDataIndex(k + z); + LevelSetDataPackage* neighbor_pkg + = data_pkg_addrs_[l + x_pair.first][m + y_pair.first][n + z_pair.first]; + int neighbor_near_interface_id + = neighbor_pkg->near_interface_id_[x_pair.second][y_pair.second][z_pair.second]; + if (neighbor_near_interface_id <= -1) + { + Real phi_n_ = neighbor_pkg->phi_[x_pair.second][y_pair.second][z_pair.second]; + Vecd norm_to_face = neighbor_pkg->n_[x_pair.second][y_pair.second][z_pair.second]; + min_distance_n = SMIN(min_distance_n, (Vecd((Real)x, (Real)y, Real(z)) * data_spacing_ - phi_n_ * norm_to_face).norm()); + } + } + *core_data_pkg->phi_addrs_[i][j][k] = min_distance_n; + // this immediate switch of near interface id + // does not intervenning with the identification of unresolved interface + // based on the assumption that positive false_and negative bands are not close to each other + *core_data_pkg->near_interface_id_addrs_[i][j][k] = 1; + } + } + } + } + //=================================================================================================// + void LevelSet::writeMeshFieldToPlt(std::ofstream& output_file) + { + Vecu number_of_operation = global_mesh_.NumberOfGridPoints(); + + output_file << "\n"; + output_file << "title='View'" << "\n"; + output_file << "variables= " << "x, " << "y, " << "z, " << "phi, " << "n_x, "<< "n_y, " << "n_z " << "\n"; + output_file << "zone i=" << number_of_operation[0] << " j=" << number_of_operation[1] << " k=" << number_of_operation[2] + << " DATAPACKING=BLOCK SOLUTIONTIME=" << 0 << "\n"; + + for (size_t k = 0; k != number_of_operation[2]; ++k) + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + Vecd data_position = global_mesh_.GridPositionFromIndex(Vecu(i, j, k)); + output_file << data_position[0] << " "; + } + output_file << " \n"; + } + + for (size_t k = 0; k != number_of_operation[2]; ++k) + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + Vecd data_position = global_mesh_.GridPositionFromIndex(Vecu(i, j, k)); + output_file << data_position[1] << " "; + } + output_file << " \n"; + } + + for (size_t k = 0; k != number_of_operation[2]; ++k) + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + Vecd data_position = global_mesh_.GridPositionFromIndex(Vecu(i, j, k)); + output_file << data_position[2] << " "; + } + output_file << " \n"; + } + + for (size_t k = 0; k != number_of_operation[2]; ++k) + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + output_file << DataValueFromGlobalIndex, + &LevelSetDataPackage::phi_>(Vecu(i, j, k)) << " "; + + } + output_file << " \n"; + } + + for (size_t k = 0; k != number_of_operation[2]; ++k) + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + output_file << DataValueFromGlobalIndex, + &LevelSetDataPackage::n_>(Vecu(i, j, k))[0] << " "; + + } + output_file << " \n"; + } + + for (size_t k = 0; k != number_of_operation[2]; ++k) + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + output_file << DataValueFromGlobalIndex, + &LevelSetDataPackage::n_>(Vecu(i, j, k))[1] << " "; + + } + output_file << " \n"; + } + + for (size_t k = 0; k != number_of_operation[2]; ++k) + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + output_file << DataValueFromGlobalIndex, + &LevelSetDataPackage::n_>(Vecu(i, j, k))[2] << " "; + + } + output_file << " \n"; + } + + for (size_t k = 0; k != number_of_operation[2]; ++k) + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + output_file << DataValueFromGlobalIndex, + &LevelSetDataPackage::near_interface_id_>(Vecu(i, j, k)) << " "; + + } + output_file << " \n"; + } + } + //=============================================================================================// + Real LevelSet::computeKernelIntegral(const Vecd& position) + { + Real phi = probeSignedDistance(position); + Real cutoff_radius = kernel_.CutOffRadius(global_h_ratio_); + Real threshold = cutoff_radius + data_spacing_; + + Real integral(0.0); + if (fabs(phi) < threshold) + { + Vecu global_index_ = global_mesh_.CellIndexFromPosition(position); + for (int i = -3; i != 4; ++i) + for (int j = -3; j != 4; ++j) + for (int k = -3; k != 4; ++k) + { + Vecu neighbor_index = Vecu(global_index_[0] + i, global_index_[1] + j, global_index_[2] + k); + Real phi_neighbor = DataValueFromGlobalIndex, + &LevelSetDataPackage::phi_>(neighbor_index); + if (phi_neighbor > -data_spacing_) { + Vecd displacement = position - global_mesh_.GridPositionFromIndex(neighbor_index); + Real distance = displacement.norm(); + if (distance < cutoff_radius) + integral += kernel_.W(global_h_ratio_, distance, displacement) + * computeHeaviside(phi_neighbor, data_spacing_); + } + } + } + return phi > threshold ? 1.0 : integral * data_spacing_ * data_spacing_ * data_spacing_; + } + //=============================================================================================// + Vecd LevelSet::computeKernelGradientIntegral(const Vecd& position) + { + Real phi = probeSignedDistance(position); + Real cutoff_radius = kernel_.CutOffRadius(global_h_ratio_); + Real threshold = cutoff_radius + data_spacing_; + + Vecd integral(0.0); + if (fabs(phi) < threshold) + { + Vecu global_index_ = global_mesh_.CellIndexFromPosition(position); + for (int i = -3; i != 4; ++i) + for (int j = -3; j != 4; ++j) + for (int k = -3; k != 4; ++k) + { + Vecu neighbor_index = Vecu(global_index_[0] + i, global_index_[1] + j, global_index_[2] + k); + Real phi_neighbor = DataValueFromGlobalIndex, + &LevelSetDataPackage::phi_>(neighbor_index); + if (phi_neighbor > -data_spacing_) { + Vecd displacement = position - global_mesh_.GridPositionFromIndex(neighbor_index); + Real distance = displacement.norm(); + if (distance < cutoff_radius) + integral += kernel_.dW(global_h_ratio_, distance, displacement) + * computeHeaviside(phi_neighbor, data_spacing_) * displacement / (distance + TinyReal); + } + } + } + return integral * data_spacing_ * data_spacing_ * data_spacing_; + } + //=============================================================================================// +} diff --git a/SPHINXsys/src/for_3D_build/high_level_simulation_class/CMakeLists.txt b/SPHINXsys/src/for_3D_build/high_level_simulation_class/CMakeLists.txt new file mode 100644 index 0000000000..fd5eae60fd --- /dev/null +++ b/SPHINXsys/src/for_3D_build/high_level_simulation_class/CMakeLists.txt @@ -0,0 +1,7 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp b/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp new file mode 100644 index 0000000000..994a5f56a4 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp @@ -0,0 +1,722 @@ +#include "solid_structural_simulation_class.h" + +//////////////////////////////////////////////////// +/* global functions in StructuralSimulation */ +//////////////////////////////////////////////////// + +BodyPartByParticleTriMesh::BodyPartByParticleTriMesh(SPHBody* body, string body_part_name, TriangleMeshShape* triangle_mesh_shape) +: BodyPartByParticle(body, body_part_name) +{ + body_part_shape_ = new ComplexShape(body_part_name); + body_part_shape_->addTriangleMeshShape(triangle_mesh_shape, ShapeBooleanOps::add); + tagBodyPart(); +} + +BodyPartByParticleTriMesh::~BodyPartByParticleTriMesh() +{ + delete body_part_shape_; +} + +ImportedModel::ImportedModel(SPHSystem &system, string body_name, TriangleMeshShape* triangle_mesh_shape, ParticleAdaptation* particle_adaptation) + : SolidBody(system, body_name, particle_adaptation) +{ + ComplexShape original_body_shape; + original_body_shape.addTriangleMeshShape(triangle_mesh_shape, ShapeBooleanOps::add); + body_shape_ = new LevelSetComplexShape(this, original_body_shape, true); +} + +ImportedModel::~ImportedModel() +{ + delete body_shape_; +} + +SolidBodyForSimulation::SolidBodyForSimulation(SPHSystem &system, string body_name, TriangleMeshShape& triangle_mesh_shape, ParticleAdaptation& particle_adaptation, Real physical_viscosity, LinearElasticSolid& material_model): + imported_model_(ImportedModel(system, body_name, &triangle_mesh_shape, &particle_adaptation)), + //material_model_(material_model), + elastic_solid_particles_(ElasticSolidParticles(&imported_model_, &material_model)), + inner_body_relation_(BodyRelationInner(&imported_model_)), + + correct_configuration_(solid_dynamics::CorrectConfiguration(&inner_body_relation_)), + stress_relaxation_first_half_(solid_dynamics::StressRelaxationFirstHalf(&inner_body_relation_)), + stress_relaxation_second_half_(solid_dynamics::StressRelaxationSecondHalf(&inner_body_relation_)), + damping_random_(DampingWithRandomChoice>(&inner_body_relation_, 0.1, "Velocity", physical_viscosity)) +{} + +void expandBoundingBox(BoundingBox* original, BoundingBox* additional) +{ + for(int i = 0; i < original->first.size(); i++) + { + if ( additional->first[i] < original->first[i] ) + { + original->first[i] = additional->first[i]; + } + if ( additional->second[i] > original->second[i] ) + { + original->second[i] = additional->second[i]; + } + } +} + +void relaxParticlesSingleResolution(In_Output* in_output, + bool write_particles_to_file, + ImportedModel* imported_model, + ElasticSolidParticles* imported_model_particles, + BodyRelationInner* imported_model_inner) +{ + + BodyStatesRecordingToVtu write_imported_model_to_vtu(*in_output, { imported_model }); + MeshRecordingToPlt cell_linked_list_recording(*in_output, imported_model, imported_model->cell_linked_list_); + + //---------------------------------------------------------------------- + // Methods used for particle relaxation. + //---------------------------------------------------------------------- + RandomizePartilePosition random_imported_model_particles(imported_model); + /** A Physics relaxation step. */ + relax_dynamics::SolidRelaxationStepInner relaxation_step_inner(imported_model_inner, true); + //---------------------------------------------------------------------- + // Particle relaxation starts here. + //---------------------------------------------------------------------- + random_imported_model_particles.parallel_exec(0.25); + relaxation_step_inner.surface_bounding_.parallel_exec(); + if (write_particles_to_file) + { + write_imported_model_to_vtu.writeToFile(0.0); + } + imported_model->updateCellLinkedList(); + if (write_particles_to_file) + { + cell_linked_list_recording.writeToFile(0.0); + } + //---------------------------------------------------------------------- + // Particle relaxation time stepping start here. + //---------------------------------------------------------------------- + int ite_p = 0; + while (ite_p < 500) + { + relaxation_step_inner.parallel_exec(); + ite_p += 1; + if (ite_p % 100 == 0) + { + cout << fixed << setprecision(9) << "Relaxation steps for the imported model N = " << ite_p << "\n"; + if (write_particles_to_file) + { + write_imported_model_to_vtu.writeToFile(Real(ite_p) * 1.0e-4); + } + } + } + cout << "The physics relaxation process of imported model finish !" << endl; +} + +StructuralSimulationInput::StructuralSimulationInput( + string relative_input_path, + vector imported_stl_list, + Real scale_stl, + vector translation_list, + vector resolution_list, + vector material_model_list, + Real physical_viscosity, + vector> contacting_bodies_list + ): + relative_input_path_(relative_input_path), + imported_stl_list_(imported_stl_list), + scale_stl_(scale_stl), + translation_list_(translation_list), + resolution_list_(resolution_list), + material_model_list_(material_model_list), + physical_viscosity_(physical_viscosity), + contacting_body_pairs_list_(contacting_bodies_list) +{ + //time dependent contact + time_dep_contacting_body_pairs_list_ = {}; + // particle_relaxation option + particle_relaxation_list_ = {}; + for (size_t i = 0; i < resolution_list_.size(); i++){ particle_relaxation_list_.push_back(true); } + // scale system boundaries + scale_system_boundaries_ = 1; + // boundary conditions + non_zero_gravity_ = {}; + acceleration_bounding_box_tuple_ = {}; + spring_damper_tuple_ = {}; + body_indeces_fixed_constraint_ = {}; + position_solid_body_tuple_ = {}; + position_scale_solid_body_tuple_ = {}; + translation_solid_body_tuple_ = {}; +}; + +/////////////////////////////////////// +/* StructuralSimulation members */ +/////////////////////////////////////// + +StructuralSimulation::StructuralSimulation(StructuralSimulationInput& input): + // generic input + relative_input_path_(input.relative_input_path_), + imported_stl_list_(input.imported_stl_list_), + scale_stl_(input.scale_stl_), + translation_list_(input.translation_list_), + resolution_list_(input.resolution_list_), + material_model_list_(input.material_model_list_), + physical_viscosity_(input.physical_viscosity_), + contacting_body_pairs_list_(input.contacting_body_pairs_list_), + time_dep_contacting_body_pairs_list_(input.time_dep_contacting_body_pairs_list_), + + // default system, optional: particle relaxation, scale_system_boundaries + particle_relaxation_list_(input.particle_relaxation_list_), + system_resolution_(0.0), + system_(SPHSystem(BoundingBox(Vec3d(0), Vec3d(0)), system_resolution_)), + scale_system_boundaries_(input.scale_system_boundaries_), + in_output_(In_Output(system_)), + + // optional: boundary conditions + non_zero_gravity_(input.non_zero_gravity_), + acceleration_bounding_box_tuple_(input.acceleration_bounding_box_tuple_), + spring_damper_tuple_(input.spring_damper_tuple_), + body_indeces_fixed_constraint_(input.body_indeces_fixed_constraint_), + position_solid_body_tuple_(input.position_solid_body_tuple_), + position_scale_solid_body_tuple_(input.position_scale_solid_body_tuple_), + translation_solid_body_tuple_(input.translation_solid_body_tuple_) +{ + // scaling of translation and resolution + scaleTranslationAndResolution(); + // set the default resolution to the max in the resolution list + setSystemResolutionMax(); + // create the body mesh list for triangular mesh shapes storage + createBodyMeshList(); + // create the particle adaptions for the bodies + createParticleAdaptationList(); + // set up the system + calculateSystemBoundaries(); + system_.run_particle_relaxation_ = true; + // initialize solid bodies with their properties + initializeElasticSolidBodies(); + // contacts + initializeAllContacts(); + + // boundary conditions + initializeGravity(); + initializeAccelerationForBodyPartInBoundingBox(); + initializeSpringDamperConstraintParticleWise(); + initializeConstrainSolidBodyRegion(); + initializePositionSolidBody(); + initializePositionScaleSolidBody(); + initializeTranslateSolidBody(); + + // initialize simulation + initializeSimulation(); +} + +StructuralSimulation::~StructuralSimulation() +{} + +void StructuralSimulation::scaleTranslationAndResolution() +{ + // scale the translation_list_, system_resolution_ and resolution_list_ + for (size_t i = 0; i < translation_list_.size(); i++) + { + translation_list_[i] *= scale_stl_; + } + system_resolution_ *= scale_stl_; + for (size_t i = 0; i < resolution_list_.size(); i++) + { + resolution_list_[i] *= scale_stl_; + } +} + +void StructuralSimulation::setSystemResolutionMax() +{ + system_resolution_ = 0.0; + for (size_t i = 0; i < resolution_list_.size(); i++) + { + if (system_resolution_ < resolution_list_[i]) + { + system_resolution_ = resolution_list_[i]; + } + } + system_.resolution_ref_ = system_resolution_; +} + +void StructuralSimulation::calculateSystemBoundaries() +{ + // calculate system bounds from all bodies + for (size_t i = 0; i < body_mesh_list_.size(); i++) + { + BoundingBox additional = body_mesh_list_[i].findBounds(); + expandBoundingBox(&system_.system_domain_bounds_, &additional); + } + // scale the system bounds around the center point + Vecd center_point = (system_.system_domain_bounds_.first + system_.system_domain_bounds_.second) * 0.5; + + Vecd distance_first = system_.system_domain_bounds_.first - center_point; + Vecd distance_second = system_.system_domain_bounds_.second - center_point; + + system_.system_domain_bounds_.first = center_point + distance_first * scale_system_boundaries_; + system_.system_domain_bounds_.second = center_point + distance_second * scale_system_boundaries_; +} + +void StructuralSimulation::createBodyMeshList() +{ + body_mesh_list_ = {}; + for (size_t i = 0; i < imported_stl_list_.size(); i++) + { + string relative_input_path_copy = relative_input_path_; + body_mesh_list_.push_back(TriangleMeshShape(relative_input_path_copy.append(imported_stl_list_[i]), translation_list_[i], scale_stl_)); + } +} + +void StructuralSimulation::createParticleAdaptationList() +{ + particle_adaptation_list_ = {}; + for (size_t i = 0; i < resolution_list_.size(); i++) + { + Real system_resolution_ratio = system_resolution_ / resolution_list_[i]; + // for solid bodies, slightly small h_spaing_ratio is used + particle_adaptation_list_.push_back(ParticleAdaptation(1.15, system_resolution_ratio)); + } +} + +void StructuralSimulation::initializeElasticSolidBodies() +{ + solid_body_list_ = {}; + for (size_t i = 0; i < body_mesh_list_.size(); i++) + { + solid_body_list_.emplace_back(make_shared(system_, imported_stl_list_[i], body_mesh_list_[i], particle_adaptation_list_[i], physical_viscosity_, material_model_list_[i])); + if (particle_relaxation_list_[i]) + { + relaxParticlesSingleResolution(&in_output_, false, solid_body_list_[i]->getImportedModel(), solid_body_list_[i]->getElasticSolidParticles(), solid_body_list_[i]->getInnerBodyRelation()); + } + } +} + +void StructuralSimulation::initializeContactBetweenTwoBodies(int first, int second) +{ + ImportedModel* first_body = solid_body_list_[first]->getImportedModel(); + ImportedModel* second_body = solid_body_list_[second]->getImportedModel(); + + SolidBodyRelationContact* first_contact = new SolidBodyRelationContact(first_body, {second_body}); + SolidBodyRelationContact* second_contact = new SolidBodyRelationContact(second_body, {first_body}); + + contact_list_.emplace_back(first_contact); + contact_list_.emplace_back(second_contact); + + contact_density_list_.emplace_back(make_shared(first_contact)); + contact_density_list_.emplace_back(make_shared(second_contact)); + + contact_force_list_.emplace_back(make_shared(first_contact)); + contact_force_list_.emplace_back(make_shared(second_contact)); +} + +void StructuralSimulation::initializeAllContacts() +{ + contact_list_ = {}; + contact_density_list_ = {}; + contact_force_list_ = {}; + for (size_t i = 0; i < contacting_body_pairs_list_.size(); i++) + { + initializeContactBetweenTwoBodies(contacting_body_pairs_list_[i][0], contacting_body_pairs_list_[i][1]); + } + for (size_t i = 0; i < time_dep_contacting_body_pairs_list_.size(); i++) + { + int body_1 = time_dep_contacting_body_pairs_list_[i].first[0]; + int body_2 = time_dep_contacting_body_pairs_list_[i].first[1]; + initializeContactBetweenTwoBodies(body_1, body_2); //vector with first element being array with indices + } +} + +void StructuralSimulation::initializeGravity() +{ + // collect all the body indeces with non-zero gravity + vector gravity_indeces = {}; + for (size_t i = 0; i < non_zero_gravity_.size(); i++) + { + gravity_indeces.push_back(non_zero_gravity_[i].first); + } + // initialize gravity + initialize_gravity_ = {}; + size_t gravity_index_i = 0; // iterating through gravity_indeces + for (size_t i = 0; i < solid_body_list_.size(); i++) + { + // check if i is in indeces_gravity + if ( count(gravity_indeces.begin(), gravity_indeces.end(), i) ) + { + initialize_gravity_.emplace_back(make_shared(solid_body_list_[i]->getImportedModel(), new Gravity(non_zero_gravity_[gravity_index_i].second))); + gravity_index_i++; + } + else + { + initialize_gravity_.emplace_back(make_shared(solid_body_list_[i]->getImportedModel())); + } + } +} + +void StructuralSimulation::initializeAccelerationForBodyPartInBoundingBox() +{ + acceleration_bounding_box_ = {}; + for (size_t i = 0; i < acceleration_bounding_box_tuple_.size(); i++) + { + SolidBody* solid_body = solid_body_list_[get<0>(acceleration_bounding_box_tuple_[i])]->getImportedModel(); + acceleration_bounding_box_.emplace_back(make_shared + (solid_body, &get<1>(acceleration_bounding_box_tuple_[i]), get<2>(acceleration_bounding_box_tuple_[i]))); + } +} + +void StructuralSimulation::initializeSpringDamperConstraintParticleWise() +{ + spring_damper_constraint_ = {}; + for (size_t i = 0; i < spring_damper_tuple_.size(); i++) + { + SolidBody* solid_body = solid_body_list_[get<0>(spring_damper_tuple_[i])]->getImportedModel(); + spring_damper_constraint_.emplace_back(make_shared(solid_body, get<1>(spring_damper_tuple_[i]), get<2>(spring_damper_tuple_[i]))); + } +} + +void StructuralSimulation::initializeConstrainSolidBodyRegion() +{ + fixed_constraint_ = {}; + for (size_t i = 0; i < body_indeces_fixed_constraint_.size(); i++) + { + int body_index = body_indeces_fixed_constraint_[i]; + BodyPartByParticleTriMesh* bp = new BodyPartByParticleTriMesh(solid_body_list_[body_index]->getImportedModel(), imported_stl_list_[body_index], &body_mesh_list_[body_index]); + fixed_constraint_.emplace_back(make_shared(solid_body_list_[body_index]->getImportedModel(), bp)); + } +} + +void StructuralSimulation::initializePositionSolidBody() +{ + position_solid_body_ = {}; + for (size_t i = 0; i < position_solid_body_tuple_.size(); i++) + { + int body_index = get<0>(position_solid_body_tuple_[i]); + Real start_time = get<1>(position_solid_body_tuple_[i]); + Real end_time = get<2>(position_solid_body_tuple_[i]); + Vecd pos_end_center = get<3>(position_solid_body_tuple_[i]); + BodyPartByParticleTriMesh* bp = new BodyPartByParticleTriMesh(solid_body_list_[body_index]->getImportedModel(), imported_stl_list_[body_index], &body_mesh_list_[body_index]); + + position_solid_body_.emplace_back(make_shared(solid_body_list_[body_index]->getImportedModel(), bp, start_time, end_time, pos_end_center)); + } +} + +void StructuralSimulation::initializePositionScaleSolidBody() +{ + position_scale_solid_body_ = {}; + for (size_t i = 0; i < position_scale_solid_body_tuple_.size(); i++) + { + int body_index = get<0>(position_scale_solid_body_tuple_[i]); + Real start_time = get<1>(position_scale_solid_body_tuple_[i]); + Real end_time = get<2>(position_scale_solid_body_tuple_[i]); + Real scale = get<3>(position_scale_solid_body_tuple_[i]); + BodyPartByParticleTriMesh* bp = new BodyPartByParticleTriMesh(solid_body_list_[body_index]->getImportedModel(), imported_stl_list_[body_index], &body_mesh_list_[body_index]); + + position_scale_solid_body_.emplace_back(make_shared(solid_body_list_[body_index]->getImportedModel(), bp, start_time, end_time, scale)); + } +} + +void StructuralSimulation::initializeTranslateSolidBody() +{ + translation_solid_body_ = {}; + for (size_t i = 0; i < translation_solid_body_tuple_.size(); i++) + { + int body_index = get<0>(translation_solid_body_tuple_[i]); + Real start_time = get<1>(translation_solid_body_tuple_[i]); + Real end_time = get<2>(translation_solid_body_tuple_[i]); + Vecd translation = get<3>(translation_solid_body_tuple_[i]); + BodyPartByParticleTriMesh* bp = new BodyPartByParticleTriMesh(solid_body_list_[body_index]->getImportedModel(), imported_stl_list_[body_index], &body_mesh_list_[body_index]); + + translation_solid_body_.emplace_back(make_shared(solid_body_list_[body_index]->getImportedModel(), bp, start_time, end_time, translation)); + } +} + +void StructuralSimulation::executeCorrectConfiguration() +{ + for (size_t i = 0; i < solid_body_list_.size(); i++) + { + solid_body_list_[i]->getCorrectConfiguration()->parallel_exec(); + } +} + +void StructuralSimulation::executeinitializeATimeStep() +{ + for (size_t i = 0; i < initialize_gravity_.size(); i++) + { + initialize_gravity_[i]->parallel_exec(); + } +} + +void StructuralSimulation::executeAccelerationForBodyPartInBoundingBox() +{ + for (size_t i = 0; i < acceleration_bounding_box_.size(); i++) + { + acceleration_bounding_box_[i]->parallel_exec(); + } +} + +void StructuralSimulation::executeSpringDamperConstraintParticleWise() +{ + for (size_t i = 0; i < spring_damper_constraint_.size(); i++) + { + spring_damper_constraint_[i]->parallel_exec(); + } +} + +void StructuralSimulation::executeContactDensitySummation() +{ + // number of contacts that are not time dependent: contact pairs * 2 + size_t number_of_general_contacts = contacting_body_pairs_list_.size() * 2; + for (size_t i = 0; i < contact_density_list_.size(); i++) + { + if (i < number_of_general_contacts) + { + contact_density_list_[i]->parallel_exec(); + } + else + { + // index of the time dependent contact body pair + // for i = 0, 1 --> index = 0, i = 2, 3 --> index = 1, and so on.. + int index = (i - number_of_general_contacts) / 2; + Real start_time = time_dep_contacting_body_pairs_list_[index].second[0]; + Real end_time = time_dep_contacting_body_pairs_list_[index].second[1]; + if(GlobalStaticVariables::physical_time_ >= start_time && GlobalStaticVariables::physical_time_ <= end_time) + { + contact_density_list_[i]->parallel_exec(); + } + } + } +} + +void StructuralSimulation::executeContactForce() +{ + // number of contacts that are not time dependent: contact pairs * 2 + size_t number_of_general_contacts = contacting_body_pairs_list_.size() * 2; + for (size_t i = 0; i < contact_force_list_.size(); i++) + { + if (i < number_of_general_contacts) + { + contact_force_list_[i]->parallel_exec(); + } + else + { + // index of the time dependent contact body pair + // for i = 0, 1 --> index = 0, i = 2, 3 --> index = 1, and so on.. + int index = (i - number_of_general_contacts) / 2; + Real start_time = time_dep_contacting_body_pairs_list_[index].second[0]; + Real end_time = time_dep_contacting_body_pairs_list_[index].second[1]; + if(GlobalStaticVariables::physical_time_ >= start_time && GlobalStaticVariables::physical_time_ <= end_time) + { + contact_force_list_[i]->parallel_exec(); + } + } + } +} + +void StructuralSimulation::executeStressRelaxationFirstHalf(Real dt) +{ + for (size_t i = 0; i < solid_body_list_.size(); i++) + { + solid_body_list_[i]->getStressRelaxationFirstHalf()->parallel_exec(dt); + } +} + +void StructuralSimulation::executeConstrainSolidBodyRegion() +{ + for (size_t i = 0; i < fixed_constraint_.size(); i++) + { + fixed_constraint_[i]->parallel_exec(); + } +} + +void StructuralSimulation::executePositionSolidBody(Real dt) +{ + for (size_t i = 0; i < position_solid_body_.size(); i++) + { + position_solid_body_[i]->parallel_exec(dt); + } +} + +void StructuralSimulation::executePositionScaleSolidBody(Real dt) +{ + for (size_t i = 0; i < position_scale_solid_body_.size(); i++) + { + position_scale_solid_body_[i]->parallel_exec(dt); + } +} + +void StructuralSimulation::executeTranslateSolidBody(Real dt) +{ + for (size_t i = 0; i < translation_solid_body_.size(); i++) + { + translation_solid_body_[i]->parallel_exec(dt); + } +} + +void StructuralSimulation::executeDamping(Real dt) +{ + for (size_t i = 0; i < solid_body_list_.size(); i++) + { + solid_body_list_[i]->getDampingWithRandomChoice()->parallel_exec(dt); + } +} + +void StructuralSimulation::executeStressRelaxationSecondHalf(Real dt) +{ + for (size_t i = 0; i < solid_body_list_.size(); i++) + { + solid_body_list_[i]->getStressRelaxationSecondHalf()->parallel_exec(dt); + } +} + +void StructuralSimulation::executeUpdateCellLinkedList() +{ + for (size_t i = 0; i < solid_body_list_.size(); i++) + { + solid_body_list_[i]->getImportedModel()->updateCellLinkedList(); + } +} + +void StructuralSimulation::executeContactUpdateConfiguration() +{ + // number of contacts that are not time dependent: contact pairs * 2 + size_t number_of_general_contacts = contacting_body_pairs_list_.size() * 2; + for (size_t i = 0; i < contact_list_.size(); i++) + { + // general contacts = contacting_bodies * 2 + if (i < number_of_general_contacts) + { + contact_list_[i]->updateConfiguration(); + } + // time dependent contacts = time dep. contacting_bodies * 2 + else + { + // index of the time dependent contact body pair + // for i = 0, 1 --> index = 0, i = 2, 3 --> index = 1, and so on.. + int index = (i - number_of_general_contacts) / 2; + Real start_time = time_dep_contacting_body_pairs_list_[index].second[0]; + Real end_time = time_dep_contacting_body_pairs_list_[index].second[1]; + if(GlobalStaticVariables::physical_time_ >= start_time && GlobalStaticVariables::physical_time_ <= end_time) + { + contact_list_[i]->updateConfiguration(); + } + } + } +} + +void StructuralSimulation::runSimulationStep(int &ite, Real &dt, Real &integration_time) +{ + if (ite % 100 == 0) cout << "N=" << ite << " Time: " << GlobalStaticVariables::physical_time_ << " dt: " << dt << "\n"; + + /** ACTIVE BOUNDARY CONDITIONS */ + executeinitializeATimeStep(); + executeAccelerationForBodyPartInBoundingBox(); + executeSpringDamperConstraintParticleWise(); + + /** CONTACT */ + executeContactDensitySummation(); + executeContactForce(); + + /** STRESS RELAXATOIN, DAMPING, POSITIONAL CONSTRAINTS */ + executeStressRelaxationFirstHalf(dt); + + executeConstrainSolidBodyRegion(); + executePositionSolidBody(dt); + executePositionScaleSolidBody(dt); + executeTranslateSolidBody(dt); + + executeDamping(dt); + + executeConstrainSolidBodyRegion(); + executePositionSolidBody(dt); + executePositionScaleSolidBody(dt); + executeTranslateSolidBody(dt); + + executeStressRelaxationSecondHalf(dt); + + /** UPDATE TIME STEP SIZE, INCREMENT */ + ite++; + dt = system_.getSmallestTimeStepAmongSolidBodies(); + integration_time += dt; + GlobalStaticVariables::physical_time_ += dt; + + /** UPDATE BODIES CELL LINKED LISTS */ + executeUpdateCellLinkedList(); + + /** UPDATE CONTACT CONFIGURATION */ + executeContactUpdateConfiguration(); +} + +void StructuralSimulation::runSimulation(Real end_time) +{ + BodyStatesRecordingToVtu write_states(in_output_, system_.real_bodies_); + GlobalStaticVariables::physical_time_ = 0.0; + + /** INITIALALIZE SYSTEM */ + system_.initializeSystemCellLinkedLists(); + system_.initializeSystemConfigurations(); + + /** INITIAL CONDITION */ + executeCorrectConfiguration(); + + /** Statistics for computing time. */ + write_states.writeToFile(0); + int ite = 0; + Real output_period = end_time / 100.0; + Real dt = 0.0; + tick_count t1 = tick_count::now(); + tick_count::interval_t interval; + /** Main loop */ + while (GlobalStaticVariables::physical_time_ < end_time) + { + Real integration_time = 0.0; + while (integration_time < output_period) + { + runSimulationStep(ite, dt, integration_time); + } + tick_count t2 = tick_count::now(); + write_states.writeToFile(); + tick_count t3 = tick_count::now(); + interval += t3 - t2; + } + tick_count t4 = tick_count::now(); + tick_count::interval_t tt; + tt = t4 - t1 - interval; + cout << "Total wall time for computation: " << tt.seconds() << " seconds." << endl; +} + +void StructuralSimulation::initializeSimulation() +{ + /** INITIALALIZE SYSTEM */ + system_.initializeSystemCellLinkedLists(); + system_.initializeSystemConfigurations(); + + /** INITIAL CONDITION */ + executeCorrectConfiguration(); +} + +double StructuralSimulation::runSimulationFixedDurationJS(int number_of_steps) +{ + BodyStatesRecordingToVtu write_states(in_output_, system_.real_bodies_); + GlobalStaticVariables::physical_time_ = 0.0; + + /** Statistics for computing time. */ + write_states.writeToFile(0); + int output_period = 100; + int ite = 0; + Real dt = 0.0; + tick_count t1 = tick_count::now(); + tick_count::interval_t interval; + /** Main loop */ + while (ite < number_of_steps) + { + Real integration_time = 0.0; + int output_step = 0; + while (output_step < output_period) + { + runSimulationStep(ite, dt, integration_time); + output_step++; + } + tick_count t2 = tick_count::now(); + write_states.writeToFile(); + tick_count t3 = tick_count::now(); + interval += t3 - t2; + } + tick_count t4 = tick_count::now(); + tick_count::interval_t tt; + tt = t4 - t1 - interval; + return tt.seconds(); +} \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.h b/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.h new file mode 100644 index 0000000000..b66783c9fa --- /dev/null +++ b/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.h @@ -0,0 +1,215 @@ +/** +* @file solid_structural_simulation_class.h +* @brief solid structural simulation class definition +* @details solid structural simulation class for general structural simulations +* @author Bence Z. Rochlitz +*/ + +#ifndef SOLID_STRUCTURAL_SIMULATION_CLASS_H +#define SOLID_STRUCTURAL_SIMULATION_CLASS_H + +#include "sphinxsys.h" +#include +#include +#include + +using namespace SPH; +using namespace std; +using GravityPair = pair; +using AccelTuple = tuple; +using SpringDamperTuple = tuple; +using PositionSolidBodyTuple = tuple; +using PositionScaleSolidBodyTuple = tuple; +using TranslateSolidBodyTuple = tuple; + +class BodyPartByParticleTriMesh : public BodyPartByParticle +{ +public: + BodyPartByParticleTriMesh(SPHBody* body, string body_part_name, TriangleMeshShape* triangle_mesh_shape); + ~BodyPartByParticleTriMesh(); +}; + +class ImportedModel : public SolidBody +{ +public: + ImportedModel(SPHSystem &system, string body_name, TriangleMeshShape* triangle_mesh_shape, ParticleAdaptation* particle_adaptation); + ~ImportedModel(); +}; + +class SolidBodyForSimulation +{ +private: + ImportedModel imported_model_; + //LinearElasticSolid material_model_; + ElasticSolidParticles elastic_solid_particles_; + BodyRelationInner inner_body_relation_; + + solid_dynamics::CorrectConfiguration correct_configuration_; + solid_dynamics::StressRelaxationFirstHalf stress_relaxation_first_half_; + solid_dynamics::StressRelaxationSecondHalf stress_relaxation_second_half_; + DampingWithRandomChoice> damping_random_; + +public: + SolidBodyForSimulation(SPHSystem &system, string body_name, TriangleMeshShape& triangle_mesh_shape, ParticleAdaptation& particle_adaptation, Real physical_viscosity, LinearElasticSolid& material_model); + ~SolidBodyForSimulation(){}; + + ImportedModel* getImportedModel() { return &imported_model_; }; + //LinearElasticSolid* GetMaterialModel() { return &material_model_; }; + ElasticSolidParticles* getElasticSolidParticles() { return &elastic_solid_particles_; }; + BodyRelationInner* getInnerBodyRelation() { return &inner_body_relation_; }; + + solid_dynamics::CorrectConfiguration* getCorrectConfiguration() { return &correct_configuration_; }; + solid_dynamics::StressRelaxationFirstHalf* getStressRelaxationFirstHalf() { return &stress_relaxation_first_half_; }; + solid_dynamics::StressRelaxationSecondHalf* getStressRelaxationSecondHalf() { return &stress_relaxation_second_half_; }; + DampingWithRandomChoice>* getDampingWithRandomChoice() { return &damping_random_; }; +}; + +void expandBoundingBox(BoundingBox* original, BoundingBox* additional); + +void relaxParticlesSingleResolution(In_Output* in_output, + bool write_particles_to_file, + ImportedModel* imported_model, + ElasticSolidParticles* imported_model_particles, + BodyRelationInner* imported_model_inner); + + +class StructuralSimulationInput +{ +public: + string relative_input_path_; + vector imported_stl_list_; + Real scale_stl_; + vector translation_list_; + vector resolution_list_; + vector material_model_list_; + Real physical_viscosity_; + vector> contacting_body_pairs_list_; + vector, array>> time_dep_contacting_body_pairs_list_; + // scale system boundaries + Real scale_system_boundaries_; + // particle relaxation + vector particle_relaxation_list_; + // boundary conditions + vector non_zero_gravity_; + vector acceleration_bounding_box_tuple_; + vector spring_damper_tuple_; + vector body_indeces_fixed_constraint_; + vector position_solid_body_tuple_; + vector position_scale_solid_body_tuple_; + vector translation_solid_body_tuple_; + + StructuralSimulationInput( + string relative_input_path, + vector imported_stl_list, + Real scale_stl, + vector translation_list, + vector resolution_list, + vector material_model_list, + Real physical_viscosity, + vector> contacting_bodies_list + ); +}; + +class StructuralSimulation + { + protected: + // mandatory input + string relative_input_path_; + vector imported_stl_list_; + Real scale_stl_; + vector translation_list_; + vector resolution_list_; + vector material_model_list_; + Real physical_viscosity_; + vector> contacting_body_pairs_list_; + vector, array>> time_dep_contacting_body_pairs_list_; //optional: time dependent contact + vector particle_relaxation_list_; // optional: particle relaxation + + // internal members + Real system_resolution_; + SPHSystem system_; + Real scale_system_boundaries_; + In_Output in_output_; + + vector body_mesh_list_; + vector particle_adaptation_list_; + vector> solid_body_list_; + + vector> contact_list_; + vector> contact_density_list_; + vector> contact_force_list_; + + // for initializeATimeStep + vector> initialize_gravity_; + vector non_zero_gravity_; + // for AccelerationForBodyPartInBoundingBox + vector> acceleration_bounding_box_; + vector acceleration_bounding_box_tuple_; + // for SpringDamperConstraintParticleWise + vector> spring_damper_constraint_; + vector spring_damper_tuple_; + // for ConstrainSolidBodyRegion + vector> fixed_constraint_; + vector body_indeces_fixed_constraint_; + // for PositionSolidBody + vector> position_solid_body_; + vector position_solid_body_tuple_; + // for PositionScaleSolidBody + vector> position_scale_solid_body_; + vector position_scale_solid_body_tuple_; + // for TranslateSolidBody + vector> translation_solid_body_; + vector translation_solid_body_tuple_; + + // for constructor, the order is important + void scaleTranslationAndResolution(); + void setSystemResolutionMax(); + void createBodyMeshList(); + void createParticleAdaptationList(); + void calculateSystemBoundaries(); + void initializeElasticSolidBodies(); + void initializeContactBetweenTwoBodies(int first, int second); + void initializeAllContacts(); + + // for initializeBoundaryConditions + void initializeGravity(); + void initializeAccelerationForBodyPartInBoundingBox(); + void initializeSpringDamperConstraintParticleWise(); + void initializeConstrainSolidBodyRegion(); + void initializePositionSolidBody(); + void initializePositionScaleSolidBody(); + void initializeTranslateSolidBody(); + + // for runSimulation, the order is important + void executeCorrectConfiguration(); + void executeinitializeATimeStep(); + void executeAccelerationForBodyPartInBoundingBox(); + void executeSpringDamperConstraintParticleWise(); + void executeContactDensitySummation(); + void executeContactForce(); + void executeStressRelaxationFirstHalf(Real dt); + void executeConstrainSolidBodyRegion(); + void executePositionSolidBody(Real dt); + void executePositionScaleSolidBody(Real dt); + void executeTranslateSolidBody(Real dt); + void executeDamping(Real dt); + void executeStressRelaxationSecondHalf(Real dt); + void executeUpdateCellLinkedList(); + void executeContactUpdateConfiguration(); + void runSimulationStep(int &ite, Real &dt, Real &integration_time); + + // initialize simulation + void initializeSimulation(); + + public: + StructuralSimulation(StructuralSimulationInput& input); + ~StructuralSimulation(); + + //For c++ + void runSimulation(Real end_time); + + //For JS + double runSimulationFixedDurationJS(int number_of_steps); + }; + +#endif //SOLID_STRUCTURAL_SIMULATION_CLASS_H \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/high_level_simulation_class/test_structural_simulation_class.h b/SPHINXsys/src/for_3D_build/high_level_simulation_class/test_structural_simulation_class.h new file mode 100644 index 0000000000..6019e27559 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/high_level_simulation_class/test_structural_simulation_class.h @@ -0,0 +1,52 @@ +#include "solid_structural_simulation_class.h" + +Real tolerance = 1e-6; + +class TestStructuralSimulation : StructuralSimulation +{ +public: + TestStructuralSimulation(StructuralSimulationInput& input) : StructuralSimulation(input){}; + void TestRunSimulation(Real end_time){ runSimulation(end_time); }; + // input members + string Get_relative_input_path_(){ return relative_input_path_; }; + vector Get_imported_stl_list_(){ return imported_stl_list_; }; + Real Get_scale_stl_(){ return scale_stl_; }; + vector Get_translation_list_(){ return translation_list_; }; + Real Get_system_resolution_(){ return system_resolution_; }; + vector Get_resolution_list_(){ return resolution_list_; }; + vector Get_material_model_list_(){ return material_model_list_; }; + Real Get_physical_viscosity_(){ return physical_viscosity_; }; + // internal members + SPHSystem Get_system_(){ return system_; }; + Real Get_scale_system_boundaries_(){ return scale_system_boundaries_; }; + In_Output Get_in_output_(){ return in_output_; }; + // other + vector Get_body_mesh_list_(){ return body_mesh_list_; }; + vector> Get_solid_body_list_(){ return solid_body_list_; }; + vector> Get_contacting_body_pairs_list_(){ return contacting_body_pairs_list_; }; + vector, array>> Get_time_dep_contacting_body_pairs_list_(){ return time_dep_contacting_body_pairs_list_; }; + vector> Get_contact_list_(){ return contact_list_; }; + vector> Get_contact_density_list_(){ return contact_density_list_; }; + vector> Get_contact_force_list_(){ return contact_force_list_; }; + // for initializeATimeStep + vector> Get_initialize_gravity_(){ return initialize_gravity_; }; + vector Get_non_zero_gravity_(){ return non_zero_gravity_; }; + // for AccelerationForBodyPartInBoundingBox + vector> Get_acceleration_bounding_box_(){ return acceleration_bounding_box_; }; + vector Get_acceleration_bounding_box_tuple_(){ return acceleration_bounding_box_tuple_; }; + // for SpringDamperConstraintParticleWise + vector> Get_spring_damper_constraint_(){ return spring_damper_constraint_; }; + vector Get_spring_damper_tuple_(){ return spring_damper_tuple_; }; + // for ConstrainSolidBodyRegion + vector> Get_fixed_constraint_(){ return fixed_constraint_; }; + vector Get_body_indeces_fixed_constraint_(){ return body_indeces_fixed_constraint_; }; + // for PositionSolidBody + vector> Get_position_solid_body_(){ return position_solid_body_; }; + vector Get_position_solid_body_tuple_(){ return position_solid_body_tuple_; }; + // for PositionScaleSolidBody + vector> Get_position_scale_solid_body_(){ return position_scale_solid_body_; }; + vector Get_position_scale_solid_body_tuple_(){ return position_scale_solid_body_tuple_; }; + // for TranslateSolidBody + vector> Get_translation_solid_body_(){ return translation_solid_body_; }; + vector Get_translation_solid_body_tuple_(){ return translation_solid_body_tuple_; }; +}; \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/meshes/CMakeLists.txt b/SPHINXsys/src/for_3D_build/meshes/CMakeLists.txt new file mode 100644 index 0000000000..fd5eae60fd --- /dev/null +++ b/SPHINXsys/src/for_3D_build/meshes/CMakeLists.txt @@ -0,0 +1,7 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/meshes/base_mesh_supplementary.cpp b/SPHINXsys/src/for_3D_build/meshes/base_mesh_supplementary.cpp new file mode 100644 index 0000000000..bad75a3cb5 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/meshes/base_mesh_supplementary.cpp @@ -0,0 +1,56 @@ +/** + * @file base_mesh_supplementary.cpp + * @author Luhui Han, Chi ZHang Yongchuan YU and Xiangyu Hu + */ + +#include "base_mesh.h" + +namespace SPH { + //=================================================================================================// + void MeshIterator(const Vec3u &index_begin, const Vec3u &index_end, MeshFunctor& mesh_functor, Real dt) + { + for (size_t i = index_begin[0]; i != index_end[0]; ++i) + for (size_t j = index_begin[1]; j != index_end[1]; ++j) + for (size_t k = index_begin[2]; k != index_end[2]; ++k) + { + mesh_functor(Vecu(i, j, k), dt); + } + } + //=================================================================================================// + void MeshIterator_parallel(const Vecu &index_begin, const Vecu &index_end, MeshFunctor& mesh_functor, Real dt) + { + parallel_for(blocked_range3d + (index_begin[0], index_end[0], index_begin[1], index_end[1], index_begin[2], index_end[2]), + [&](const blocked_range3d& r) { + for (size_t i = r.pages().begin(); i != r.pages().end(); ++i) + for (size_t j = r.rows().begin(); j != r.rows().end(); ++j) + for (size_t k = r.cols().begin(); k != r.cols().end(); ++k) + { + mesh_functor(Vecu(i, j, k), dt); + } + }, ap); + } + //=================================================================================================// + Vecu BaseMesh::transfer1DtoMeshIndex(const Vecu &number_of_grid_points, size_t i) + { + size_t row_times_column_size = number_of_grid_points[1] * number_of_grid_points[2]; + size_t page = i / row_times_column_size; + size_t left_over = (i - page * row_times_column_size); + size_t row_size = number_of_grid_points[2]; + size_t column = left_over / row_size; + return Vecu(page, column, left_over - column * row_size); + } + //=================================================================================================// + size_t BaseMesh::transferMeshIndexTo1D(const Vecu &number_of_grid_points, const Vecu &grid_index) + { + return grid_index[0] * number_of_grid_points[1] * number_of_grid_points[2] + + grid_index[1] * number_of_grid_points[2] + + grid_index[2]; + } + //=================================================================================================// + size_t BaseMesh::transferMeshIndexToMortonOrder(const Vecu &grid_index) + { + return MortonCode(grid_index[0]) | (MortonCode(grid_index[1]) << 1) + | (MortonCode(grid_index[2]) << 2); + } +} diff --git a/SPHINXsys/src/for_3D_build/meshes/cell_linked_list.hpp b/SPHINXsys/src/for_3D_build/meshes/cell_linked_list.hpp new file mode 100644 index 0000000000..09aedb83b7 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/meshes/cell_linked_list.hpp @@ -0,0 +1,51 @@ +/** + * @file body_relation.hpp + * @brief Here, Functions belong to BaseBody, RealBody and FictitiousBody are given. + * @author hi ZHang and Xiangyu Hu + */ + +#pragma once + +#include "body_relation.h" +#include "base_particles.h" +#include "base_kernel.h" +#include "cell_linked_list.h" + +namespace SPH +{ + //=================================================================================================// + template + void CellLinkedList::searchNeighborsByParticles(size_t total_real_particles, BaseParticles& source_particles, + ParticleConfiguration& particle_configuration, GetParticleIndex& get_particle_index, + GetSearchDepth& get_search_depth, GetNeighborRelation& get_neighbor_relation) + { + parallel_for(blocked_range(0, total_real_particles), + [&](const blocked_range& r) { + StdLargeVec& pos_n = source_particles.pos_n_; + for (size_t num = r.begin(); num != r.end(); ++num) { + size_t index_i = get_particle_index(num); + Vecd& particle_position = pos_n[index_i]; + int search_depth = get_search_depth(index_i); + Vecu target_cell_index = CellIndexFromPosition(particle_position); + int i = (int)target_cell_index[0]; + int j = (int)target_cell_index[1]; + int k = (int)target_cell_index[2]; + + Neighborhood& neighborhood = particle_configuration[index_i]; + for (int l = SMAX(i - search_depth, 0); l <= SMIN(i + search_depth, int(number_of_cells_[0]) - 1); ++l) + for (int m = SMAX(j - search_depth, 0); m <= SMIN(j + search_depth, int(number_of_cells_[1]) - 1); ++m) + for (int q = SMAX(k - search_depth, 0); q <= SMIN(k + search_depth, int(number_of_cells_[2]) - 1); ++q) + { + ListDataVector& target_particles = cell_linked_lists_[l][m][q].cell_list_data_; + for (const ListData& list_data : target_particles) + { + //displacement pointing from neighboring particle to origin particle + Vecd displacement = particle_position - list_data.second; + get_neighbor_relation(neighborhood, displacement, index_i, list_data.first); + } + } + } + }, ap); + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/for_3D_build/meshes/cell_linked_list_supplementary.cpp b/SPHINXsys/src/for_3D_build/meshes/cell_linked_list_supplementary.cpp new file mode 100644 index 0000000000..e583501015 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/meshes/cell_linked_list_supplementary.cpp @@ -0,0 +1,326 @@ +#include "cell_linked_list.h" +#include "base_kernel.h" +#include "base_body.h" +#include "base_particles.h" +#include "neighbor_relation.h" + +namespace SPH +{ + //=================================================================================================// + CellList::CellList() + { + concurrent_particle_indexes_.reserve(36); + } + //=================================================================================================// + void CellLinkedList ::allocateMeshDataMatrix() + { + Allocate3dArray(cell_linked_lists_, number_of_cells_); + } + //=================================================================================================// + void CellLinkedList ::deleteMeshDataMatrix() + { + Delete3dArray(cell_linked_lists_, number_of_cells_); + } + //=================================================================================================// + void CellLinkedList::clearCellLists() + { + parallel_for( + blocked_range3d(0, number_of_cells_[0], 0, number_of_cells_[1], 0, number_of_cells_[2]), + [&](const blocked_range3d &r) + { + for (size_t i = r.pages().begin(); i != r.pages().end(); ++i) + for (size_t j = r.rows().begin(); j != r.rows().end(); ++j) + for (size_t k = r.cols().begin(); k != r.cols().end(); ++k) + { + cell_linked_lists_[i][j][k].concurrent_particle_indexes_.clear(); + cell_linked_lists_[i][j][k].real_particle_indexes_.clear(); + } + }, + ap); + } + //=================================================================================================// + void CellLinkedList::UpdateCellListData() + { + StdLargeVec &pos_n = base_particles_->pos_n_; + parallel_for( + blocked_range3d(0, number_of_cells_[0], 0, number_of_cells_[1], 0, number_of_cells_[2]), + [&](const blocked_range3d &r) + { + for (size_t i = r.pages().begin(); i != r.pages().end(); ++i) + for (size_t j = r.rows().begin(); j != r.rows().end(); ++j) + for (size_t k = r.cols().begin(); k != r.cols().end(); ++k) + { + CellList &cell_list = cell_linked_lists_[i][j][k]; + cell_list.cell_list_data_.clear(); + for (size_t s = 0; s != cell_list.concurrent_particle_indexes_.size(); ++s) + { + size_t particle_index = cell_list.concurrent_particle_indexes_[s]; + cell_list.cell_list_data_.emplace_back(std::make_pair(particle_index, pos_n[particle_index])); + } + } + }, + ap); + } + //=================================================================================================// + void CellLinkedList::updateSplitCellLists(SplitCellLists &split_cell_lists) + { + //clear the data + clearSplitCellLists(split_cell_lists); + + parallel_for( + blocked_range3d(0, number_of_cells_[0], 0, number_of_cells_[1], 0, number_of_cells_[2]), + [&](const blocked_range3d &r) + { + for (size_t i = r.pages().begin(); i != r.pages().end(); ++i) + for (size_t j = r.rows().begin(); j != r.rows().end(); ++j) + for (size_t k = r.cols().begin(); k != r.cols().end(); ++k) + { + CellList &cell_list = cell_linked_lists_[i][j][k]; + size_t real_particles_in_cell = cell_list.concurrent_particle_indexes_.size(); + if (real_particles_in_cell != 0) + { + for (size_t s = 0; s != real_particles_in_cell; ++s) + cell_list.real_particle_indexes_.push_back(cell_list.concurrent_particle_indexes_[s]); + split_cell_lists[transferMeshIndexTo1D(Vecu(3), Vecu(i % 3, j % 3, k % 3))] + .push_back(&cell_linked_lists_[i][j][k]); + } + } + }, + ap); + } + //=================================================================================================// + void CellLinkedList ::insertACellLinkedParticleIndex(size_t particle_index, const Vecd &particle_position) + { + Vecu cellpos = CellIndexFromPosition(particle_position); + cell_linked_lists_[cellpos[0]][cellpos[1]][cellpos[2]].concurrent_particle_indexes_.emplace_back(particle_index); + } + //=================================================================================================// + void CellLinkedList ::InsertACellLinkedListDataEntry(size_t particle_index, const Vecd &particle_position) + { + Vecu cellpos = CellIndexFromPosition(particle_position); + cell_linked_lists_[cellpos[0]][cellpos[1]][cellpos[2]].cell_list_data_.emplace_back(std::make_pair(particle_index, particle_position)); + } + //=================================================================================================// + ListData CellLinkedList::findNearestListDataEntry(const Vecd &position) + { + Real min_distance = Infinity; + ListData nearest_entry = std::make_pair(MaxSize_t, Vecd(Infinity)); + + Vecu cell_location = CellIndexFromPosition(position); + int i = (int)cell_location[0]; + int j = (int)cell_location[1]; + int k = (int)cell_location[2]; + + for (int l = SMAX(i - 1, 0); l <= SMIN(i + 1, int(number_of_cells_[0]) - 1); ++l) + { + for (int m = SMAX(j - 1, 0); m <= SMIN(j + 1, int(number_of_cells_[1]) - 1); ++m) + { + for (int q = SMAX(k - 1, 0); q <= SMIN(k + 1, int(number_of_cells_[2]) - 1); ++q) + { + ListDataVector &target_particles = cell_linked_lists_[l][m][q].cell_list_data_; + for (const ListData &list_data : target_particles) + { + Real distance = (position - list_data.second).norm(); + if (distance < min_distance) + { + min_distance = distance; + nearest_entry = list_data; + } + } + } + } + } + return nearest_entry; + } + //=================================================================================================// + void CellLinkedList:: + tagBodyPartByCell(CellLists &cell_lists, std::function &check_included) + { + for (int i = 0; i < (int)number_of_cells_[0]; ++i) + for (int j = 0; j < (int)number_of_cells_[1]; ++j) + for (int k = 0; k < (int)number_of_cells_[2]; ++k) + { + bool is_included = false; + for (int l = SMAX(i - 1, 0); l <= SMIN(i + 1, int(number_of_cells_[0]) - 1); ++l) + for (int m = SMAX(j - 1, 0); m <= SMIN(j + 1, int(number_of_cells_[1]) - 1); ++m) + for (int n = SMAX(k - 1, 0); n <= SMIN(k + 1, int(number_of_cells_[2]) - 1); ++n) + { + //all cells near or contained by the body part shape are inlcuded + if (check_included(CellPositionFromIndex(Vecu(l, m, n)), grid_spacing_)) + { + is_included = true; + } + } + if (is_included == true) + cell_lists.push_back(&cell_linked_lists_[i][j][k]); + } + } + //=================================================================================================// + void CellLinkedList:: + tagBodyDomainBoundingCells(StdVec &cell_lists, BoundingBox &body_domain_bounds, int axis) + { + int second_axis = SecondAxis(axis); + int third_axis = ThirdAxis(axis); + Vecu body_lower_bound_cell_ = CellIndexFromPosition(body_domain_bounds.first); + Vecu body_upper_bound_cell_ = CellIndexFromPosition(body_domain_bounds.second); + + //lower bound cells + for (size_t k = SMAX(int(body_lower_bound_cell_[third_axis]) - 1, 0); + k < (size_t)SMIN(int(body_upper_bound_cell_[third_axis] + 2), int(number_of_cells_[third_axis])); ++k) + { + + for (size_t j = SMAX(int(body_lower_bound_cell_[second_axis]) - 1, 0); + j < (size_t)SMIN(int(body_upper_bound_cell_[second_axis] + 2), int(number_of_cells_[second_axis])); ++j) + { + + for (size_t i = SMAX(int(body_lower_bound_cell_[axis]) - 1, 0); + i <= (size_t)SMIN(int(body_lower_bound_cell_[axis] + 1), int(number_of_cells_[axis] - 1)); ++i) + { + Vecu cell_position(0); + cell_position[axis] = i; + cell_position[second_axis] = j; + cell_position[third_axis] = k; + cell_lists[0].push_back(&cell_linked_lists_[cell_position[0]][cell_position[1]][cell_position[2]]); + } + } + } + + //upper bound cells + for (size_t k = SMAX(int(body_lower_bound_cell_[third_axis]) - 1, 0); + k < (size_t)SMIN(int(body_upper_bound_cell_[third_axis] + 2), int(number_of_cells_[third_axis])); ++k) + { + + for (size_t j = SMAX(int(body_lower_bound_cell_[second_axis]) - 1, 0); + j < (size_t)SMIN(int(body_upper_bound_cell_[second_axis] + 2), int(number_of_cells_[second_axis])); ++j) + { + + for (size_t i = SMAX(int(body_upper_bound_cell_[axis]) - 1, 0); + i <= (size_t)SMIN(int(body_upper_bound_cell_[axis] + 1), int(number_of_cells_[axis] - 1)); ++i) + { + Vecu cell_position(0); + cell_position[axis] = i; + cell_position[second_axis] = j; + cell_position[third_axis] = k; + cell_lists[1].push_back(&cell_linked_lists_[cell_position[0]][cell_position[1]][cell_position[2]]); + } + } + } + } + //=================================================================================================// + void CellLinkedList:: + tagMirrorBoundingCells(CellLists &cell_lists, BoundingBox &body_domain_bounds, int axis, bool positive) + { + int second_axis = SecondAxis(axis); + int third_axis = ThirdAxis(axis); + Vecu body_lower_bound_cell_ = CellIndexFromPosition(body_domain_bounds.first); + Vecu body_upper_bound_cell_ = CellIndexFromPosition(body_domain_bounds.second); + + if (positive) + { + //upper bound cells + for (size_t k = SMAX(int(body_lower_bound_cell_[third_axis]) - 1, 0); + k < (size_t)SMIN(int(body_upper_bound_cell_[third_axis] + 2), int(number_of_cells_[third_axis])); ++k) + { + + for (size_t j = SMAX(int(body_lower_bound_cell_[second_axis]) - 1, 0); + j < (size_t)SMIN(int(body_upper_bound_cell_[second_axis] + 2), int(number_of_cells_[second_axis])); ++j) + { + + for (size_t i = SMAX(int(body_upper_bound_cell_[axis]) - 1, 0); + i <= (size_t)SMIN(int(body_upper_bound_cell_[axis] + 1), int(number_of_cells_[axis] - 1)); ++i) + { + Vecu cell_position(0); + cell_position[axis] = i; + cell_position[second_axis] = j; + cell_position[third_axis] = k; + cell_lists.push_back(&cell_linked_lists_[cell_position[0]][cell_position[1]][cell_position[2]]); + } + } + } + } + else + { + //lower bound cells + for (size_t k = SMAX(int(body_lower_bound_cell_[third_axis]) - 1, 0); + k < (size_t)SMIN(int(body_upper_bound_cell_[third_axis] + 2), int(number_of_cells_[third_axis])); ++k) + { + + for (size_t j = SMAX(int(body_lower_bound_cell_[second_axis]) - 1, 0); + j < (size_t)SMIN(int(body_upper_bound_cell_[second_axis] + 2), int(number_of_cells_[second_axis])); ++j) + { + + for (size_t i = SMAX(int(body_lower_bound_cell_[axis]) - 1, 0); + i <= (size_t)SMIN(int(body_lower_bound_cell_[axis] + 1), int(number_of_cells_[axis] - 1)); ++i) + { + Vecu cell_position(0); + cell_position[axis] = i; + cell_position[second_axis] = j; + cell_position[third_axis] = k; + cell_lists.push_back(&cell_linked_lists_[cell_position[0]][cell_position[1]][cell_position[2]]); + } + } + } + } + } + //=================================================================================================// + void CellLinkedList::writeMeshFieldToPlt(std::ofstream &output_file) + { + Vecu number_of_operation = number_of_cells_; + + output_file << "\n"; + output_file << "title='View'" + << "\n"; + output_file << "variables= " + << "x, " + << "y, " + << "z, " + << "particles_in_cell " + << "\n"; + output_file << "zone i=" << number_of_operation[0] << " j=" << number_of_operation[1] << " k=" << number_of_operation[2] + << " DATAPACKING=BLOCK SOLUTIONTIME=" << 0 << "\n"; + + for (size_t k = 0; k != number_of_operation[2]; ++k) + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + Vecd data_position = CellPositionFromIndex(Vecu(i, j, k)); + output_file << data_position[0] << " "; + } + output_file << " \n"; + } + + for (size_t k = 0; k != number_of_operation[2]; ++k) + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + Vecd data_position = CellPositionFromIndex(Vecu(i, j, k)); + output_file << data_position[1] << " "; + } + output_file << " \n"; + } + + for (size_t k = 0; k != number_of_operation[2]; ++k) + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + Vecd data_position = CellPositionFromIndex(Vecu(i, j, k)); + output_file << data_position[2] << " "; + } + output_file << " \n"; + } + + for (size_t k = 0; k != number_of_operation[2]; ++k) + for (size_t j = 0; j != number_of_operation[1]; ++j) + { + for (size_t i = 0; i != number_of_operation[0]; ++i) + { + output_file << cell_linked_lists_[i][j][k].concurrent_particle_indexes_.size() << " "; + } + output_file << " \n"; + } + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/for_3D_build/meshes/mesh_with_data_packages.hpp b/SPHINXsys/src/for_3D_build/meshes/mesh_with_data_packages.hpp new file mode 100644 index 0000000000..1b22f4f4d8 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/meshes/mesh_with_data_packages.hpp @@ -0,0 +1,185 @@ +/** +* @file base_mesh.hpp +* @brief This is the implementation of the template function and class for base mesh +* @author Chi ZHang and Xiangyu Hu +*/ + +#ifndef MESH_WITH_DATA_PACKAGES_3D_HPP +#define MESH_WITH_DATA_PACKAGES_3D_HPP + +#include "mesh_with_data_packages.h" + +//=================================================================================================// +namespace SPH { + //=================================================================================================// + template + template + DataType BaseDataPackage + ::probeDataPackage(PackageDataAddress& pkg_data_addrs, const Vecd& position) + { + Vec3u grid_idx = CellIndexFromPosition(position); + Vec3d grid_pos = GridPositionFromIndex(grid_idx); + Vec3d alpha = (position - grid_pos) / grid_spacing_; + Vec3d beta = Vec3d(1.0) - alpha; + + DataType bilinear_1 + = *pkg_data_addrs[grid_idx[0]][grid_idx[1]][grid_idx[2]] * beta[0] * beta[1] + + *pkg_data_addrs[grid_idx[0] + 1][grid_idx[1]][grid_idx[2]] * alpha[0] * beta[1] + + *pkg_data_addrs[grid_idx[0]][grid_idx[1] + 1][grid_idx[2]] * beta[0] * alpha[1] + + *pkg_data_addrs[grid_idx[0] + 1][grid_idx[1] + 1][grid_idx[2]] * alpha[0] * alpha[1]; + DataType bilinear_2 + = *pkg_data_addrs[grid_idx[0]][grid_idx[1]][grid_idx[2] + 1] * beta[0] * beta[1] + + *pkg_data_addrs[grid_idx[0] + 1][grid_idx[1]][grid_idx[2] + 1] * alpha[0] * beta[1] + + *pkg_data_addrs[grid_idx[0]][grid_idx[1] + 1][grid_idx[2] + 1] * beta[0] * alpha[1] + + *pkg_data_addrs[grid_idx[0] + 1][grid_idx[1] + 1][grid_idx[2] + 1] * alpha[0] * alpha[1]; + return bilinear_1 * beta[2] + bilinear_2 * alpha[2]; + } + //=================================================================================================// + template + template + void BaseDataPackage:: + computeGradient(PackageDataAddress& in_pkg_data_addrs, + PackageDataAddress out_pkg_data_addrs, Real dt) + { + for (int i = 1; i != PKG_SIZE + 1; ++i) + for (int j = 1; j != PKG_SIZE + 1; ++j) + for (int k = 1; k != PKG_SIZE + 1; ++k) + { + Real dphidx = (*in_pkg_data_addrs[i + 1][j][k] - *in_pkg_data_addrs[i - 1][j][k]); + Real dphidy = (*in_pkg_data_addrs[i][j + 1][k] - *in_pkg_data_addrs[i][j - 1][k]); + Real dphidz = (*in_pkg_data_addrs[i][j][k + 1] - *in_pkg_data_addrs[i][j][k - 1]); + *out_pkg_data_addrs[i][j][k] = Vecd(dphidx, dphidy, dphidz); + } + } + //=================================================================================================// + template + template + void BaseDataPackage:: + computeNormalizedGradient(PackageDataAddress& in_pkg_data_addrs, + PackageDataAddress out_pkg_data_addrs, Real dt) + { + for (int i = 1; i != PKG_SIZE + 1; ++i) + for (int j = 1; j != PKG_SIZE + 1; ++j) + for (int k = 1; k != PKG_SIZE + 1; ++k) + { + Real dphidx = (*in_pkg_data_addrs[i + 1][j][k] - *in_pkg_data_addrs[i - 1][j][k]); + Real dphidy = (*in_pkg_data_addrs[i][j + 1][k] - *in_pkg_data_addrs[i][j - 1][k]); + Real dphidz = (*in_pkg_data_addrs[i][j][k + 1] - *in_pkg_data_addrs[i][j][k - 1]); + Vecd normal = Vecd(dphidx, dphidy, dphidz); + *out_pkg_data_addrs[i][j][k] = normal / (normal.norm() + TinyReal); + } + } + //=================================================================================================// + template + template + void BaseDataPackage:: + initializePackageDataAddress(PackageData& pkg_data, + PackageDataAddress& pkg_data_addrs) + { + for (int i = 0; i != ADDRS_SIZE; ++i) + for (int j = 0; j != ADDRS_SIZE; ++j) + for (int k = 0; k != ADDRS_SIZE; ++k) + { + pkg_data_addrs[i][j][k] = &pkg_data[0][0][0]; + } + } + //=================================================================================================// + template + template + void BaseDataPackage:: + assignPackageDataAddress(PackageDataAddress& pkg_data_addrs, Vecu& addrs_index, + PackageData& pkg_data, Vecu& data_index) + { + pkg_data_addrs[addrs_index[0]][addrs_index[1]][addrs_index[2]] + = &pkg_data[data_index[0]][data_index[1]][data_index[2]]; + } + //=================================================================================================// + template + template + DataType BaseDataPackage:: + CornerAverage(PackageDataAddress& pkg_data_addrs, Veci addrs_index, Veci corner_direction) + { + DataType average(0); + for (int i = 0; i != 2; ++i) + for (int j = 0; j != 2; ++j) + for (int k = 0; k != 2; ++k) + { + int x_index = addrs_index[0] + i * corner_direction[0]; + int y_index = addrs_index[1] + j * corner_direction[1]; + int z_index = addrs_index[2] + k * corner_direction[2]; + average += *pkg_data_addrs[x_index][y_index][z_index]; + } + return average * 0.125; + } + //=================================================================================================// + template + template + DataType MeshWithDataPackages:: + DataValueFromGlobalIndex(Vecu global_grid_index) + { + Vecu pkg_index_(0); + Vecu local_data_index(0); + for (int n = 0; n != 3; n++) + { + size_t cell_index_in_this_direction = global_grid_index[n] / pkg_size_; + pkg_index_[n] = cell_index_in_this_direction; + local_data_index[n] = global_grid_index[n] - cell_index_in_this_direction * pkg_size_; + } + PackageDataType& data = data_pkg_addrs_[pkg_index_[0]][pkg_index_[1]][pkg_index_[2]]->*MemPtr; + return data[local_data_index[0]][local_data_index[1]][local_data_index[2]]; + } + //=================================================================================================// + template + void MeshWithDataPackages::initializePackageAddressesInACell(Vecu cell_index) + { + int i = (int)cell_index[0]; + int j = (int)cell_index[1]; + int k = (int)cell_index[2]; + + DataPackageType* data_pkg = data_pkg_addrs_[i][j][k]; + if (data_pkg->is_inner_pkg_) { + for (int l = 0; l != pkg_addrs_size_; ++l) + for (int m = 0; m != pkg_addrs_size_; ++m) + for (int n = 0; n != pkg_addrs_size_; ++n) { + std::pair x_pair = CellShiftAndDataIndex(l); + std::pair y_pair = CellShiftAndDataIndex(m); + std::pair z_pair = CellShiftAndDataIndex(n); + + data_pkg->assignAllPackageDataAddress(Vecu(l, m, n), + data_pkg_addrs_[i + x_pair.first][j + y_pair.first][k + z_pair.first], + Vecu(x_pair.second, y_pair.second, z_pair.second)); + } + } + } + //=================================================================================================// + template + void MeshWithDataPackages::allocateMeshDataMatrix() + { + Allocate3dArray(data_pkg_addrs_, number_of_cells_); + } + //=================================================================================================// + template + void MeshWithDataPackages::deleteMeshDataMatrix() + { + Delete3dArray(data_pkg_addrs_, number_of_cells_); + } + //=================================================================================================// + template + template + DataType MeshWithDataPackages::probeMesh(const Vecd& position) + { + Vecu grid_index = CellIndexFromPosition(position); + size_t i = grid_index[0]; + size_t j = grid_index[1]; + size_t k = grid_index[2]; + + DataPackageType* data_pkg = data_pkg_addrs_[i][j][k]; + PackageDataAddressType& pkg_data_addrs = data_pkg->*MemPtr; + return data_pkg->is_inner_pkg_ ? + data_pkg->DataPackageType::template probeDataPackage(pkg_data_addrs, position) + : *pkg_data_addrs[0][0][0]; + } + //=================================================================================================// +} +//=================================================================================================// +#endif //MESH_WITH_DATA_PACKAGES_3D_HPP \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/particle_dynamics/CMakeLists.txt b/SPHINXsys/src/for_3D_build/particle_dynamics/CMakeLists.txt new file mode 100644 index 0000000000..fd5eae60fd --- /dev/null +++ b/SPHINXsys/src/for_3D_build/particle_dynamics/CMakeLists.txt @@ -0,0 +1,7 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/CMakeLists.txt b/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/CMakeLists.txt new file mode 100644 index 0000000000..9d7cea4b86 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) + diff --git a/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3.h b/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3.h new file mode 100644 index 0000000000..05adb8db99 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3.h @@ -0,0 +1,68 @@ +// MIT License +// +// Copyright (c) 2017 Martin Bisson +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#ifndef __POLAR_DECOMPOSITION_3X3_H__ +#define __POLAR_DECOMPOSITION_3X3_H__ + + +// Inclusion of the actual implementation of the algorithm. +#include "polar_decomposition_3x3_impl.h" + + + + +namespace polar +{ + + + // Compute the polar decomposition of the input matrix. + // + // This method decomposes the input matrix into one orthonormal (rotation) matrix + // and a remaining positive semi-definite (scale) matrix. + // + // Matrices are represented as 3x3 matrices in column-major representation. + // + // A [in] : Matrix to decompose. + // Q [out] : Rotation part of the polar decomposition. + // H [out] : Symmetric matrix represention the scale part of the polar decomposition. + template + void polar_decomposition(TReal* Q, TReal* H, const TReal* A) + { + // Convert parameters for the algorithm implementation. + typedef detail::matrix TMatrix; + + // Depending on the implementation, this part might involve a copy, + // but here the memory layout requirement is for matrices to be 3x3, + // column-major, so we can just cast directly. + TMatrix* matrixQ = reinterpret_cast(Q); + TMatrix* matrixH = reinterpret_cast(H); + const TMatrix* matrixA = reinterpret_cast(A); + + detail::run_algorithm_3_5(*matrixQ, *matrixH, *matrixA); + } + + +}; // End of namespace polar. + + + + +#endif // __POLAR_DECOMPOSITION_3X3_H__ diff --git a/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3_impl.h b/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3_impl.h new file mode 100644 index 0000000000..a2a34d571d --- /dev/null +++ b/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3_impl.h @@ -0,0 +1,1373 @@ +// MIT License +// +// Copyright (c) 2017 Martin Bisson +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#ifndef __POLAR_DECOMPOSITION_3X3_IMPL_H__ +#define __POLAR_DECOMPOSITION_3X3_IMPL_H__ + + + + +#include "polar_decomposition_3x3_matrix.h" + +#include + + +namespace polar +{ + + + // This part implements portions of the algorithms that are tailored + // to just the specifics of what is needed. + namespace detail + { + + + // This method is based on the Matlab implementation. + // It computes the determinant of B matrix from an LU factorization with partial pivoting, + // except that it actually computes it from the values of A, (B matrix is a function of A). + template + inline TReal compute_b_determinant_from_a_matrix_lu_partial(const matrix& A) + { + TReal b = 0; + { + TReal temp; + temp = (A(1,1)*A(2,2)-A(2,1)*A(1,2)); b += (temp * temp); + temp = (A(0,1)*A(2,2)-A(2,1)*A(0,2)); b += (temp * temp); + temp = (A(0,1)*A(1,2)-A(1,1)*A(0,2)); b += (temp * temp); + temp = (A(0,0)*A(1,2)-A(1,0)*A(0,2)); b += (temp * temp); + temp = (A(0,0)*A(2,2)-A(2,0)*A(0,2)); b += (temp * temp); + temp = (A(1,0)*A(2,2)-A(2,0)*A(1,2)); b += (temp * temp); + temp = (A(1,0)*A(2,1)-A(2,0)*A(1,1)); b += (temp * temp); + temp = (A(0,0)*A(2,1)-A(2,0)*A(0,1)); b += (temp * temp); + temp = (A(0,0)*A(1,1)-A(1,0)*A(0,1)); b += (temp * temp); + b = -4 * b + 1; + } + return b; + } + + + // This method is based on the Matlab implementation. + // It computes the determinant of A matrix from an LU factorization with partial pivoting. + template + inline TReal compute_determinant_lu_partial(const matrix& A, TReal& d) + { + TReal dd; // Determinant, d is the sign of the determinant. + // ANSME: Do we really need to keep track of the sign, or can't we just check it at the end? + + matrix AA; + const TReal absA00 = math_utils::fabs(A(0,0)); + const TReal absA01 = math_utils::fabs(A(0,1)); + const TReal absA02 = math_utils::fabs(A(0,2)); + if (absA01 > absA02) + { + if (absA00 > absA01) + { + AA = A; + dd = 1; + } + else + { + AA(0,0) = A(0,1); + AA(1,0) = A(1,1); + AA(2,0) = A(2,1); + AA(0,1) = A(0,0); + AA(1,1) = A(1,0); + AA(2,1) = A(2,0); + AA(0,2) = A(0,2); + AA(1,2) = A(1,2); + AA(2,2) = A(2,2); + dd = -1; + } + } + else + { + if (absA00 > absA02) + { + AA = A; + dd = 1; + } + else + { + AA(0,0) = A(0,2); + AA(1,0) = A(1,2); + AA(2,0) = A(2,2); + AA(0,1) = A(0,1); + AA(1,1) = A(1,1); + AA(2,1) = A(2,1); + AA(0,2) = A(0,0); + AA(1,2) = A(1,0); + AA(2,2) = A(2,0); + dd = -1; + } + } + + d = dd; + vector U; + U(0) = AA(0, 0); + if (U(0) < 0) + d = -d; + + const TReal m1 = AA(1,0) / AA(0,0); + const TReal m2 = AA(2,0) / AA(0,0); + const TReal AA00 = AA(1,1) - AA(0,1) * m1; + const TReal AA10 = AA(2,1) - AA(0,1) * m2; + const TReal AA01 = AA(1,2) - AA(0,2) * m1; + const TReal AA11 = AA(2,2) - AA(0,2) * m2; + + if (math_utils::fabs(AA00) < math_utils::fabs(AA01)) + { + U(1) = AA01; + U(2) = AA10 - AA00 * AA11 / AA01; + dd = -dd; + d = -d; + if (U(1) < 0) + d = -d; + if (U(2) < 0) + d = -d; + } + else if (AA00 == 0) + { + U(1) = 0; + U(2) = 0; + } + else + { + U(1) = AA00; + U(2) = AA11 - AA01 * AA10 / AA00; + if (U(1) < 0) + d = -d; + if (U(2) < 0) + d = -d; + } + + dd = dd * U(0) * U(1) * U(2); + if (d == 0) + d = 1; + + assert(((d < 0) && (dd < 0)) || (dd >= 0)); + assert((d == 1) || (d == -1)); + + return dd; + } + + + // Swap rows. + template + inline void swap_rows(matrix& m, const int row0, const int row1) + { + assert(row0 != row1); + std::swap(m(0,row0), m(0,row1)); + std::swap(m(1,row0), m(1,row1)); + std::swap(m(2,row0), m(2,row1)); + } + template + inline void swap_rows(matrix& m, const int row0, const int row1) + { + assert(row0 != row1); + std::swap(m(0,row0), m(0,row1)); + std::swap(m(1,row0), m(1,row1)); + std::swap(m(2,row0), m(2,row1)); + std::swap(m(3,row0), m(3,row1)); + } + + // Swap columns. + template + inline void swap_columns(matrix& m, const int column0, const int column1) + { + assert(column0 != column1); + std::swap(m(column0,0), m(column1,0)); + std::swap(m(column0,1), m(column1,1)); + std::swap(m(column0,2), m(column1,2)); + } + template + inline void swap_columns(matrix& m, const int column0, const int column1) + { + assert(column0 != column1); + std::swap(m(column0,0), m(column1,0)); + std::swap(m(column0,1), m(column1,1)); + std::swap(m(column0,2), m(column1,2)); + std::swap(m(column0,3), m(column1,3)); + } + + + // This method is based on the Matlab implementation. + // It computes an LDL^T factorization with diagonal pivoting, P^T Bs P = L D L^T. + // This method modifies the Bs matrix. + // Note: The method does not fill the whole L matrix, just the lower left part. + // The caller should assume: + // - L(i,i) == 1 + // - L(i,j) == 0 for i > j + template + inline void compute_ldlt_factorization_diagonal(matrix& L, vector& D, vector& p, matrix& Bs) + { + p(0) = 0; + p(1) = 1; + p(2) = 2; + p(3) = 3; + + // ANSME: Should we compare absolute values to pick which row to pivot? + // This whole code could be refactored and improved to be more coherent + // from one step to another. + + // First step. + { + int r = 3; + if (Bs(3,3) < Bs(2,2)) + r = 2; + if (Bs(r,r) < Bs(1,1)) + r = 1; + + if (Bs(r,r) > Bs(0,0)) + { + std::swap(p(0), p(r)); + swap_rows(Bs, 0, r); + swap_columns(Bs, 0, r); + } + + D(0) = Bs(0,0); + L(0,1) = Bs(0,1) / D(0); + L(0,2) = Bs(0,2) / D(0); + L(0,3) = Bs(0,3) / D(0); + + Bs(1,1) = Bs(1,1) - L(0,1) * Bs(1,0); + Bs(1,2) = Bs(1,2) - L(0,1) * Bs(2,0); + Bs(2,1) = Bs(1,2); + Bs(1,3) = Bs(1,3) - L(0,1) * Bs(3,0); + Bs(3,1) = Bs(1,3); + + Bs(2,2) = Bs(2,2) - L(0,2) * Bs(2,0); + Bs(2,3) = Bs(2,3) - L(0,2) * Bs(3,0); + Bs(3,2) = Bs(2,3); + + Bs(3,3) = Bs(3,3) - L(0,3) * Bs(3,0); + } + + // Second step. + { + int r = 3; + if (Bs(3,3) < Bs(2,2)) + r = 2; + + if (Bs(r,r) > Bs(1,1)) + { + std::swap(p(1), p(r)); + swap_rows(Bs, 1, r); + swap_columns(Bs, 1, r); + #if 0 + swap_rows(L, 1, r); + swap_columns(L, 1, r); + #else + // Here, only the first column has been written, so we can swap just that. + // swap(1,2) swap(1,3) + // | 1 0 0 0 | | 1 0 0 0 | | 1 0 0 0 | + // | a 1 0 0 | | b 1 0 0 | | c 1 0 0 | + // | b 0 1 0 | | a 0 1 0 | | b 0 1 0 | + // | c 0 0 1 | | c 0 0 1 | | a 0 0 1 | + std::swap(L(0,1), L(0,r)); + #endif + } + + D(1) = Bs(1,1); + L(1,2) = Bs(1,2) / D(1); + L(1,3) = Bs(1,3) / D(1); + + Bs(2,2) = Bs(2,2) - L(1,2) * Bs(2,1); + Bs(2,3) = Bs(2,3) - L(1,2) * Bs(3,1); + Bs(3,2) = Bs(2,3); + + Bs(3,3) = Bs(3,3) - L(1,3) * Bs(3,1); + } + + // Third step. + { + if (Bs(2,2) < Bs(3,3)) + { + D(2) = Bs(3,3); + std::swap(p(2), p(3)); + swap_rows(Bs, 2, 3); + swap_columns(Bs, 2, 3); + #if 0 + swap_rows(L, 2, 3); + swap_columns(L, 2, 3); + #else + // Here, only the first two columns has been written, so we can swap just that. + // swap(2,3) + // | 1 0 0 0 | | 1 0 0 0 | + // | a 1 0 0 | | a 1 0 0 | + // | b e 1 0 | | c f 1 0 | + // | c f 0 1 | | b e 0 1 | + std::swap(L(0,2), L(0,3)); + std::swap(L(1,2), L(1,3)); + #endif + } + else + { + D(2) = Bs(2,2); + } + + L(2,3) = Bs(2,3) / D(2); + } + } + + + // This method is based on the Matlab implementation. + // It computes the determinant of A matrix from an LU factorization with complete pivoting. + template + inline TReal compute_determinant_lu_complete(const matrix& A, TReal& d, TReal& u22) + { + TReal dd; // Determinant, d is the sign of the determinant. + // ANSME: Do we really need to keep track of the sign, or can't we just check it at the end? + + matrix AA = A; + { + int r = 0; + int c = 0; + dd = 1; + if (math_utils::fabs(A(0,1)) > math_utils::fabs(A(0,0))) + { + r = 1; + } + if (math_utils::fabs(A(0,2)) > math_utils::fabs(A(c,r))) + { + r = 2; + } + if (math_utils::fabs(A(1,0)) > math_utils::fabs(A(c,r))) + { + r = 0; + c = 1; + } + if (math_utils::fabs(A(1,1)) > math_utils::fabs(A(c,r))) + { + r = 1; + c = 1; + } + if (math_utils::fabs(A(1,2)) > math_utils::fabs(A(c,r))) + { + r = 2; + c = 1; + } + if (math_utils::fabs(A(2,0)) > math_utils::fabs(A(c,r))) + { + r = 0; + c = 2; + } + if (math_utils::fabs(A(2,1)) > math_utils::fabs(A(c,r))) + { + r = 1; + c = 2; + } + if (math_utils::fabs(A(2,2)) > math_utils::fabs(A(c,r))) + { + r = 2; + c = 2; + } + + if (r > 0) + { + swap_rows(AA, 0, r); + dd = -1; + } + if (c > 0) + { + swap_columns(AA, 0, c); + dd = -dd; + } + } + + vector U; + U(0) = AA(0,0); + + matrix< TReal, 2, 2> AA_; + // In case the whole matrix is 0, we add a small value. + const TReal m1 = AA(1,0) / (AA(0,0) + (std::numeric_limits::min)()); + const TReal m2 = AA(2,0) / (AA(0,0) + (std::numeric_limits::min)()); + AA_(0,0) = AA(1,1) - AA(0,1) * m1; + AA_(1,0) = AA(2,1) - AA(0,1) * m2; + AA_(0,1) = AA(1,2) - AA(0,2) * m1; + AA_(1,1) = AA(2,2) - AA(0,2) * m2; + + { + int r = 0; + int c = 0; + if (math_utils::fabs(AA_(0,1)) > math_utils::fabs(AA_(0,0))) + { + r = 1; + } + if (math_utils::fabs(AA_(1,0)) > math_utils::fabs(AA_(c,r))) + { + r = 0; + c = 1; + } + if (math_utils::fabs(AA_(1,1)) > math_utils::fabs(AA_(c,r))) + { + r = 1; + c = 1; + } + + if (r > 0) + { + dd = -dd; + } + if (c > 0) + { + dd = -dd; + } + U(1) = AA_(c,r); + if (U(1) == 0) + { + U(2) = 0; + } + else + { + U(2) = AA_(1-c,1-r) - AA_(1-c,r) * AA_(c,1-r) / U(1); + } + } + + d = dd; + dd = dd * U(0) * U(1) * U(2); + if (U(0) < 0) + d = -d; + if (U(1) < 0) + d = -d; + if (U(2) < 0) + d = -d; + + u22 = U(1); + + assert(((d < 0) && (dd < 0)) || (dd >= 0)); + assert((d == 1) || (d == -1)); + + return dd; + } + + + // This method is based on the Matlab implementation. + // It computes an LDL^T factorization by block LDL^T factorization with Bunch-Parlett pivoting. + // This method modifies the Bs matrix. + // Note: The method does not fill the whole L matrix, just the lower left part. + // The caller should assume: + // - L(i,i) == 1 + // - L(i,j) == 0 for i > j + // - L(2,3) == 0 + // FIXME: Not sure we actually need a 4x4 matrix for D. + template + inline void compute_ldlt_factorization_bunch_parlett(matrix& L, matrix& D, vector& p, matrix& Bs) + { + p(0) = 0; + p(1) = 1; + p(2) = 2; + p(3) = 3; + + // ANSME: Should we compare absolute values to pick which row to pivot? + // This whole code could be refactored and improved to be more coherent + // from one step to another. + + // First step. + { + int r = 3; + if (Bs(3,3) < Bs(2,2)) + r = 2; + if (Bs(r,r) < Bs(1,1)) + r = 1; + + if (Bs(r,r) > Bs(0,0)) + { + std::swap(p(0), p(r)); + swap_rows(Bs, 0, r); + swap_columns(Bs, 0, r); + } + + D(0,0) = Bs(0,0); + L(0,1) = Bs(0,1) / D(0,0); + L(0,2) = Bs(0,2) / D(0,0); + L(0,3) = Bs(0,3) / D(0,0); + + Bs(1,1) = Bs(1,1) - L(0,1) * Bs(1,0); + Bs(1,2) = Bs(1,2) - L(0,1) * Bs(2,0); + Bs(2,1) = Bs(1,2); + Bs(1,3) = Bs(1,3) - L(0,1) * Bs(3,0); + Bs(3,1) = Bs(1,3); + + Bs(2,2) = Bs(2,2) - L(0,2) * Bs(2,0); + Bs(2,3) = Bs(2,3) - L(0,2) * Bs(3,0); + Bs(3,2) = Bs(2,3); + + Bs(3,3) = Bs(3,3) - L(0,3) * Bs(3,0); + } + + // Second step. + { + int r = 2; + if (Bs(2,2) < Bs(1,1)) + r = 1; + + if (Bs(r,r) > Bs(0,0)) + { + std::swap(p(1), p(r)); + swap_rows(Bs, 1, r); + swap_columns(Bs, 1, r); + #if 0 + swap_rows(L, 1, r); + swap_columns(L, 1, r); + #else + // Here, only the first column has been written, so we can swap just that. + // swap(1,2) swap(1,3) + // | 1 0 0 0 | | 1 0 0 0 | | 1 0 0 0 | + // | a 1 0 0 | | b 1 0 0 | | c 1 0 0 | + // | b 0 1 0 | | a 0 1 0 | | b 0 1 0 | + // | c 0 0 1 | | c 0 0 1 | | a 0 0 1 | + std::swap(L(0,1), L(0,r)); + #endif + } + + D(1,1) = Bs(1,1); + L(1,2) = Bs(1,2) / D(1,1); + L(1,3) = Bs(1,3) / D(1,1); + + D(2,2) = Bs(2,2) - L(1,2) * Bs(2,1); + D(2,3) = Bs(2,3) - L(1,2) * Bs(3,1); + D(3,2) = D(2,3); + + D(3,3) = Bs(3,3) - L(1,3) * Bs(3,1); + } + } + + + // This computes the null space of the matrix, knowing that it is symmetric: + // + // | a b | + // | b c | + template + inline vector compute_null_space(const TReal a, const TReal b, const TReal c) + { + // We assume that the matrix determinant is 0. + assert(0 == a * c - b * b); + + vector nullSpace; + if (a != 0) + { + // Transform: R1 = R1 / a + // + // | 1 b / a | + // | b c | + // + // R2 = R2 - b R1 + // + // | 1 b / a | + // | 0 c - b * b / a | + // + // Because a != 0 and a * c - b * b == 0, we have + // 1 * x1 + b / a * x2 = 0 + // a x1 + b x2 = 0 + // + // solution: [ b -a ]^T + nullSpace(0) = b; + nullSpace(1) = -a; + } + else + { + // Since a == 0 and the determinant is null we have: + // + // 0 * c - b * b = 0 + // b = 0 + // + // If b == 0 + // + // We have: + // + // | 0 0 | + // | b c | + // + // b x1 + c x2 = 0 + // solution: [ c -b ]^T + // + // (With b == 0, [ c 0 ]^T). + // + assert(a == 0); + assert(b == 0); + assert(c != 0); + nullSpace(0) = c; + nullSpace(1) = -b; + } + + return nullSpace; + } + + + // These methods are used for optimized operations in reverse iteration with LDL^T. + template + inline vector multiply_il_v( + const TReal IL01, const TReal IL02, const TReal IL03, const TReal IL12, const TReal IL13, + const vector& v + ) + { + // {{1,0,0,0},{m_12,1,0,0},{m_13,m_23,1,0},{m_14,m_24,0,1}} * {{v_1},{v_2},{v_3},{v_4}} + // + // | 1 0 0 0 | | v1 | | v1 | + // | IL01 1 0 0 | * | v2 | = | v1 * IL01 + v | + // | IL02 IL12 1 0 | | v3 | | v1 * IL02 + v2 * IL12 + v3 | + // | IL03 IL13 0 1 | | v4 | | v1 * IL03 + v2 * IL13 + v4 | + vector result; + result(0) = v(0); + result(1) = v(0) * IL01 + v(1); + result(2) = v(0) * IL02 + v(1) * IL12 + v(2); + result(3) = v(0) * IL03 + v(1) * IL13 + v(3); + return result; + } + + template + inline vector multiply_id_v( + const TReal ID00, const TReal ID11, const matrix& ID, + const vector& v + ) + { + // {{a_11,0,0,0},{0,a_22,0,0},{0,0,b_11,b_21},{0,0,b_12,b_22}} * {{v_1},{v_2},{v_3},{v_4}} + // + // | ID00 0 0 0 | | v1 | | v1 * ID00 | + // | 0000 ID11 0 0 | * | v2 | = | v2 * ID11 | + // | 0 0 ID(0,0) ID(1,0) | | v3 | | v3 * ID(0,0) + v4 * ID(1,0) | + // | 0 0 ID(0,1) ID(1,1) | | v4 | | v3 * ID(0,1) + v4 * ID(1,1) | + vector result; + result(0) = v(0) * ID00; + result(1) = v(1) * ID11; + result(2) = v(2) * ID(0,0) + v(3) * ID(1,0); + result(3) = v(2) * ID(0,1) + v(3) * ID(1,1); + return result; + } + + template + inline vector multiply_v_il( + const vector& v, + const TReal IL01, const TReal IL02, const TReal IL03, const TReal IL12, const TReal IL13 + ) + { + // Transpose[{{1,0,0,0},{m_12,1,0,0},{m_13,m_23,1,0},{m_14,m_24,0,1}}] * {{v_1},{v_2},{v_3},{v_4}} + // {v_1,v_2,v_3,v_4} * {{1,0,0,0},{m_12,1,0,0},{m_13,m_23,1,0},{m_14,m_24,0,1}} + // + // | v1 |^T * | 1 0 0 0 | | v1 + v2 * IL01 + v3 * IL02 + v4 * IL03 |^T + // | v2 | | IL01 1 0 0 | = | v2 + v3 * IL12 + v4 * IL13 | + // | v3 | | IL02 IL12 1 0 | | v3 | + // | v4 | | IL03 IL13 0 1 | | v4 | + vector result; + result(0) = v(0) + v(1) * IL01 + v(2) * IL02 + v(3) * IL03; + result(1) = v(1) + v(2) * IL12 + v(3) * IL13; + result(2) = v(2); + result(3) = v(3); + return result; + } + + template + inline vector multiply_minus_v_d( + const vector& v, + const matrix& D + ) + { + // -{v1,v2,v3,v4} * {{D_11,0,0,0},{0,D_22,0,0},{0,0,D_33,D_43},{0,0,D_34,D_44}} + // + // - | v1 |^T * | D(0,0) 0 0 0 | | -v1 * D(0,0) |^T + // | v2 | | 0 D(1,1) 0 0 | = | -v2 * D(1,1) | + // | v3 | | 0 0 D(2,2) D(3,2) | | -v3 * D(2,2) - v4 * D(2,3) | + // | v4 | | 0 0 D(2,3) D(3,3) | | -v4 * D(3,2) - v4 * D(3,3) | + vector result; + result(0) = -v(0) * D(0,0); + result(1) = -v(1) * D(1,1); + result(2) = -v(2) * D(2,2) - v(3) * D(2,3); + result(3) = -v(2) * D(3,2) - v(3) * D(3,3); + return result; + } + + + // Orthonormalization of matrices of the type: + // + // | v00 v10 | + // | v01 v11 | + // | 1 0 | + // | 0 1 | + template + inline void orthonormalize_v_with_qr( + vector& v0, + vector& v1, + const TReal v00, const TReal v10, const TReal v01, const TReal v11 + ) + { + // The factorization was obtained symbolically by WolframAlpha + // by running the following query: + // + // QRDecomposition[{{v_11,v_21},{v_12,v_22},{1,0},{0,1}}] + v0(0) = v00; + v0(1) = v01; + v0(2) = 1; + v0(3) = 0; + normalize(v0); + + // OPTME: We could reuse some of the multiplications. + v1(0) = v10 + v01 * v01 * v10 - v00 * v01 * v11; + v1(1) = v11 - v00 * v01 * v10 + v00 * v00 * v11; + v1(2) = -v00 * v10 - v01 * v11; + v1(3) = v00 * v00 + v01 * v01 + 1; + normalize(v1); + } + + + template + inline void orthonormalize_v_with_qr( + vector& v0, + vector& v1 + ) + { + // The factorization was obtained symbolically by WolframAlpha + // by running the following query: + // + // QRDecomposition[{{a,e},{b,f},{c,g},{d,h}}] + + normalize(v0); + + // To avoid numerical stability issues when multiplying too big values in the solution, + // we scale down the second vector. + TReal factor = v1(0); + if (factor < math_utils::fabs(v1(1))) + factor = math_utils::fabs(v1(1)); + if (factor < math_utils::fabs(v1(2))) + factor = math_utils::fabs(v1(2)); + if (factor < math_utils::fabs(v1(3))) + factor = math_utils::fabs(v1(3)); + factor = 1 / (factor + (std::numeric_limits::min)()); + + const TReal a = v0(0); + const TReal b = v0(1); + const TReal c = v0(2); + const TReal d = v0(3); + const TReal e = v1(0) * factor; + const TReal f = v1(1) * factor; + const TReal g = v1(2) * factor; + const TReal h = v1(3) * factor; + + // OPTME: We could reuse some of the multiplications. + // ANSME: Is there a more numerically stable way to do this? + v1(0) = b*b*e + c*c*e + d*d*e - a*b*f - a*c*g - a*d*h; + v1(1) = -a*b*e + a*a*f + c*c*f + d*d*f - b*c*g - b*d*h; + v1(2) = -a*c*e - b*c*f + a*a*g + b*b*g + d*d*g - c*d*h; + v1(3) = -a*d*e - b*d*f - c*d*g + a*a*h + b*b*h + c*c*h; + normalize(v1); + } + + + }; // End of namespace detail. + + + + + namespace detail + { + + + // "Hard-coded" constants used by this algorithm. + // + // For the time being, they are same whether single or double precision + // is used. + template + struct constants + { + // Tolerance for determinant of matrix B. + static inline TReal get_tau2(); + // Tolerance for third of determinant of matrix B. + static inline TReal get_tau1(); + // Tolerance for Newton iterations. + static inline TReal get_newton_tolerance(); + // Threshold for sub-space iterations. + static inline TReal get_subspace_threshold(); + }; + + template + inline TReal constants::get_tau2() + { + return static_cast(1.0e-4); + } + + template + inline TReal constants::get_tau1() + { + return static_cast(1.0e-4); + } + + template + inline TReal constants::get_newton_tolerance() + { + // The paper mentions 10^-15, but the Matlab implementation uses 10^-12. + return static_cast(1.0e-12); + } + + template + inline TReal constants::get_subspace_threshold() + { + // This constant is used in the test: + // if log10 |u22| > -7.18 + // This implies that u22 > 10^-7.18 ~= 6.607e-8 + // However, this constant was determined using the machine error on + // floating-point representation, so it should probably be different + // between the single and double precision versions. + return static_cast(6.607e-8); + } + + + }; // End of namespace detail. + + + + + // Implementation of 3x3 polar decomposition based on: + // + // "An algorithm to compute the polar decomposition of a 3x3 matrix" + // Nicholas J Higham and Vanni Noferini + // July 2015 + // + // The paper is available at: + // http://eprints.ma.man.ac.uk/2352/01/covered/MIMS_ep2015_66.pdf + // + // It is now available at: + // http://link.springer.com/article/10.1007%2Fs11075-016-0098-7 + // + // This C++ implementation is also extensively based on the Matlab + // implementation of this algorithm available at: + // https://github.com/higham/polar-decomp-3by3 + // + // It is worth noting that Matlab uses a (row,column) matrix indexing, + // but this implementation uses (column,row) indexing. Also, Matlab is + // 1-index based, but this implementation is 0-index based. + // + // This implementation has very specific goals that drive implementation + // choices: + // - It tries to avoid dependencies to third-party libraries. Therefore, + // it reimplements basic operations (matrix operations such as multiplication, + // transposition, etc.). These are straight-forward to implement and + // are kept separate to the actual algorithm so that using an actual + // linear algebra library would make the implementation more straight-forward. + // - It tries to be as efficient as possible. Therefore it potentially combines + // multiple operations into one (for instance, combine matrix transposition + // with multiplication) to minimize runtime cost. While this might reduce + // code simplicity, we favor runtime efficiency while trying to make those + // optimizations as easy to read as possible. + // + // It is also worth noting that this implementation relies on two major sources: + // - The algorithm as described in the paper + // - The algorithm as implemented in the Matlab implementation. + // + // This implementation tries to highlight its references to both the paper and + // the source code. + // + // The algorithms described in the paper are referred to using ### comments. + // For instance, specific lines such as the beginning of algorithm 3.5 will + // be highlighted: + // + // ### Algorithm 3.5 + // + // So that references to the paper are obvious. References to the Matlab + // implementation of the algorithm will be more textual. + namespace detail + { + + + template + inline TReal run_algorithm_3_3(const TReal absDetA, const TReal detB); + + template + inline TReal run_algorithm_3_4(const TReal absDetA, const TReal detB); + + // Implementation of algorithm 3.2. + template + inline void run_algorithm_3_2( + vector& v, + vector& p, + const matrix& A, + const matrix& B, + const TReal detB + ) + { + // ### 1. Form B in R4? from A via (2.5). + + // ### 2. Compute b = det B from an LU factorization with partial pivoting. + // Already done. + const TReal b = detB; + + // ### 3. Compute d = det A from an LU factorization with partial pivoting. + // Sign of the determinant. + TReal d; + // Determinant. + TReal dd = compute_determinant_lu_partial(A, d); + assert((d == 1) || (d == -1)); + + // ### 4. if d < 0, B = -B, d = -d, end + // We use the Bs matrix since we will need it anyways. Bs matrix is formed from + // minus B matrix. + matrix Bs = B; + multiply(Bs, -d); + dd *= d; + + // ### 5. Estimate lambda1, a dominant eigenvalue of B, via Algorithm 3.3. + const TReal lambda1 = run_algorithm_3_3(dd, b); + + // ### 6. Bs = lambda1 I - B + // Bs already holds -B. + Bs(0,0) += lambda1; + Bs(1,1) += lambda1; + Bs(2,2) += lambda1; + Bs(3,3) += lambda1; + + // ### 7. Compute an LDLT factorization with diagonal pivoting, P^T Bs P = L D L^T. + matrix L; + vector D; + compute_ldlt_factorization_diagonal(L, D, p, Bs); + + // ### 8. v = PL^-T e4 / ||L^-T e4||2 + // Normalization will be done in the common part. + v(0) = L(0,1) * L(1,3) + L(0,2) * L(2,3) - L(0,1) * L(2,3) * L(1,2) - L(0,3); + v(1) = L(2,3) * L(1,2) - L(1,3); + v(2) = -L(2,3); + v(3) = 1; + + // ### 9. Form the matrix Q using (2.7). + // ### 10. Compute the upper triangle of H = Q^T A and set the lower triangle equal to + // the upper triangle. + // Both are done at the end of algorithm 3.5, along with v normalization. + } + + + // Implementation of algorithm 3.3. + template + inline TReal run_algorithm_3_3( + const TReal absDetA, + const TReal detB + ) + { + TReal lambda1; + + const TReal& dd = absDetA; + const TReal& b = detB; + + // ### 1. tau1 = 10^4 % Tolerance. + static const TReal kTau1 = constants::get_tau1(); + + // ### 2. if b + 1/3 > 1 + if (b > kTau1 - 1 / static_cast(3)) + { + // ### 3. c = 8d + const TReal c = 8 * dd; + // ### 4. delta0 = 1 + 3b + const TReal delta0 = 1 + 3 * b; + // ### 5. delta1 = -1 + (27/16)c^2 + 9b + const TReal delta1 = -1 + (27 / static_cast(16)) * c * c + 9 * b; + // ### 6. phi = delta1/delta0^(3/2) + TReal phi = delta1 / (delta0 * math_utils::sqrt(delta0)); + // This was not in the original algorithm, but clamp to [-1,1] in case of rounding errors. + phi = math_utils::clamp(phi, -1, 1); + // ### 7. z = (4/3)(1 + delta0^(1/2)cos(arccos(alpha)/3)) + const TReal z = (4 / static_cast(3)) * (1 + math_utils::sqrt(delta0) * math_utils::cos(math_utils::acos(phi) / 3)); + // ### 8. s = z^0.5/2 + const TReal s = math_utils::sqrt(z) / 2; + // ### 9. lambda1 = s + (max(0, 4 - z + c/s))^(1/2)/2. + lambda1 = s; + const TReal temp = 4 - z + c / s; + if (temp > 0) + lambda1 += math_utils::sqrt(temp) / 2; + } + // ### 10. else + else + { + // ### 11. Use Newton's method (Algorithm 3.4) to approximate + lambda1 = run_algorithm_3_4(dd, b); + } + // ### 12. end + + return lambda1; + } + + + // Implementation of algorithm 3.4. + template + inline TReal run_algorithm_3_4( + const TReal absDetA, + const TReal detB + ) + { + const TReal& dd = absDetA; + const TReal& b = detB; + + // ### 1. x = sqrt(3) + TReal x = math_utils::sqrt(3); + // ### 2. xold = 3 + TReal xold = 3; + // ### 3. while xold - x > 10^-15 + static const TReal kNewtonTolerance = constants::get_newton_tolerance(); + while (xold - x > kNewtonTolerance) + { + // ### 4. xold = x + xold = x; + // ### 5. Evaluate p = p(x) = det(xI - B) by Horner's method. + const TReal c = 8 * dd; + const TReal px = x * (x * (x * x - 2) - c) + b; + // ### 6. Evaluate pd = p0(x) by Horner's method. + const TReal dpx = x * (4 * x * x - 4) - c; + // ### 7. x = x - p/pd + x = x - px / dpx; + } + // ### 8. end + const TReal lambda1 = x; + + return lambda1; + } + + + // Implementation of algorithm 3.5. + template + inline void run_algorithm_3_5( + matrix& paramQ, + matrix& paramH, + const matrix& paramA + ) + { + // First make sure the input matrix is normalized. + matrix A = paramA; + normalize(A); + + // ### Algorithm 3.5 + + // ### 1. tau2 = 10^-4 % Tolerance. + static const TReal kTau2 = constants::get_tau2(); + + // ### 2. Form B in R4? from A via (2.5). + matrix B; + // Computation of the matrix as described in the paper's formula. + // Note: In the author's Matlab implementation, the computation + // is slightly different. It first computes the trace as the + // sum of the diagonal and then computes the diagonal elements + // of B derived from this. This can create numerical differences + // for which the algorithm should be tolerant, but depending on + // whether single or double precision is used, different paths + // in the algorithm might be used. However, they should all yield + // satisfactory solutions as the algorithm should be numerically stable + // in both cases. + // + // OPTME: B matrix is symmetric, we could store it in a different way. + B(0,0) = A(0,0) + A(1,1) + A(2,2); + B(1,1) = A(0,0) - A(1,1) - A(2,2); + B(2,2) = A(1,1) - A(0,0) - A(2,2); + B(3,3) = A(2,2) - A(0,0) - A(1,1); + B(0,1) = B(1,0) = A(2,1) - A(1,2); + B(1,2) = B(2,1) = A(1,0) + A(0,1); + B(2,3) = B(3,2) = A(2,1) + A(1,2); + B(0,2) = B(2,0) = A(0,2) - A(2,0); + B(1,3) = B(3,1) = A(2,0) + A(0,2); + B(0,3) = B(3,0) = A(1,0) - A(0,1); + + // ### 3. Compute b = det B from an LU factorization with partial pivoting. + // It actually computes B determinant from A matrix is B is a function of A. + const TReal b = compute_b_determinant_from_a_matrix_lu_partial(A); + + vector v; + vector p; + + // ### 4. if b < 1 - tau2 + if (b < 1 - kTau2) + { + // ### % Dominant eigenvalue of B is well separated. + // ### 5. Call Algorithm 3.2. + run_algorithm_3_2(v, p, A, B, b); + } + // ### 6. else + else + { + // ### 7. Compute d = detA using an LU factorization with complete pivoting. + // Also keep the second U value of the factorization. + TReal u22; + // Sign of the determinant. + TReal d; + // Determinant. + TReal dd = compute_determinant_lu_complete(A, d, u22); + assert((d == 1) || (d == -1)); + + // ### 8. If d < 0, B = -B, end + // We use the Bs matrix since we will need it anyways. Bs matrix is formed from + // minus B matrix. + matrix Bs = B; + multiply(Bs, -d); + dd *= d; + + // ### 9. Estimate lambda1 using Algorithm 3.3. + const TReal lambda1 = run_algorithm_3_3(dd, b); + + // ### 10. Bs = lambda1 I - B + // Bs already holds -B. + Bs(0, 0) += lambda1; + Bs(1, 1) += lambda1; + Bs(2, 2) += lambda1; + Bs(3, 3) += lambda1; + + // Compute Bs = LDL^T by block LDL^T factorization with Bunch-Parlett pivoting + // This will be used in both following cases. + matrix L; + matrix D; + compute_ldlt_factorization_bunch_parlett(L, D, p, Bs); + assert(D(2,3) == D(3,2)); + + const TReal DD = D(2,2) * D(3,3) - D(3,2) * D(3,2); + if (DD == 0) + { + // Treat this case specially. It is not really mentioned in the paper's algorithm, + // but it is part of the Matlab implementation. + const bool allZero = (D(2,2) == 0) && (D(3,3) == 0) && (D(3,2) == 0); + if (allZero) + { + // This is the equivalent of choosing a null space of (0,1) and do + // the same calculation as the other case. + v(0) = L(0,1) * L(1,3) - L(0,3); + v(1) = -L(1,3); + v(2) = 0; + v(3) = 1; + } + else + { + // ANSME: A more robust way might be to get into this case for determinant close to 0, + // instead of exactly equal to zero, and then use something more robust such as taking + // the vector associated with the smallest singular value (0 if there is actually a + // null space), which could probably be computed efficiently for 2x2 matrices. + const vector nullSpace = compute_null_space(D(2,2), D(2,3), D(3,3)); + + // Since L is diagonal, we can solve v = L^-T * [0 0 a b]^T. + // We also know, from compute_ldlt_factorization_bunch_parlett, that L(2,3) is 0. + // So v can be computed symbolically by WolframAlpha by running the following query: + // Inverse[Transpose[{{1,0,0,0},{l_12,1,0,0},{l_13,l_23,1,0},{l_14,l_24,0,1}}]] * {{0},{0},{a},{b}} + // + // We have L^-T = | 1 -L(0,1) L(0,1)*L(1,2) - L(0,2) -L(0,3) + L(0,2)*L(2,3) + L(0,1)*(L(1,3) - L(1,2)*L(2,3)) | + // | 0 1 -L(1,2) L(1,2)*L(2,3) - L(1,3) | + // | 0 0 1 -L(2,3) | + // | 0 0 0 1 | + // + // Using L(2,3) == 0 we get: + // + // We have L^-T = | 1 -L(0,1) L(0,1)*L(1,2) - L(0,2) L(0,1)*L(1,3) -L(0,3) | + // | 0 1 -L(1,2) -L(1,3) | + // | 0 0 1 0 | + // | 0 0 0 1 | + // + // Multiplied by [0 0 a b]^T: + // + // | a * (L(0,1)*L(1,2) - L(0,2)) + b * (L(0,1)*L(1,3) - L(0,3)) | + // | -a * L(1,2) - b * L(1,3) | + // | a | + // | b | + + v(0) = nullSpace(0) * (L(0,1) * L(1,2) - L(0,2)) + nullSpace(1) * (L(0,1) * L(1,3) - L(0,3)); + v(1) = -nullSpace(0) * L(1,2) - nullSpace(1) * L(1,3); + v(2) = nullSpace(0); + v(3) = nullSpace(1); + } + } + else + { + // Compute inverse of L. + // See above for explanation of L^-1 computation (and assumptions about which values are 0 and 1). + const TReal IL01 = -L(0,1); + const TReal IL02 = L(0,1) * L(1,2) - L(0,2); + const TReal IL12 = -L(1,2); + const TReal IL03 = L(0,1) * L(1,3) - L(0,3); + const TReal IL13 = -L(1,3); + + // Compute inverse of D. + // Inverse[{{d_11,0,0,0},{0,d_22,0,0},{0,0,d_33,d_43},{0,0,d_43,d_44}}] + const TReal ID00 = 1 / D(0,0); + const TReal ID11 = 1 / D(1,1); + matrix ID; + ID(0,0) = D(3,3); + ID(0,1) = -D(3,2); + ID(1,0) = -D(3,2); + ID(1,1) = D(2,2); + multiply(ID, 1 / DD); + + // ### 11. if log10 |u22| > -7.18 + // This implies that u22 > 10^-7.18 ~= 6.607e-8 + static const TReal kSubspaceThreshold = constants::get_subspace_threshold(); + const TReal AU = math_utils::fabs(u22); + if (AU > kSubspaceThreshold) + { + // ### 12. nit = ceil(15/(16.86 + 2 log10 |u22|)) + const int nit = static_cast( + math_utils::ceil(15 / (static_cast(16.8) + 2 * math_utils::log10(AU))) + ); + + // ### 13. Compute Bs = LDL^T by block LDL^T factorization with Bunch-Parlett pivoting + // Already done. + + // ### 14. v = L^-T e4 / ||L^-T e4|| % Initial guess. + // + // | 1 IL01 IL02 IL03 | | 0 | | IL03 | + // | 0 1 IL12 IL13 | * | 0 | = | IL13 | + // | 0 0 1 0 | | 0 | | 0 | + // | 0 0 0 1 | | 1 | | 1 | + v(0) = IL03; + v(1) = IL13; + v(2) = 0; + v(3) = 1; + // Normalization will happen in the loop. + + // ### 15. for i = 1 : nit + for (int i = 0; i < nit; ++i) + { + normalize(v); + + // ### 16. Update v using one step of inverse iteration with LDL^T + // OPTME: Maybe some of these operations could be combined to be optimized...? + + // v = L^-1 * v = IL * v; + v = multiply_il_v(IL01, IL02, IL03, IL12, IL13, v); + + // v = D^-1 = ID * v; + v = multiply_id_v(ID00, ID11, ID, v); + + // v = L^-T * v = IL^T * v = v * IL; + v = multiply_v_il(v, IL01, IL02, IL03, IL12, IL13); + } + // ### 17. end + // The last normalization of v will be done at the end. + } + // ### 18. else + else + { + // ### 19. Compute Bs = LDL^T by block LDL^T factorization with Bunch-Parlett pivoting + // Already done. + + // ### 20. V = L^-T [e3 e4] % Initial guess. + // + // | 1 IL01 IL02 IL03 | | 0 0 | | IL02 IL03 | + // | 0 1 IL12 IL13 | * | 0 0 | = | IL12 IL13 | + // | 0 0 1 0 | | 1 0 | | 1 0 | + // | 0 0 0 1 | | 0 1 | | 0 1 | + const TReal v00 = IL02; + const TReal v10 = IL03; + const TReal v01 = IL12; + const TReal v11 = IL13; + + // ### 21. for i = 1:2 + // ### 22. Orthonormalize V via QR factorization. + // ### 23. Update V using one step of inverse subspace iteration with LDL^T. + vector v0, v1; + orthonormalize_v_with_qr(v0, v1, v00, v10, v01, v11); + + for (int i = 0; i < 2; ++i) + { + v0 = multiply_il_v(IL01, IL02, IL03, IL12, IL13, v0); + v1 = multiply_il_v(IL01, IL02, IL03, IL12, IL13, v1); + + v0 = multiply_id_v(ID00, ID11, ID, v0); + v1 = multiply_id_v(ID00, ID11, ID, v1); + + v0 = multiply_v_il(v0, IL01, IL02, IL03, IL12, IL13); + v1 = multiply_v_il(v1, IL01, IL02, IL03, IL12, IL13); + } + // ### 24. end + + // ### 25. Orthonormalize V via QR factorization. + orthonormalize_v_with_qr(v0, v1); + + // ### 26. Bp = V^T Bs V in R2x2 + // ### 27. Find w, eigenvector of smallest eigenvalue of Bp, by analytic formula. + // ### 28. v = V w + // L has the same form as IL, so we can use the same function to multiply them. + const vector v0_temp = multiply_v_il(v0, L(0,1), L(0,2), L(0,3), L(1,2), L(1,3)); + const vector v1_temp = multiply_v_il(v1, L(0,1), L(0,2), L(0,3), L(1,2), L(1,3)); + const vector H0 = multiply_minus_v_d(v0_temp, D); + const vector H1 = multiply_minus_v_d(v1_temp, D); + const TReal H00 = dot(H0, v0_temp); + const TReal H10 = dot(H0, v1_temp); + const TReal H11 = dot(H1, v1_temp); + if (math_utils::fabs(H10) < static_cast(1.0e-15)) + { + if (H00 > H10) + v = v0; + else + v = v1; + } + else + { + const TReal r = (H00 - H11) / (2 * H10); + const int s = (H10 < 0 ? -1 : 1); + const TReal f = r + s * math_utils::sqrt(1 + r * r); + v(0) = v0(0) * f + v1(0); + v(1) = v0(1) * f + v1(1); + v(2) = v0(2) * f + v1(2); + v(3) = v0(3) * f + v1(3); + } + } + // ### 29. end + } + + // ### 30. Form the matrix Q from v as in Theorem 2.5. + // ### 31. Compute H = Q^T A. + // Both are done at the end of the function. + } + + // Compute rotation from dominant eigen vector v. + normalize(v); + vector vtemp = v; + v(p(0)) = vtemp(0); + v(p(1)) = vtemp(1); + v(p(2)) = vtemp(2); + v(p(3)) = vtemp(3); + + const TReal v12 = 2 * v(0) * v(1); + const TReal v13 = 2 * v(0) * v(2); + const TReal v14 = 2 * v(0) * v(3); + const TReal v22 = 2 * v(1) * v(1); + const TReal v23 = 2 * v(1) * v(2); + const TReal v24 = 2 * v(1) * v(3); + const TReal v33 = 2 * v(2) * v(2); + const TReal v34 = 2 * v(2) * v(3); + const TReal v44 = 2 * v(3) * v(3); + + paramQ(0,0) = 1 - (v33 + v44); + paramQ(0,1) = v23 - v14; + paramQ(0,2) = v24 + v13; + paramQ(1,0) = v23 + v14; + paramQ(1,1) = 1 - (v22 + v44); + paramQ(1,2) = v34 - v12; + paramQ(2,0) = v24 - v13; + paramQ(2,1) = v34 + v12; + paramQ(2,2) = 1 - (v22 + v33); + + // The Matlab implementation returns the opposite of the matrix if det A < 0. + // We don't do that because we want a right-handed rotation. + + // Compute scale. + transpose_multiply(paramH, paramQ, paramA); + + // The Matlab implementation suggests averaging the top and lower part of the + // matrix to ensure symmetry, but we don't do it. + } + + + }; // End of namespace detail. + + +}; // End of namespace polar. + + + + +#endif // __POLAR_DECOMPOSITION_3X3_IMPL_H__ diff --git a/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3_matrix.h b/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3_matrix.h new file mode 100644 index 0000000000..14fa8c3bd3 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/polar_decomposition_3x3_matrix.h @@ -0,0 +1,359 @@ +// MIT License +// +// Copyright (c) 2017 Martin Bisson +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#ifndef __POLAR_DECOMPOSITION_3X3_MATRIX_H__ +#define __POLAR_DECOMPOSITION_3X3_MATRIX_H__ + + + + +#include +#include +#include + + +namespace polar +{ + + + namespace detail + { + + + // Helper methods for standard mathematical function calls. + template + struct math_utils + { + static inline TReal sqrt(const TReal x); + static inline TReal fabs(const TReal x); + static inline TReal clamp(const TReal x, const TReal a, const TReal b); + static inline TReal cos(const TReal x); + static inline TReal acos(const TReal x); + static inline TReal ceil(const TReal x); + static inline TReal log10(const TReal x); + }; + + template <> + inline float math_utils::sqrt(const float x) + { + return ::sqrtf(x); + } + template <> + inline double math_utils::sqrt(const double x) + { + return ::sqrt(x); + } + + template <> + inline float math_utils::fabs(const float x) + { + return ::fabs(x); + } + template <> + inline double math_utils::fabs(const double x) + { + return ::fabs(x); + } + + template + inline TReal math_utils::clamp(const TReal x, const TReal a, const TReal b) + { + assert(a <= b); + if (x < a) + return a; + else if (x > b) + return b; + else + return x; + } + + template <> + inline float math_utils::cos(const float x) + { + return ::cosf(x); + } + template <> + inline double math_utils::cos(const double x) + { + return ::cos(x); + } + + template <> + inline float math_utils::acos(const float x) + { + return ::acosf(x); + } + template <> + inline double math_utils::acos(const double x) + { + return ::acos(x); + } + + template <> + inline float math_utils::ceil(const float x) + { + return ::ceilf(x); + } + template <> + inline double math_utils::ceil(const double x) + { + return ::ceil(x); + } + + template <> + inline float math_utils::log10(const float x) + { + return ::log10f(x); + } + template <> + inline double math_utils::log10(const double x) + { + return ::log10(x); + } + + + }; // End of namespace detail. + + + + + // Implementation of the basic matrix type used in the algorithm. + namespace detail + { + + + // Definition of the column-major matrix type. + template + class matrix + { + public: + inline TReal operator()(int columnIndex, int rowIndex) const; + inline TReal& operator()(int columnIndex, int rowIndex); + inline TReal operator()(int index) const; + inline TReal& operator()(int index); + + private: + TReal data[NbColumns * NbRows]; + }; + + + // We rely on the compiler to be efficient in inlining the calls to this + // method, and especially optimize the double-indices access and convert them + // to single access ones. + template + inline TReal matrix::operator()(int columnIndex, int rowIndex) const + { + assert(0 <= rowIndex); + assert(rowIndex < NbRows); + assert(0 <= columnIndex); + assert(columnIndex < NbColumns); + return data[columnIndex * NbRows + rowIndex]; + } + + template + inline TReal& matrix::operator()(int columnIndex, int rowIndex) + { + assert(0 <= rowIndex); + assert(rowIndex < NbRows); + assert(0 <= columnIndex); + assert(columnIndex < NbColumns); + return data[columnIndex * NbRows + rowIndex]; + } + + template + inline TReal matrix::operator()(int index) const + { + assert(0 <= index); + assert(index < NbRows * NbColumns); + return data[index]; + } + + template + inline TReal& matrix::operator()(int index) + { + assert(0 <= index); + assert(index < NbRows * NbColumns); + return data[index]; + } + + + // Definition of the vector type. + template + class vector + { + public: + inline TReal operator()(int index) const; + inline TReal& operator()(int index); + + private: + TReal data[NbElements]; + }; + + + template + inline TReal vector::operator()(int index) const + { + assert(0 <= index); + assert(index < NbElements); + return data[index]; + } + + template + inline TReal& vector::operator()(int index) + { + assert(0 <= index); + assert(index < NbElements); + return data[index]; + } + + + // + // The following re-implements matrix operations that are very common in + // most linear algebra packages such as GLM or Eigen. However, by design + // this library does not want to depend on such packages, so the simple + // matrix operations are re-implemented here. + // + // Most methods are not implemented in the most generic way; they are + // specialized for the ways they are used in this algorithm specific + // implementation. + // + // They definitely could be implemented in a "cleaner", shorter way + // using the proper libraries. + // + + template + inline void normalize(matrix& m) + { + TReal length = m(0) * m(0); + length += m(1) * m(1); + length += m(2) * m(2); + length += m(3) * m(3); + length += m(4) * m(4); + length += m(5) * m(5); + length += m(6) * m(6); + length += m(7) * m(7); + length += m(8) * m(8); + + // Add small numerical value to make sure we are ok with 0-length. + const TReal factor = 1 / (math_utils::sqrt(length) + (std::numeric_limits::min)()); + + m(0) *= factor; + m(1) *= factor; + m(2) *= factor; + m(3) *= factor; + m(4) *= factor; + m(5) *= factor; + m(6) *= factor; + m(7) *= factor; + m(8) *= factor; + } + + template + inline void normalize(vector& m) + { + TReal length = m(0) * m(0); + length += m(1) * m(1); + length += m(2) * m(2); + length += m(3) * m(3); + + // Add small numerical value to make sure we are ok with 0-length. + const TReal factor = 1 / (math_utils::sqrt(length) + (std::numeric_limits::min)()); + + m(0) *= factor; + m(1) *= factor; + m(2) *= factor; + m(3) *= factor; + } + + + template + inline void transpose_multiply( + matrix& result, + const matrix& a, + const matrix& b + ) + { + // Perform transposition at the same time as the multiplication. + result(0,0) = a(0,0) * b(0,0) + a(0,1) * b(0,1) + a(0,2) * b(0,2); + result(0,1) = a(1,0) * b(0,0) + a(1,1) * b(0,1) + a(1,2) * b(0,2); + result(0,2) = a(2,0) * b(0,0) + a(2,1) * b(0,1) + a(2,2) * b(0,2); + result(1,0) = a(0,0) * b(1,0) + a(0,1) * b(1,1) + a(0,2) * b(1,2); + result(1,1) = a(1,0) * b(1,0) + a(1,1) * b(1,1) + a(1,2) * b(1,2); + result(1,2) = a(2,0) * b(1,0) + a(2,1) * b(1,1) + a(2,2) * b(1,2); + result(2,0) = a(0,0) * b(2,0) + a(0,1) * b(2,1) + a(0,2) * b(2,2); + result(2,1) = a(1,0) * b(2,0) + a(1,1) * b(2,1) + a(1,2) * b(2,2); + result(2,2) = a(2,0) * b(2,0) + a(2,1) * b(2,1) + a(2,2) * b(2,2); + } + + + template + inline void multiply( + matrix& result, + const TReal factor + ) + { + result(0) *= factor; + result(1) *= factor; + result(2) *= factor; + result(3) *= factor; + result(4) *= factor; + result(5) *= factor; + result(6) *= factor; + result(7) *= factor; + result(8) *= factor; + result(9) *= factor; + result(10) *= factor; + result(11) *= factor; + result(12) *= factor; + result(13) *= factor; + result(14) *= factor; + result(15) *= factor; + } + + template + inline void multiply( + matrix& result, + const TReal factor + ) + { + result(0) *= factor; + result(1) *= factor; + result(2) *= factor; + result(3) *= factor; + } + + + template + inline TReal dot(const vector& a, const vector& b) + { + return a(0)*b(0) + a(1)*b(1) + a(2)*b(2) + a(3)*b(3); + } + + + }; // End of namespace detail. + + +}; // End of namespace polar. + + + + +#endif // __POLAR_DECOMPOSITION_3X3_MATRIX_H__ diff --git a/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/solid_dynamics_supplementary.cpp b/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/solid_dynamics_supplementary.cpp new file mode 100644 index 0000000000..54da9540d3 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/particle_dynamics/solid_dynamics/solid_dynamics_supplementary.cpp @@ -0,0 +1,69 @@ +#include "all_solid_dynamics.h" +#include "solid_body.h" +#include "solid_particles.h" +#include "neighbor_relation.h" +#include "base_kernel.h" +#include "base_data_package.h" +#include "elastic_solid.h" +#include "external_force.h" +#include "cell_linked_list.h" +#include "fluid_particles.h" +#include "weakly_compressible_fluid.h" +#include "polar_decomposition_3x3.h" + +using namespace polar; +using namespace SimTK; + +namespace SPH +{ + namespace solid_dynamics + { + //=========================================================================================// + void UpdateElasticNormalDirection::Update(size_t index_i, Real dt) + { + Matd& F = F_[index_i]; + Mat3d R; + Real Q[9], H[9], A[9]; + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + A[i * 3 + j] = F(i, j); + + polar::polar_decomposition(Q, H, A); + //this decomposition has the form A = Q*H, where Q is orthogonal and H is symmetric positive semidefinite. + //Ref. "An algorithm to compute the polar decomposition of a 3*3 matrix, Nicholas J. Higham et al. Numer Algor(2016) " + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + R(i, j) = Q[i * 3 + j]; + n_[index_i] = R * n_0_[index_i]; + } + //=================================================================================================// + void ConstrainSolidBodyPartBySimBody::Update(size_t index_i, Real dt) + { + Vec3 rr, pos, vel, acc; + rr = pos_0_[index_i] - initial_mobod_origin_location_; + mobod_.findStationLocationVelocityAndAccelerationInGround(*simbody_state_, rr, pos, vel, acc); + /** this is how we calculate the particle position in after transform of MBbody. + * const SimTK::Rotation& R_GB = mobod_.getBodyRotation(simbody_state); + * const SimTK::Vec3& p_GB = mobod_.getBodyOriginLocation(simbody_state); + * const SimTK::Vec3 r = R_GB * rr; // re-express station vector p_BS in G (15 flops) + * base_particle_data_i.pos_n_ = (p_GB + r); + */ + pos_n_[index_i] = pos; + vel_n_[index_i] = vel; + dvel_dt_[index_i] = acc; + n_[index_i] = (mobod_.getBodyRotation(*simbody_state_) * n_0_[index_i]); + } + //=================================================================================================// + SimTK::SpatialVec TotalForceOnSolidBodyPartForSimBody + ::ReduceFunction(size_t index_i, Real dt) + { + Vec3 force_from_particle = force_from_fluid_[index_i] + contact_force_[index_i]; + Vec3 displacement(0); + displacement = pos_n_[index_i] - current_mobod_origin_location_; + Vec3 torque_from_particle = cross(displacement, force_from_particle); + + return SimTK::SpatialVec(torque_from_particle, force_from_particle); + } + //=================================================================================================// + } +} \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/particle_generator/CMakeLists.txt b/SPHINXsys/src/for_3D_build/particle_generator/CMakeLists.txt new file mode 100644 index 0000000000..fd5eae60fd --- /dev/null +++ b/SPHINXsys/src/for_3D_build/particle_generator/CMakeLists.txt @@ -0,0 +1,7 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/particle_generator/all_particle_generators.h b/SPHINXsys/src/for_3D_build/particle_generator/all_particle_generators.h new file mode 100644 index 0000000000..ada6310d3a --- /dev/null +++ b/SPHINXsys/src/for_3D_build/particle_generator/all_particle_generators.h @@ -0,0 +1,9 @@ + +#ifndef ALL_PARTICLE_GENERATORS_3D_H +#define ALL_PARTICLE_GENERATORS_3D_H + + + +#include "particle_generator_lattice.h" +#include "particle_generator_network.h" +#endif //ALL_PARTICLE_GENERATORS_3D_H \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_lattice_supplementary.cpp b/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_lattice_supplementary.cpp new file mode 100644 index 0000000000..bd1e841930 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_lattice_supplementary.cpp @@ -0,0 +1,31 @@ +//common functions used by 3d buildings only + +#include "particle_generator_lattice.h" + +#include "geometry.h" +#include "base_mesh.h" +#include "base_body.h" +#include "base_particles.h" + +namespace SPH { + //=================================================================================================// + void ParticleGeneratorLattice::createBaseParticles(BaseParticles* base_particles) + { + std::unique_ptr mesh(new BaseMesh(domain_bounds_, lattice_spacing_, 0)); + Real particle_volume = lattice_spacing_ * lattice_spacing_ * lattice_spacing_; + Vecu number_of_lattices = mesh->NumberOfCellsFromNumberOfGridPoints(mesh->NumberOfGridPoints()); + for (size_t i = 0; i < number_of_lattices[0]; ++i) + for (size_t j = 0; j < number_of_lattices[1]; ++j) + for (size_t k = 0; k < number_of_lattices[2]; ++k) { + Vecd particle_position = mesh->CellPositionFromIndex(Vecu(i, j, k)); + if (body_shape_->checkNotFar(particle_position, lattice_spacing_)) + { + if (body_shape_->checkContain(particle_position)) + { + createABaseParticle(base_particles, particle_position, particle_volume); + } + } + } + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.cpp b/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.cpp new file mode 100644 index 0000000000..3f55eb341a --- /dev/null +++ b/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.cpp @@ -0,0 +1,232 @@ +/** + * @file particle_generator_network.cpp + * @author Chi ZHang and Xiangyu Hu + */ +#include "sph_system.h" +#include "particle_generator_network.h" +#include "cell_linked_list.h" +#include "level_set.h" +#include "base_body.h" +#include "base_particles.h" +#include "in_output.h" + //=================================================================================================// +namespace SPH +{ + //=================================================================================================// + ParticleGeneratorNetwork:: + ParticleGeneratorNetwork(Vecd starting_pnt, Vecd second_pnt, int iterator, Real grad_factor) + : ParticleGenerator(), starting_pnt_(starting_pnt), second_pnt_(second_pnt), + n_it_(iterator), fascicles_(true), segments_in_branch_(10), segment_length_(0), + grad_factor_(grad_factor), body_shape_(nullptr), tree_(nullptr){} + //=================================================================================================// + void ParticleGeneratorNetwork::initialize(SPHBody* sph_body) + { + sph_body_ = sph_body; + cell_linked_list_ = dynamic_cast(sph_body)->cell_linked_list_; + segment_length_ = sph_body_->particle_adaptation_->ReferenceSpacing(); + body_shape_ = sph_body_->body_shape_; + tree_ = new GenerativeTree(sph_body_); + sph_body_->generative_structure_ = tree_; + Vecd displacement = second_pnt_ - starting_pnt_; + Vecd end_direction = displacement / (displacement.norm() + TinyReal); + //add particle to the first branch of the tree + tree_->growAParticleOnBranch(tree_->root_, starting_pnt_, end_direction); + cell_linked_list_->InsertACellLinkedListDataEntry(0, tree_->pos_n_[0]); + } + //=================================================================================================// + Vecd ParticleGeneratorNetwork::getGradientFromNearestPoints(Vecd pt, Real delta) + { + Vecd upgrad(0), downgrad(0); + Vecd shift(delta); + for (int i = 0; i != Dimensions; i++) { + Vecd upwind = pt; + Vecd downwind = pt; + upwind[i] -= shift[i]; + downwind[i] += shift[i]; + ListData up_nearest_list = cell_linked_list_->findNearestListDataEntry(upwind); + ListData down_nearest_list = cell_linked_list_->findNearestListDataEntry(downwind); + upgrad[i] = up_nearest_list.first != MaxSize_t ? (upwind - up_nearest_list.second).norm() / 2.0 * delta : 1.0; + downgrad[i] = down_nearest_list.first != MaxSize_t ? (downwind - down_nearest_list.second).norm() / 2.0 * delta : 1.0; + } + return downgrad - upgrad; + } + //=================================================================================================// + Vecd ParticleGeneratorNetwork::createATentativeNewBranchPoint(Vecd init_point, Vecd dir) + { + Vecd pnt_to_project = init_point + dir * segment_length_; + + Real phi = body_shape_->findSignedDistance(pnt_to_project); + Vecd unit_normal = body_shape_->findNormalDirection(pnt_to_project); + unit_normal /= unit_normal.norm() + TinyReal; + Vecd new_point = pnt_to_project - phi * unit_normal; + return new_point; + } + //=================================================================================================// + bool ParticleGeneratorNetwork:: + isCollision(Vecd& new_point, ListData& nearest_neighbor, size_t parent_id) + { + bool collision = false; + bool is_family = false; + + collision = extraCheck(new_point); + + size_t edge_location = tree_->BranchLocation(nearest_neighbor.first); + if (edge_location == parent_id) is_family = true; + for (const size_t& brother_branch : tree_->branches_[parent_id]->out_edge_) + { + if (edge_location == brother_branch) is_family = true; + } + + if (!is_family) + { + Real min_distance = (new_point - nearest_neighbor.second).norm(); + if (min_distance < 5.0 * segment_length_) collision = true; + } + + return collision; + } + //=================================================================================================// + bool ParticleGeneratorNetwork:: + createABranchIfValid(SPHBody* sph_body, size_t parent_id, Real angle, + Real repulsivity, size_t number_segments) + { + bool is_valid = false; + GenerativeTree::Branch* parent_branch = tree_->branches_[parent_id]; + IndexVector& parent_elements = parent_branch->inner_particles_; + StdLargeVec &tree_points = tree_->pos_n_; + + Vecd init_point = tree_points[parent_elements.back()]; + Vecd init_direction = parent_branch->end_direction_; + + + Vecd surface_norm = body_shape_->findNormalDirection(init_point); + surface_norm /= surface_norm.norm() + TinyReal; + Vecd in_plane = - SimTK::cross(init_direction, surface_norm); + + Real delta = grad_factor_ * segment_length_; + Vecd grad = getGradientFromNearestPoints(init_point, delta); + Vecd dir = cos(angle) * init_direction + sin(angle) * in_plane; + dir /= dir.norm() + TinyReal; + Vecd end_direction = (repulsivity * grad + dir) / ((repulsivity * grad + dir).norm() + TinyReal); + Vecd end_point = init_point; + + Vecd new_point = createATentativeNewBranchPoint(end_point, end_direction); + ListData nearest_neighbor = cell_linked_list_->findNearestListDataEntry(new_point); + if (!isCollision(new_point, nearest_neighbor, parent_id)) + { + is_valid = true; + GenerativeTree::Branch* new_branch = new GenerativeTree::Branch(parent_id, tree_); + tree_->growAParticleOnBranch(new_branch, new_point, end_direction); + + for (size_t i = 1; i < number_segments; i++) + { + surface_norm = body_shape_->findNormalDirection(new_point); + surface_norm /= surface_norm.norm() + TinyReal; + /** Project grad to surface. */ + grad = getGradientFromNearestPoints(new_point, delta); + grad -= dot(grad, surface_norm) * surface_norm; + dir = (repulsivity * grad + end_direction) / ((repulsivity * grad + end_direction).norm() + TinyReal); + end_direction = dir; + end_point = new_point; + + new_point = createATentativeNewBranchPoint(end_point, end_direction); + ListData nearest_neighbor = cell_linked_list_->findNearestListDataEntry(new_point); + if (isCollision(new_point, nearest_neighbor, parent_id)) + { + new_branch->is_terminated_ = true; + std::cout << "Branch Collision Detected, Break! " << std::endl; + break; + } + /** This constraint imposed to avoid too small time step size. */ + if((new_point - end_point).norm() < 0.5 * segment_length_) + { + new_branch->is_terminated_ = true; + std::cout << "New branch point is too close, Break! " << std::endl; + break; + } + tree_->growAParticleOnBranch(new_branch, new_point, end_direction); + + } + + for (const size_t& particle_idx : new_branch->inner_particles_) + { + cell_linked_list_->InsertACellLinkedListDataEntry(particle_idx, tree_points[particle_idx]); + } + } + + return is_valid; + } + //=================================================================================================// + void ParticleGeneratorNetwork::createBaseParticles(BaseParticles* base_particles) + { + In_Output* in_output = sph_body_->getSPHSystem().in_output_; + BodyStatesRecordingToVtu write_states(*in_output, { sph_body_ }); + + std::cout << "Now creating Particles on network... " << "\n" << std::endl; + + //the second branch + bool is_valid = createABranchIfValid(sph_body_, 0, 0.0, 0.0, segments_in_branch_); + + size_t ite = 0; + sph_body_->setNewlyUpdated(); + write_states.writeToFile(0); + + IndexVector branches_to_grow; + IndexVector new_branches_to_grow; + if (is_valid) branches_to_grow.push_back(tree_->last_branch_id_); + + if (fascicles_) + { + /** Set vertices in family branch. */ + branches_to_grow.clear(); + for (size_t i = 0; i != 2; i++) + { + /** Creating a new branch. */ + Real angle_to_use = fascicle_angles_[i]; + size_t fascicles_segments = int(fascicle_ratio_ * segments_in_branch_); + bool is_valid = createABranchIfValid(sph_body_, 1, angle_to_use, 0.0, fascicles_segments); + if (is_valid) branches_to_grow.push_back(tree_->last_branch_id_); + } + + ite++; + sph_body_->setNewlyUpdated(); + write_states.writeToFile(ite); + + } + + for(size_t i = 0; i != n_it_; i++) + { + new_branches_to_grow.clear(); + random_shuffle(branches_to_grow.begin(), branches_to_grow.end()); + for(size_t j = 0; j != branches_to_grow.size(); j++) + { + size_t grow_id = branches_to_grow[j]; + Real rand_num = ((Real)rand() / (RAND_MAX)) - 0.5; + Real angle_to_use = angle_ + rand_num * 0.05; + for(size_t k = 0; k != 2; k++) + { + /** Creating a new branch with fixed number of segments. */ + size_t random_number_segments = segments_in_branch_; + bool is_valid = createABranchIfValid(sph_body_, grow_id, angle_to_use, repulsivity_, + random_number_segments); + + if(is_valid && !tree_->LastBranch()->is_terminated_) + { + new_branches_to_grow.push_back(tree_->last_branch_id_); + } + + angle_to_use *= -1.0; + } + } + branches_to_grow = new_branches_to_grow; + + ite++; + sph_body_->setNewlyUpdated(); + write_states.writeToFile(ite); + } + + std::cout << base_particles->total_real_particles_ << " Particles has been successfully created!" << "\n" << std::endl; + } + //=================================================================================================// +} +//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.h b/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.h new file mode 100644 index 0000000000..d5fd520b65 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.h @@ -0,0 +1,116 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file particle_generator_network.h + * @brief This is a class of particle generator, which generates particles + * with in network or tree form. + * @author Chi ZHang and Xiangyu Hu + */ + +#ifndef PARTICLE_GENERATOR_NETWORK_H +#define PARTICLE_GENERATOR_NETWORK_H + + +#include "sph_data_conainers.h" +#include "base_particle_generator.h" +#include "generative_structures.h" + +namespace SPH +{ + class BaseLevelSet; + class BaseCellLinkedList; + class ComplexShape; + + /** + * @class ParticleGeneratorNetwork + * @brief Generate a tree-shape network for the conduction system of a heart with particles. + */ + class ParticleGeneratorNetwork : public ParticleGenerator + { + public: + ParticleGeneratorNetwork(Vecd starting_pnt, Vecd second_pnt, int iterator, Real grad_factor); + virtual ~ParticleGeneratorNetwork() {}; + /** + *@brief Parameters initialization. + *@param[in] sph_body*(SPHBody) SPHBody to whom it generate particles. + */ + virtual void initialize(SPHBody* sph_body) override; + /** + *@brief Created base particles based on edges in branch. + *@param[in] base_particles(BaseParticles) Pointer to baseparticle link to a SPHBody. + */ + virtual void createBaseParticles(BaseParticles* base_particles) override; + protected: + Vecd starting_pnt_; /**< Starting point for net work. */ + Vecd second_pnt_; /**< Second point, approximate the growing direction. */ + size_t n_it_; /**< Number of iterations (generations of branch. */ + bool fascicles_; /**< Create fascicles? */ + size_t segments_in_branch_; /**< approximated number of segments in a branch. */ + Real segment_length_; /**< segment length of the branch. */ + Real angle_ = 0.3; /**< angle with respect to the direction of the previous edge and the new edge. */ + Real repulsivity_ = 0.175; /**< repulsivity parameter. */ + Real grad_factor_; /**< Factor for computing gradient from nearest node. */ + std::vector fascicle_angles_ = {-1.25, 0.75}; /**< angles with respect to the initial edge of the fascicles.*/ + Real fascicle_ratio_ = 15.0; /**< ratio of length of the fascicles. Include one per fascicle to include.*/ + ComplexShape* body_shape_; + BaseCellLinkedList* cell_linked_list_; + GenerativeTree *tree_; + /** + *@brief Get the gradient from nearest points, for imposing repulsive force. + *@param[in] pt(Vecd) Inquiry point. + *@param[in] delta(Real) parameter for gradient calculation. + */ + Vecd getGradientFromNearestPoints(Vecd pt, Real delta); + /** + *@brief Create a new branch if it is valid. + *@param[in] sph_body(SPHBody) The SPHBody to whom the tree belongs. + *@param[in] parent_id(size_t) Id of parent branch. + *@param[in] angle(Real) The angle for growing new points. + *@param[in] repulsivity(Real) The repulsivity for creating new points. + *@param[in] number_segments(size_t) Number of segments in this branch. + */ + bool createABranchIfValid(SPHBody* sph_body, size_t parent_id, Real angle, + Real repulsivity, size_t number_segments); + /** + *@brief Functions that creates a new node in the mesh surface and it to the queue is it lies in the surface. + *@param[in] init_node vector that contains the coordinates of the last node added in the branch. + * vector that contains the coordinates of the last node added in the branch. + *@param[in] dir a vector that contains the direction from the init_node to the node to project. + *@param[out] end point of the created segment. + */ + Vecd createATentativeNewBranchPoint(Vecd init_point, Vecd dir); + /** + *@brief Check if the new point has collision with the existing points. + *@param[in] new_point(Vecd) The enquiry point. + *@param[in] nearest_neighbor(ListData) The nearest point of the existing points. + *@param[in] parent_id(size_t) Id of parent branch + */ + bool isCollision(Vecd& new_point, ListData& nearest_neighbor, size_t parent_id); + /** + *@brief Check if the new point is valid according to extra constraint. + *@param[in] new_point(Vecd) The enquiry point. + */ + virtual bool extraCheck(Vecd& new_point){return false;}; + }; +} +#endif //PARTICLE_GENERATOR_NETWORK_H \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/particles/CMakeLists.txt b/SPHINXsys/src/for_3D_build/particles/CMakeLists.txt new file mode 100644 index 0000000000..fd5eae60fd --- /dev/null +++ b/SPHINXsys/src/for_3D_build/particles/CMakeLists.txt @@ -0,0 +1,7 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/particles/solid_particles_supplementary.cpp b/SPHINXsys/src/for_3D_build/particles/solid_particles_supplementary.cpp new file mode 100644 index 0000000000..3d44f71ce1 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/particles/solid_particles_supplementary.cpp @@ -0,0 +1,34 @@ +#include "solid_particles.h" +#include "base_body.h" + +#include + +namespace SPH { + //=============================================================================================// + void SolidParticles::ParticleTranslationAndRotation(Transformd& transform) + { + std::cout << "\n Error: the function ParticleTranslationAndRotation in 3d is not defined!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + //=================================================================================================// + Real ElasticSolidParticles::von_Mises_stress(size_t particle_i) + { + Real J = rho0_ / rho_n_[particle_i]; + Mat3d F = F_[particle_i]; + Mat3d stress = stress_PK1_[particle_i]; + Mat3d sigma = (stress * ~F) / J; + + Real sigmaxx = sigma(0, 0); + Real sigmayy = sigma(1, 1); + Real sigmazz = sigma(2, 2); + Real sigmaxy = sigma(0, 1); + Real sigmaxz = sigma(0, 2); + Real sigmayz = sigma(1, 2); + + return sqrt(sigmaxx * sigmaxx + sigmayy * sigmayy + sigmazz * sigmazz + - sigmaxx * sigmayy - sigmaxx * sigmazz - sigmayy * sigmazz + + 3.0 * (sigmaxy * sigmaxy + sigmaxz * sigmaxz + sigmayz * sigmayz)); + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/CMakeLists.txt b/SPHINXsys/src/shared/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/bodies/CMakeLists.txt b/SPHINXsys/src/shared/bodies/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/bodies/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/bodies/all_bodies.h b/SPHINXsys/src/shared/bodies/all_bodies.h new file mode 100644 index 0000000000..0947695d6a --- /dev/null +++ b/SPHINXsys/src/shared/bodies/all_bodies.h @@ -0,0 +1,14 @@ + +#ifndef ALL_BODIES_H +#define ALL_BODIES_H + + +/** @file +This is the header file that user code should include to pick up all +bodies used in SPHinXsys. **/ + +#pragma once + +#include "fluid_body.h" +#include "solid_body.h" +#endif //ALL_BODIES_H diff --git a/SPHINXsys/src/shared/bodies/base_body.cpp b/SPHINXsys/src/shared/bodies/base_body.cpp new file mode 100644 index 0000000000..b157de9ea8 --- /dev/null +++ b/SPHINXsys/src/shared/bodies/base_body.cpp @@ -0,0 +1,276 @@ +/** + * @file base_body.cpp + * @brief Here, Functions belong to BaseBody, RealBody and FictitiousBody are given. + * @author Chi ZHang and Xiangyu Hu + */ +#include "base_body.h" + +#include "sph_system.h" +#include "base_particles.h" +#include "body_relation.h" +#include "cell_linked_list.h" + +namespace SPH +{ + //=================================================================================================// + SPHBody::SPHBody(SPHSystem &sph_system, std::string body_name, + ParticleAdaptation *particle_adaptation, ParticleGenerator *particle_generator) + : sph_system_(sph_system), body_name_(body_name), newly_updated_(true), + body_domain_bounds_(0, 0), is_domain_bounds_determined_(false), + particle_adaptation_(particle_adaptation), particle_generator_(particle_generator), + body_shape_(nullptr), generative_structure_(nullptr) + { + sph_system_.addABody(this); + particle_adaptation_->initialize(this); + } + //=================================================================================================// + BoundingBox SPHBody::getSPHSystemBounds() + { + return sph_system_.system_domain_bounds_; + } + //=================================================================================================// + std::string SPHBody::getBodyName() + { + return body_name_; + } + //=================================================================================================// + SPHSystem &SPHBody::getSPHSystem() + { + return sph_system_; + } + //=================================================================================================// + void SPHBody::useParticleGeneratorReload() + { + particle_generator_->~ParticleGenerator(); + particle_generator_ = new ParticleGeneratorReload(sph_system_.in_output_, body_name_); + } + //=================================================================================================// + void SPHBody::assignBaseParticles(BaseParticles *base_particles) + { + base_particles_ = base_particles; + } + //=================================================================================================// + void SPHBody::allocateConfigurationMemoriesForBufferParticles() + { + for (size_t i = 0; i < body_relations_.size(); i++) + { + body_relations_[i]->updateConfigurationMemories(); + } + } + //=================================================================================================// + void SPHBody::setBodyDomainBounds(BoundingBox body_domain_bounds) + { + body_domain_bounds_ = body_domain_bounds; + is_domain_bounds_determined_ = true; + }; + //=================================================================================================// + BoundingBox SPHBody::getBodyDomainBounds() + { + if (!is_domain_bounds_determined_) + { + body_domain_bounds_ = body_shape_->findBounds(); + is_domain_bounds_determined_ = true; + } + return body_domain_bounds_; + } + //=================================================================================================// + void SPHBody::writeParticlesToVtuFile(std::ofstream &output_file) + { + base_particles_->writeParticlesToVtuFile(output_file); + newly_updated_ = false; + } + //=================================================================================================// + void SPHBody::writeParticlesToPltFile(std::ofstream &output_file) + { + if (newly_updated_) + base_particles_->writeParticlesToPltFile(output_file); + newly_updated_ = false; + } + //=================================================================================================// + void SPHBody::writeParticlesToXmlForRestart(std::string &filefullpath) + { + base_particles_->writeParticlesToXmlForRestart(filefullpath); + } + //=================================================================================================// + void SPHBody::readParticlesFromXmlForRestart(std::string &filefullpath) + { + base_particles_->readParticleFromXmlForRestart(filefullpath); + } + //=================================================================================================// + void SPHBody::writeToXmlForReloadParticle(std::string &filefullpath) + { + base_particles_->writeToXmlForReloadParticle(filefullpath); + } + //=================================================================================================// + void SPHBody::readFromXmlForReloadParticle(std::string &filefullpath) + { + base_particles_->readFromXmlForReloadParticle(filefullpath); + } + //=================================================================================================// + RealBody::RealBody(SPHSystem &sph_system, std::string body_name, + ParticleAdaptation *particle_adaptation, ParticleGenerator *particle_generator) + : SPHBody(sph_system, body_name, particle_adaptation, particle_generator), + particle_sorting_(this) + { + sph_system.addARealBody(this); + cell_linked_list_ = particle_adaptation_->createCellLinkedList(); + size_t number_of_split_cell_lists = powerN(3, Vecd(0).size()); + split_cell_lists_.resize(number_of_split_cell_lists); + } + //=================================================================================================// + void RealBody::assignBaseParticles(BaseParticles *base_particles) + { + SPHBody::assignBaseParticles(base_particles); + particle_sorting_.assignBaseParticles(base_particles); + cell_linked_list_->assignBaseParticles(base_particles); + } + //=================================================================================================// + void RealBody::sortParticleWithCellLinkedList() + { + StdLargeVec &sequence = base_particles_->sequence_; + size_t size = base_particles_->total_real_particles_; + cell_linked_list_->computingSequence(sequence); + particle_sorting_.sortingParticleData(sequence.data(), size); + } + //=================================================================================================// + void RealBody::updateCellLinkedList() + { + cell_linked_list_->UpdateCellLists(); + } + FictitiousBody:: + FictitiousBody(SPHSystem &system, std::string body_name, + ParticleAdaptation *particle_adaptation, ParticleGenerator *particle_generator) + : SPHBody(system, body_name, particle_adaptation, particle_generator) + { + system.addAFictitiousBody(this); + } + //=================================================================================================// + BodyPartByShape::BodyPartByShape(SPHBody *body, std::string body_part_name) + : BodyPart(body, body_part_name), body_part_shape_(nullptr) {} + //=================================================================================================// + BoundingBox BodyPartByShape::BodyPartBounds() + { + return body_part_shape_->findBounds(); + } + //=================================================================================================// + void BodyPartByParticle::tagAParticle(size_t particle_index) + { + body_part_particles_.push_back(particle_index); + } + //=================================================================================================// + void BodyPartByParticle::tagBodyPart() + { + BaseParticles *base_particles = body_->base_particles_; + for (size_t i = 0; i < base_particles->total_real_particles_; ++i) + { + if (body_part_shape_->checkContain(base_particles->pos_n_[i])) + tagAParticle(i); + } + } + //=================================================================================================// + ShapeSurface::ShapeSurface(SPHBody *body) + : BodyPartByParticle(body, "Surface"), + particle_spacing_min_(body->particle_adaptation_->MinimumSpacing()) + { + tagBodyPart(); + } + //=================================================================================================// + void ShapeSurface::tagBodyPart() + { + BaseParticles *base_particles = body_->base_particles_; + for (size_t i = 0; i < base_particles->total_real_particles_; ++i) + { + Real phi = body_->body_shape_->findSignedDistance(base_particles->pos_n_[i]); + if (fabs(phi) < particle_spacing_min_) + tagAParticle(i); + } + std::cout << "Number of surface particles : " << body_part_particles_.size() << std::endl; + } + //=================================================================================================// + ShapeSurfaceLayer::ShapeSurfaceLayer(SPHBody *body, Real layer_thickness) + : BodyPartByParticle(body, "InnerLayers"), + thickness_threshold_(body->particle_adaptation_->ReferenceSpacing() * layer_thickness) + { + tagBodyPart(); + } + //=================================================================================================// + void ShapeSurfaceLayer::tagBodyPart() + { + BaseParticles *base_particles = body_->base_particles_; + for (size_t i = 0; i < base_particles->total_real_particles_; ++i) + { + Vecd position_i = base_particles->pos_n_[i]; + Real distance = fabs(body_->body_shape_->findSignedDistance(position_i)); + if (distance < thickness_threshold_) + tagAParticle(i); + } + std::cout << "Number of inner layers particles : " << body_part_particles_.size() << std::endl; + } + //=================================================================================================// + BodyPartByCell::BodyPartByCell(RealBody *real_body, std::string body_part_name) + : BodyPartByShape(real_body, body_part_name), real_body_(real_body), + checkIncluded_(std::bind(&BodyPartByCell::checkIncluded, this, _1, _2)) {} + //=================================================================================================// + bool BodyPartByCell::checkIncluded(Vecd cell_position, Real threshold) + { + return body_part_shape_->checkNotFar(cell_position, threshold); + } + //=================================================================================================// + void BodyPartByCell::tagBodyPart() + { + real_body_->cell_linked_list_->tagBodyPartByCell(body_part_cells_, checkIncluded_); + } + //=================================================================================================// + NearShapeSurface:: + NearShapeSurface(RealBody *real_body, ComplexShape *complex_shape, std::string body_part_name) + : BodyPartByCell(real_body, body_part_name) + { + level_set_complex_shape_ = new LevelSetComplexShape(real_body, *complex_shape, true); + body_part_shape_ = level_set_complex_shape_; + tagBodyPart(); + } + //=================================================================================================// + NearShapeSurface::NearShapeSurface(RealBody *real_body) + : BodyPartByCell(real_body, "NearShapeSurface") + { + body_part_shape_ = real_body->body_shape_; + level_set_complex_shape_ = dynamic_cast(body_part_shape_); + if (level_set_complex_shape_ == nullptr) + { + std::cout << "\n FAILURE: LevelSetComplexShape is undefined!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + tagBodyPart(); + } + //=================================================================================================// + LevelSetComplexShape *NearShapeSurface::getLevelSetComplexShape() + { + return level_set_complex_shape_; + } + //=================================================================================================// + bool NearShapeSurface::checkIncluded(Vecd cell_position, Real threshold) + { + return body_part_shape_->checkNearSurface(cell_position, threshold); + } + //=================================================================================================// + TerminateBranches::TerminateBranches(SPHBody *body) + : BodyPartByParticle(body, "Leaves"), + tree_(dynamic_cast(body->generative_structure_)) + { + tagBodyPart(); + } + //=================================================================================================// + void TerminateBranches::tagBodyPart() + { + for (const auto *branch : tree_->branches_) + { + if (branch->is_terminated_) + { + size_t particle_id = branch->inner_particles_.back(); + tagAParticle(particle_id); + } + } + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/bodies/base_body.h b/SPHINXsys/src/shared/bodies/base_body.h new file mode 100644 index 0000000000..f6903912f6 --- /dev/null +++ b/SPHINXsys/src/shared/bodies/base_body.h @@ -0,0 +1,314 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file base_body.h + * @brief This is the base classes of SPH bodies. The real body is for + * that with cell linked list and the fictitious one does not. + * Before the definition of the SPH bodies, the shapes with complex + * geometries, i.e. those are produced by advanced binary operation, + * such as intersection, should be produced first. + * Then, all shapes used in body definition should be either contain + * or not contain each other. + * Partial overlap between them are not premitted. + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#ifndef BASE_BODY_H +#define BASE_BODY_H + +#include "base_data_package.h" +#include "sph_data_conainers.h" +#include "particle_adaptation.h" +#include "all_particle_generators.h" +#include "particle_sorting.h" +#include "all_geometries.h" +#include "generative_structures.h" + +#include + +namespace SPH +{ + class SPHSystem; + class BaseParticles; + class BaseCellLinkedList; + class SPHBodyRelation; + + /** + * @class SPHBody + * @brief SPHBody is a base body with basic data and functions. + * Its derived class can be a real fluid body, a real deformable solid body, + * a static or moving solid body or a fictitious body. + * Note that only real bodies have cell linked list. + */ + class SPHBody + { + protected: + SPHSystem &sph_system_; + std::string body_name_; + bool newly_updated_; /**< whether this body is in a newly updated state */ + /**< Computational domain bounds for boundary conditions. + * Note that domain bounds may be different from those of the initial body geometry. */ + BoundingBox body_domain_bounds_; + bool is_domain_bounds_determined_; + + public: + ParticleAdaptation *particle_adaptation_; /**< Particle adapation policy. */ + ParticleGenerator *particle_generator_; /**< Particle generator manner */ + BaseParticles *base_particles_; /**< Base particles of this body. */ + PositionsAndVolumes body_input_points_volumes_; /**< For direct generate particles. Note this should be moved to direct generator. */ + ComplexShape *body_shape_; /**< describe the geometry of the body*/ + GenerativeStructure *generative_structure_; /**< structure which can be used to generate particles or/and configurations directly*/ + /** + * @brief particle by cells lists is for parallel splitting algorithm. + * All particles in each cell are collected together. + * If two partiles each belongs two different cell entries, + * they have no interaction because they are too far. + */ + SplitCellLists split_cell_lists_; + + StdVec body_relations_; /**< all contact relations centered from this body **/ + + explicit SPHBody(SPHSystem &sph_system, std::string body_name, + ParticleAdaptation *particle_adaptation = new ParticleAdaptation(), + ParticleGenerator *particle_generator = new ParticleGeneratorLattice()); + virtual ~SPHBody(){}; + + std::string getBodyName(); + SPHSystem &getSPHSystem(); + Real getSPHBodyResolutionRef() { return particle_adaptation_->ReferenceSpacing(); }; + void setNewlyUpdated() { newly_updated_ = true; }; + void setNotNewlyUpdated() { newly_updated_ = false; }; + bool checkNewlyUpdated() { return newly_updated_; }; + void useParticleGeneratorReload(); + + void setBodyDomainBounds(BoundingBox body_domain_bounds); + BoundingBox getBodyDomainBounds(); + BoundingBox getSPHSystemBounds(); + + /** This will be called in BaseParticle constructor + * and is important because particles are not defined in SPHBody constructor. */ + virtual void assignBaseParticles(BaseParticles *base_particles); + void allocateConfigurationMemoriesForBufferParticles(); + + virtual void writeParticlesToVtuFile(std::ofstream &output_file); + virtual void writeParticlesToPltFile(std::ofstream &output_file); + virtual void writeParticlesToXmlForRestart(std::string &filefullpath); + virtual void readParticlesFromXmlForRestart(std::string &filefullpath); + virtual void writeToXmlForReloadParticle(std::string &filefullpath); + virtual void readFromXmlForReloadParticle(std::string &filefullpath); + virtual SPHBody *ThisObjectPtr() { return this; }; + }; + + /** + * @class RealBody + * @brief Derived class from SPHBody. + * With inner particle configuration or inner interactions. + */ + class RealBody : public SPHBody + { + public: + ParticleSorting particle_sorting_; + BaseCellLinkedList *cell_linked_list_; /**< Cell linked mesh of this body. */ + + RealBody(SPHSystem &sph_system, std::string body_name, ParticleAdaptation *particle_adaptation, + ParticleGenerator *particle_generator = new ParticleGeneratorLattice()); + RealBody(SPHSystem &sph_system, std::string body_name, Real sph_body_resolution_ref, + ParticleAdaptation *particle_adaptation, + ParticleGenerator *particle_generator = new ParticleGeneratorLattice()); + virtual ~RealBody(){}; + + /** This will be called in BaseParticle constructor + * and is important because particles are not defined in FluidBody constructor. */ + virtual void assignBaseParticles(BaseParticles *base_particles) override; + virtual void sortParticleWithCellLinkedList(); + virtual void updateCellLinkedList(); + }; + + /** + * @class FictitiousBody + * @brief Derived class from SPHBody. + * Without inner configuration or inner interaction. + */ + class FictitiousBody : public SPHBody + { + public: + FictitiousBody(SPHSystem &system, std::string body_name, + ParticleAdaptation *particle_adaptation = new ParticleAdaptation(), + ParticleGenerator *particle_generator = new ParticleGeneratorDirect()); + virtual ~FictitiousBody(){}; + }; + + /** + * @class BodyPart + * @brief An abstract auxillary class for SPHBody to indicate a part of the body. + */ + class BodyPart + { + public: + BodyPart(SPHBody *body, std::string body_part_name) + : body_(body), body_part_name_(body_part_name){}; + virtual ~BodyPart(){}; + + SPHBody *getBody() { return body_; }; + std::string BodyPartName() { return body_part_name_; }; + + protected: + SPHBody *body_; + std::string body_part_name_; + + virtual void tagBodyPart() = 0; + }; + + /** + * @class BodyPartByShape + * @brief An auxillary class for SPHBody to indicate + * a part of the body defined by a presribed complex shape. + */ + class BodyPartByShape : public BodyPart + { + public: + BodyPartByShape(SPHBody *body, std::string body_part_name); + virtual ~BodyPartByShape(){}; + + ComplexShape *getBodyPartShape() { return body_part_shape_; }; + BoundingBox BodyPartBounds(); + + protected: + ComplexShape *body_part_shape_; + }; + /** + * @class BodyPartByParticle + * @brief An auxillary class for SPHBody to + * indicate a part of the body moving together with particles. + */ + class BodyPartByParticle : public BodyPartByShape + { + public: + IndexVector body_part_particles_; /**< Collection particle in this body part. */ + + BodyPartByParticle(SPHBody *body, std::string body_part_name) + : BodyPartByShape(body, body_part_name){}; + + virtual ~BodyPartByParticle(){}; + + protected: + void tagAParticle(size_t particle_index); + virtual void tagBodyPart() override; + }; + + /** + * @class ShapeSurface + * @brief A auxillary class for Body to + * indicate the surface of a shape + */ + class ShapeSurface : public BodyPartByParticle + { + public: + ShapeSurface(SPHBody *body); + virtual ~ShapeSurface(){}; + + protected: + Real particle_spacing_min_; + virtual void tagBodyPart() override; + }; + + /** + * @class ShapeSurfaceLayer + * @brief A auxillary class for Body to + * indicate the particles within the inner layers of a shape + */ + class ShapeSurfaceLayer : public BodyPartByParticle + { + public: + ShapeSurfaceLayer(SPHBody *body, Real layer_thickness = 3.0); + virtual ~ShapeSurfaceLayer(){}; + + protected: + Real thickness_threshold_; + + virtual void tagBodyPart() override; + }; + + /** + * @class BodyPartByCell + * @brief An auxillary class for SPHBody to + * indicate a part of the body fixed in space defined by mesh cells. + */ + using namespace std::placeholders; + class BodyPartByCell : public BodyPartByShape + { + protected: + RealBody *real_body_; + typedef std::function CheckIncludedFunctor; + CheckIncludedFunctor checkIncluded_; + + /** all cells near or contained by the body part shape are included */ + virtual bool checkIncluded(Vecd cell_position, Real threshold); + virtual void tagBodyPart() override; + + public: + CellLists body_part_cells_; /**< Collection of cells to indicate the body part. */ + + BodyPartByCell(RealBody *real_body, std::string body_part_name); + virtual ~BodyPartByCell(){}; + }; + + /** + * @class NearShapeSurface + * @brief An auxillary class for SPHBody to + * indicate the region close to the surface of shape. + */ + class NearShapeSurface : public BodyPartByCell + { + public: + /** for the case that the body part shape is not that of the body */ + NearShapeSurface(RealBody *real_body, ComplexShape *complex_shape, std::string body_part_name); + /** for the case that the body part is the surface of the body shape */ + NearShapeSurface(RealBody *real_body); + virtual ~NearShapeSurface(){}; + + LevelSetComplexShape *getLevelSetComplexShape(); + + protected: + LevelSetComplexShape *level_set_complex_shape_; + /** only cells near the surface of the body part shape are included */ + virtual bool checkIncluded(Vecd cell_position, Real threshold) override; + }; + + /** + * @class TerminateBranches + * @brief A auxillary class for a Tree-like Body to + * indicate the particles from the terminates of the tree. + */ + class TerminateBranches : public BodyPartByParticle + { + public: + TerminateBranches(SPHBody *body); + virtual ~TerminateBranches(){}; + + protected: + GenerativeTree *tree_; + virtual void tagBodyPart() override; + }; +} +#endif //BASE_BODY_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/bodies/body_relation.cpp b/SPHINXsys/src/shared/bodies/body_relation.cpp new file mode 100644 index 0000000000..11569dbc45 --- /dev/null +++ b/SPHINXsys/src/shared/bodies/body_relation.cpp @@ -0,0 +1,272 @@ +/** + * @file body_relation.cpp + * @brief Here, Functions belong to BaseBody, RealBody and FictitiousBody are given. + * @author Chi ZHang and Xiangyu Hu + */ + +#include "body_relation.h" + +#include "base_kernel.h" +#include "base_particles.h" +#include "cell_linked_list.hpp" + +namespace SPH +{ + //=================================================================================================// + SPHBodyRelation::SPHBodyRelation(SPHBody* sph_body) + : sph_body_(sph_body), base_particles_(sph_body->base_particles_) {} + //=================================================================================================// + BaseBodyRelationInner::BaseBodyRelationInner(RealBody* real_body) + : SPHBodyRelation(real_body), real_body_(real_body) + { + subscribeToBody(); + updateConfigurationMemories(); + } + //=================================================================================================// + void BaseBodyRelationInner::updateConfigurationMemories() + { + size_t updated_size = sph_body_->base_particles_->real_particles_bound_; + inner_configuration_.resize(updated_size, Neighborhood()); + } + //=================================================================================================// + void BaseBodyRelationInner::resetNeighborhoodCurrentSize() + { + parallel_for(blocked_range(0, base_particles_->total_real_particles_), + [&](const blocked_range& r) { + for (size_t num = r.begin(); num != r.end(); ++num) { + inner_configuration_[num].current_size_ = 0; + } + }, ap); + } + //=================================================================================================// + BodyRelationInner::BodyRelationInner(RealBody* real_body) + : BaseBodyRelationInner(real_body), get_inner_neighbor_(real_body), + cell_linked_list_(dynamic_cast(real_body->cell_linked_list_)) {} + //=================================================================================================// + void BodyRelationInner::updateConfiguration() + { + resetNeighborhoodCurrentSize(); + cell_linked_list_->searchNeighborsByParticles(base_particles_->total_real_particles_, *base_particles_, + inner_configuration_, get_particle_index_, get_single_search_depth_, get_inner_neighbor_); + } + //=================================================================================================// + BodyRelationInnerVariableSmoothingLength:: + BodyRelationInnerVariableSmoothingLength(RealBody* real_body) + : BaseBodyRelationInner(real_body), total_levels_(0), + get_inner_neighbor_variable_smoothing_length_(real_body) + { + MultilevelCellLinkedList* multi_level_cell_linked_list = + dynamic_cast(real_body->cell_linked_list_); + cell_linked_list_levels_ = multi_level_cell_linked_list->getMeshLevels(); + total_levels_ = cell_linked_list_levels_.size(); + for (size_t l = 0; l != total_levels_; ++l) { + get_multi_level_search_depth_.push_back( + new SearchDepthVariableSmoothingLength(real_body, cell_linked_list_levels_[l])); + } + } + //=================================================================================================// + void BodyRelationInnerVariableSmoothingLength::updateConfiguration() + { + resetNeighborhoodCurrentSize(); + for (size_t l = 0; l != total_levels_; ++l) { + cell_linked_list_levels_[l]->searchNeighborsByParticles(base_particles_->total_real_particles_, + *base_particles_, inner_configuration_, get_particle_index_, + *get_multi_level_search_depth_[l], get_inner_neighbor_variable_smoothing_length_); + } + } + //=================================================================================================// + BaseBodyRelationContact::BaseBodyRelationContact(SPHBody* sph_body, RealBodyVector contact_sph_bodies) + : SPHBodyRelation(sph_body), contact_bodies_(contact_sph_bodies) + { + subscribeToBody(); + updateConfigurationMemories(); + } + //=================================================================================================// + BaseBodyRelationContact::BaseBodyRelationContact(SPHBody* sph_body, BodyPartVector contact_body_parts) + : SPHBodyRelation(sph_body) + { + for(size_t k = 0; k != contact_body_parts.size(); ++k) + { + contact_bodies_.push_back(dynamic_cast(contact_body_parts[k]->getBody())); + } + subscribeToBody(); + updateConfigurationMemories(); + } + //=================================================================================================// + void BaseBodyRelationContact::updateConfigurationMemories() + { + size_t updated_size = sph_body_->base_particles_->real_particles_bound_; + contact_configuration_.resize(contact_bodies_.size()); + for (size_t k = 0; k != contact_bodies_.size(); ++k) { + contact_configuration_[k].resize(updated_size, Neighborhood()); + } + } + //=================================================================================================// + void BaseBodyRelationContact::resetNeighborhoodCurrentSize() + { + for (size_t k = 0; k != contact_bodies_.size(); ++k) { + parallel_for(blocked_range(0, base_particles_->total_real_particles_), + [&](const blocked_range& r) { + for (size_t num = r.begin(); num != r.end(); ++num) { + contact_configuration_[k][num].current_size_ = 0; + } + }, ap); + } + } + //=================================================================================================// + BodyRelationContact::BodyRelationContact(SPHBody* sph_body, RealBodyVector contact_sph_bodies) + : BaseBodyRelationContact(sph_body, contact_sph_bodies) + { + initialization(); + } + //=================================================================================================// + BodyRelationContact::BodyRelationContact(SPHBody* sph_body, BodyPartVector contact_body_parts) + : BaseBodyRelationContact(sph_body, contact_body_parts) + { + initialization(); + } + //=================================================================================================// + void BodyRelationContact::initialization() + { + for (size_t k = 0; k != contact_bodies_.size(); ++k) + { + CellLinkedList* target_cell_linked_list = + dynamic_cast(contact_bodies_[k]->cell_linked_list_); + target_cell_linked_lists_.push_back(target_cell_linked_list); + get_search_depths_.push_back(new SearchDepthMultiResolution(sph_body_, target_cell_linked_list)); + get_contact_neighbors_.push_back(new NeighborRelationContact(sph_body_, contact_bodies_[k])); + } + } + //=================================================================================================// + void BodyRelationContact::updateConfiguration() + { + resetNeighborhoodCurrentSize(); + size_t total_real_particles = base_particles_->total_real_particles_; + for (size_t k = 0; k != contact_bodies_.size(); ++k) { + target_cell_linked_lists_[k]->searchNeighborsByParticles(total_real_particles, + *base_particles_, contact_configuration_[k], + get_particle_index_, *get_search_depths_[k], *get_contact_neighbors_[k]); + } + } + //=================================================================================================// + SolidBodyRelationContact::SolidBodyRelationContact(SPHBody* sph_body, RealBodyVector contact_sph_bodies) + : BaseBodyRelationContact(sph_body, contact_sph_bodies), + body_part_particles_(body_surface_layer_.body_part_particles_), + get_body_part_particle_index_(body_part_particles_), + body_surface_layer_(ShapeSurfaceLayer(sph_body)) + { + initialization(); + } + //=================================================================================================// + void SolidBodyRelationContact::resetNeighborhoodCurrentSize() + { + for (size_t k = 0; k != contact_bodies_.size(); ++k) { + parallel_for(blocked_range(0, body_part_particles_.size()), + [&](const blocked_range& r) { + for (size_t num = r.begin(); num != r.end(); ++num) { + size_t index_i = get_body_part_particle_index_(num); + contact_configuration_[k][index_i].current_size_ = 0; + } + }, ap); + } + } + //=================================================================================================// + void SolidBodyRelationContact::initialization() + { + for (size_t k = 0; k != contact_bodies_.size(); ++k) + { + CellLinkedList* target_cell_linked_list = + dynamic_cast(contact_bodies_[k]->cell_linked_list_); + target_cell_linked_lists_.push_back(target_cell_linked_list); + get_search_depths_.push_back(new SearchDepthMultiResolution(sph_body_, target_cell_linked_list)); + get_contact_neighbors_.push_back(new NeighborRelationSolidContact(sph_body_, contact_bodies_[k])); + } + } + //=================================================================================================// + void SolidBodyRelationContact::updateConfiguration() + { + resetNeighborhoodCurrentSize(); + size_t total_real_particles = body_part_particles_.size(); + for (size_t k = 0; k != contact_bodies_.size(); ++k) { + target_cell_linked_lists_[k]->searchNeighborsByParticles(total_real_particles, + *base_particles_, contact_configuration_[k], + get_body_part_particle_index_,*get_search_depths_[k], *get_contact_neighbors_[k]); + } + } + //=================================================================================================// + void GenerativeBodyRelationInner::updateConfiguration() + { + generative_structure_->buildParticleConfiguration(*base_particles_, inner_configuration_); + } + //=================================================================================================// + BodyPartRelationContact::BodyPartRelationContact(BodyPart* body_part, RealBodyVector contact_bodies) + : BodyRelationContact(body_part->getBody(), contact_bodies), body_part_(body_part), + body_part_particles_(dynamic_cast(body_part)->body_part_particles_), + get_body_part_particle_index_(dynamic_cast(body_part)->body_part_particles_) + { + } + //=================================================================================================// + void BodyPartRelationContact::updateConfiguration() + { + size_t number_of_particles = body_part_particles_.size(); + for (size_t k = 0; k != contact_bodies_.size(); ++k) + { + target_cell_linked_lists_[k]->searchNeighborsByParticles(number_of_particles, + *base_particles_, contact_configuration_[k], + get_body_part_particle_index_, *get_search_depths_[k], *get_contact_neighbors_[k]); + } + } + //=================================================================================================// + BodyRelationContactToBodyPart::BodyRelationContactToBodyPart(RealBody* real_body, BodyPartVector contact_body_parts) + : BodyRelationContact(real_body, contact_body_parts), contact_body_parts_(contact_body_parts) + { + for (size_t k = 0; k != contact_bodies_.size(); ++k) + { + get_part_contact_neighbors_.push_back(new NeighborRelationContactBodyPart(sph_body_, contact_body_parts[k])); + } + } + //=================================================================================================// + void BodyRelationContactToBodyPart::updateConfiguration() + { + size_t number_of_particles = base_particles_->total_real_particles_; + for (size_t k = 0; k != contact_body_parts_.size(); ++k) + { + target_cell_linked_lists_[k]->searchNeighborsByParticles(number_of_particles, + *base_particles_, contact_configuration_[k], + get_particle_index_, *get_search_depths_[k], *get_part_contact_neighbors_[k]); + } + } + //=================================================================================================// + ComplexBodyRelation::ComplexBodyRelation(BaseBodyRelationInner* inner_relation, BaseBodyRelationContact* contact_relation) : + SPHBodyRelation(inner_relation->sph_body_), + inner_relation_(inner_relation), contact_relation_(contact_relation), + contact_bodies_(contact_relation->contact_bodies_), + inner_configuration_(inner_relation->inner_configuration_), + contact_configuration_(contact_relation->contact_configuration_) + { + updateConfigurationMemories(); + } + //=================================================================================================// + ComplexBodyRelation::ComplexBodyRelation(RealBody* real_body, RealBodyVector contact_bodies) : + ComplexBodyRelation(new BodyRelationInner(real_body), new BodyRelationContact(real_body, contact_bodies)) {} + //=================================================================================================// + ComplexBodyRelation:: + ComplexBodyRelation(BaseBodyRelationInner* inner_relation, RealBodyVector contact_bodies) : + ComplexBodyRelation(inner_relation, new BodyRelationContact(inner_relation->sph_body_, contact_bodies)) {} + //=================================================================================================// + ComplexBodyRelation::ComplexBodyRelation(RealBody* real_body, BodyPartVector contact_body_parts) + :ComplexBodyRelation(new BodyRelationInner(real_body), new BodyRelationContactToBodyPart(real_body, contact_body_parts)) {} + //=================================================================================================// + void ComplexBodyRelation::updateConfigurationMemories() + { + inner_relation_->updateConfigurationMemories(); + contact_relation_->updateConfigurationMemories(); + } + //=================================================================================================// + void ComplexBodyRelation::updateConfiguration() + { + inner_relation_->updateConfiguration(); + contact_relation_->updateConfiguration(); + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/bodies/body_relation.h b/SPHINXsys/src/shared/bodies/body_relation.h new file mode 100644 index 0000000000..0ae0dbacef --- /dev/null +++ b/SPHINXsys/src/shared/bodies/body_relation.h @@ -0,0 +1,311 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file body_relation.h + * @brief The topological relations between bodies are described here. + * @author Xiangyu Hu + * @version 0.3.0 + * -- Add reduced body relation for network. Chi ZHANG + */ + + +#ifndef BODY_RELATION_H +#define BODY_RELATION_H + +#include "base_body.h" +#include "base_particles.h" +#include "cell_linked_list.h" +#include "neighbor_relation.h" +#include "base_geometry.h" + +namespace SPH +{ + /** a small functor for obtaining particle index for container index */ + struct SPHBodyParticlesIndex + { + size_t operator () (size_t particle_index) const { return particle_index; }; + }; + + /** a small functor for obtaining particle index for body part container index */ + struct BodyPartParticlesIndex + { + IndexVector& body_part_particles_; + BodyPartParticlesIndex(IndexVector& body_part_particles) : body_part_particles_(body_part_particles) {}; + size_t operator () (size_t particle_entry) const {return body_part_particles_[particle_entry]; }; + }; + + /** a small functor for obtaining search range for the simplest case */ + struct SearchDepthSingleResolution + { + int operator () (size_t particle_index) const { return 1; }; + }; + + /** @brief a small functor for obtaining search depth across resolution + * @details Note that the search depth is defined on the target cell linked list. + */ + struct SearchDepthMultiResolution + { + int search_depth_; + SearchDepthMultiResolution(SPHBody* body, CellLinkedList* target_cell_linked_list) : search_depth_(1) + { + Real inv_grid_spacing_ = 1.0 / target_cell_linked_list->GridSpacing(); + Kernel* kernel_ = body->particle_adaptation_->getKernel(); + search_depth_ = 1 + (int)floor(kernel_->CutOffRadius() * inv_grid_spacing_); + }; + int operator () (size_t particle_index) const { return search_depth_; }; + }; + + /** @brief a small functor for obtaining search depth for variable smoothing length + * @details Note that the search depth is defined on the target cell linked list. + */ + struct SearchDepthVariableSmoothingLength + { + Real inv_grid_spacing_; + Kernel* kernel_; + StdLargeVec& h_ratio_; + SearchDepthVariableSmoothingLength(SPHBody* body, CellLinkedList* target_cell_linked_list) : + inv_grid_spacing_(1.0 / target_cell_linked_list->GridSpacing()), + kernel_(body->particle_adaptation_->getKernel()), + h_ratio_(*body->base_particles_->getVariableByName("SmoothingLengthRatio")) {}; + int operator () (size_t particle_index) const + { + return 1 + (int)floor(kernel_->CutOffRadius(h_ratio_[particle_index]) * inv_grid_spacing_); + }; + }; + + /** + * @class SPHBodyRelation + * @brief The abstract class for all relations within a SPH body or with its contact SPH bodies + */ + class SPHBodyRelation + { + public: + SPHBody* sph_body_; + BaseParticles* base_particles_; + + SPHBodyRelation(SPHBody* sph_body); + virtual ~SPHBodyRelation() {}; + + void subscribeToBody() { sph_body_->body_relations_.push_back(this); }; + virtual void updateConfigurationMemories() = 0; + virtual void updateConfiguration() = 0; + }; + + /** + * @class BaseBodyRelationInner + * @brief The abstract relation within a SPH body + */ + class BaseBodyRelationInner : public SPHBodyRelation + { + protected: + virtual void resetNeighborhoodCurrentSize(); + public: + RealBody* real_body_; + ParticleConfiguration inner_configuration_; /**< inner configuration for the neighbor relations. */ + + BaseBodyRelationInner(RealBody* real_body); + virtual ~BaseBodyRelationInner() {}; + + virtual void updateConfigurationMemories() override; + }; + + /** + * @class BodyRelationInner + * @brief The first concrete relation within a SPH body + */ + class BodyRelationInner : public BaseBodyRelationInner + { + protected: + SPHBodyParticlesIndex get_particle_index_; + SearchDepthSingleResolution get_single_search_depth_; + NeighborRelationInner get_inner_neighbor_; + CellLinkedList* cell_linked_list_; + + public: + BodyRelationInner(RealBody* real_body); + virtual ~BodyRelationInner() {}; + + virtual void updateConfiguration() override; + }; + + /** + * @class BodyRelationInnerVariableSmoothingLength + * @brief The relation within a SPH body with smoothing length adaptation + */ + class BodyRelationInnerVariableSmoothingLength : public BaseBodyRelationInner + { + protected: + size_t total_levels_; + SPHBodyParticlesIndex get_particle_index_; + StdVec get_multi_level_search_depth_; + NeighborRelationInnerVariableSmoothingLength get_inner_neighbor_variable_smoothing_length_; + StdVec cell_linked_list_levels_; + public: + BodyRelationInnerVariableSmoothingLength(RealBody* real_body); + virtual ~BodyRelationInnerVariableSmoothingLength() {}; + + virtual void updateConfiguration() override; + }; + + /** + * @class BaseBodyRelationContact + * @brief The base relation between a SPH body and its contact SPH bodies + */ + class BaseBodyRelationContact : public SPHBodyRelation + { + protected: + StdVec target_cell_linked_lists_; + StdVec get_search_depths_; + StdVec get_contact_neighbors_; + + virtual void resetNeighborhoodCurrentSize(); + public: + RealBodyVector contact_bodies_; + ContatcParticleConfiguration contact_configuration_; /**< Configurations for particle interaction between bodies. */ + + BaseBodyRelationContact(SPHBody* body, RealBodyVector contact_bodies); + BaseBodyRelationContact(SPHBody* body, BodyPartVector contact_body_parts); + virtual ~BaseBodyRelationContact() {}; + + virtual void updateConfigurationMemories() override; + }; + + /** + * @class BodyRelationContact + * @brief The relation between a SPH body and its contact SPH bodies + */ + class BodyRelationContact : public BaseBodyRelationContact + { + protected: + SPHBodyParticlesIndex get_particle_index_; + + void initialization(); + public: + BodyRelationContact(SPHBody* body, RealBodyVector contact_bodies); + BodyRelationContact(SPHBody* body, BodyPartVector contact_body_parts); + virtual ~BodyRelationContact() {}; + virtual void updateConfiguration() override; + }; + + /** + * @class SolidBodyRelationContact + * @brief The relation between a solid body and its contact solid bodies + */ + class SolidBodyRelationContact : public BaseBodyRelationContact + { + protected: + IndexVector& body_part_particles_; + BodyPartParticlesIndex get_body_part_particle_index_; + + void initialization(); + virtual void resetNeighborhoodCurrentSize() override; + public: + ShapeSurfaceLayer body_surface_layer_; + + SolidBodyRelationContact(SPHBody* body, RealBodyVector contact_bodies); + virtual ~SolidBodyRelationContact() {}; + + virtual void updateConfiguration() override; + }; + + /** + * @class GenerativeBodyRelationInner + * @brief The relation within a reduced SPH body, viz. network + */ + class GenerativeBodyRelationInner : public BodyRelationInner + { + protected: + GenerativeStructure* generative_structure_; + public: + GenerativeBodyRelationInner(RealBody* real_body) + : BodyRelationInner(real_body), + generative_structure_(real_body->generative_structure_){}; + virtual ~GenerativeBodyRelationInner() {}; + + virtual void updateConfiguration() override; + }; + + /** + * @class BodyPartRelationContact + * @brief The relation between a Body part with a SPH body. + */ + class BodyPartRelationContact : public BodyRelationContact + { + + public: + BodyPart* body_part_; + IndexVector& body_part_particles_; + BodyPartParticlesIndex get_body_part_particle_index_; + + BodyPartRelationContact(BodyPart* body_part, RealBodyVector contact_bodies); + virtual ~BodyPartRelationContact() {}; + + virtual void updateConfiguration() override; + }; + + /** + * @class BodyRelationContactToBodyPart + * @brief The relation between a SPH body and a vector of body parts. + */ + class BodyRelationContactToBodyPart : public BodyRelationContact + { + + public: + BodyPartVector contact_body_parts_; + StdVec get_part_contact_neighbors_; + + BodyRelationContactToBodyPart(RealBody* real_body, BodyPartVector contact_body_parts); + virtual ~BodyRelationContactToBodyPart() {}; + + virtual void updateConfiguration() override; + }; + + /** + * @class ComplexBodyRelation + * @brief The relation combined an inner and a contactbody relation. + * The interaction is in a inner-boundary-condition fashion. Here inner interaction is + * different from contact interaction. + */ + class ComplexBodyRelation : public SPHBodyRelation + { + public: + BaseBodyRelationInner* inner_relation_; + BaseBodyRelationContact* contact_relation_; + RealBodyVector contact_bodies_; + ParticleConfiguration& inner_configuration_; + ContatcParticleConfiguration& contact_configuration_; + + ComplexBodyRelation(BaseBodyRelationInner* inner_relation, BaseBodyRelationContact* contact_relation); + ComplexBodyRelation(RealBody* real_body, RealBodyVector contact_bodies); + ComplexBodyRelation(BaseBodyRelationInner* inner_relation, RealBodyVector contact_bodies); + ComplexBodyRelation(RealBody* real_body, BodyPartVector contact_body_parts); + virtual ~ComplexBodyRelation() { + delete inner_relation_; + delete contact_relation_; + }; + + virtual void updateConfigurationMemories() override; + virtual void updateConfiguration() override; + }; +} +#endif //BODY_RELATION_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/bodies/fluid_body.cpp b/SPHINXsys/src/shared/bodies/fluid_body.cpp new file mode 100644 index 0000000000..ab2fd5caa7 --- /dev/null +++ b/SPHINXsys/src/shared/bodies/fluid_body.cpp @@ -0,0 +1,28 @@ +/** + * @file fluid_body.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#include "fluid_body.h" +#include "cell_linked_list.h" + +namespace SPH { + //=================================================================================================// + FluidBody::FluidBody(SPHSystem &system, std::string body_name, + ParticleAdaptation* particle_adaptation, ParticleGenerator* particle_generator) + : RealBody(system, body_name, particle_adaptation, particle_generator), + iteration_count_(0) {} + //=================================================================================================// + void FluidBody::updateCellLinkedList() + { + //sorting is carried out once for 100 iterations + if (iteration_count_ % 100 == 0) sortParticleWithCellLinkedList(); + iteration_count_++; + cell_linked_list_->UpdateCellLists(); + } + //=================================================================================================// + EulerianFluidBody::EulerianFluidBody(SPHSystem &system, std::string body_name, + ParticleAdaptation* particle_adaptation, ParticleGenerator* particle_generator) + : RealBody(system, body_name, particle_adaptation, particle_generator) {} + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/bodies/fluid_body.h b/SPHINXsys/src/shared/bodies/fluid_body.h new file mode 100644 index 0000000000..bb4a59aa57 --- /dev/null +++ b/SPHINXsys/src/shared/bodies/fluid_body.h @@ -0,0 +1,73 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file fluid_body.h + * @brief This is the class for bodies used for fluid. + * @author Chi ZHang and Xiangyu Hu + */ + +#ifndef FLUID_BODY_H +#define FLUID_BODY_H + + + +#include "base_body.h" + +namespace SPH { + class SPHSystem; + /** + * @class FluidBody + * @brief Fluid body uses smoothing length to particle spacing 1.3 + * and carry out particle sorting every 100 iterations. + */ + class FluidBody : public RealBody + { + public: + explicit FluidBody(SPHSystem &system, std::string body_name, + ParticleAdaptation* particle_adaptation = new ParticleAdaptation(), + ParticleGenerator* particle_generator = new ParticleGeneratorLattice()); + virtual ~FluidBody() {}; + + /** Update cell linked list with particle sorting. */ + virtual void updateCellLinkedList() override; + virtual FluidBody* ThisObjectPtr() override {return this;}; + protected: + size_t iteration_count_; + }; + + /** + * @class EulerianFluidBody + * @brief Eulerian Fluid body uses smoothing length to particle spacing 1.3 + */ + class EulerianFluidBody : public RealBody + { + public: + explicit EulerianFluidBody(SPHSystem &system, std::string body_name, + ParticleAdaptation* particle_adaptation = new ParticleAdaptation(), + ParticleGenerator* particle_generator = new ParticleGeneratorLattice()); + virtual ~EulerianFluidBody() {}; + + virtual EulerianFluidBody* ThisObjectPtr() override { return this; }; + }; +} +#endif //FLUID_BODY_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/bodies/solid_body.cpp b/SPHINXsys/src/shared/bodies/solid_body.cpp new file mode 100644 index 0000000000..3fd0a4818a --- /dev/null +++ b/SPHINXsys/src/shared/bodies/solid_body.cpp @@ -0,0 +1,39 @@ +/** + * @file solid_body.cpp + * @brief This is the class for bodies used for solid BCs or Elastic structure. + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#include "solid_body.h" + +#include "base_kernel.h" +#include "sph_system.h" +#include "base_material.h" +#include "solid_particles.h" + +namespace SPH { + //=================================================================================================// + SolidBody::SolidBody(SPHSystem &system, std::string body_name, + ParticleAdaptation* particle_adaptation, ParticleGenerator* particle_generator) + : RealBody(system, body_name, particle_adaptation, particle_generator) + { + sph_system_.addASolidBody(this); + } + //=================================================================================================// + ThinStructure::ThinStructure(SPHSystem& system, std::string body_name, + ParticleAdaptation* particle_adaptation, ParticleGenerator* particle_generator) + : SolidBody(system, body_name, particle_adaptation, particle_generator) + { + particle_adaptation->getKernel()->reduceOnce(); + } + //=================================================================================================// + SolidBodyPartForSimbody + ::SolidBodyPartForSimbody(SPHBody* solid_body, std::string solid_body_part_name) + : BodyPartByParticle(solid_body, solid_body_part_name) + { + solid_particles_ = dynamic_cast(body_->base_particles_); + Solid* solid = dynamic_cast(body_->base_particles_->base_material_); + solid_body_density_ = solid->ReferenceDensity(); + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/bodies/solid_body.h b/SPHINXsys/src/shared/bodies/solid_body.h new file mode 100644 index 0000000000..d6e1241d47 --- /dev/null +++ b/SPHINXsys/src/shared/bodies/solid_body.h @@ -0,0 +1,91 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file solid_body.h + * @brief This is the class for bodies used for solid BCs or Elastic structure. + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + + +#ifndef SOLID_BODY_H +#define SOLID_BODY_H + + +#include "base_body.h" + +namespace SPH { + /** + * @brief Preclaimed class. + */ + class SPHSystem; + class SolidParticles; + /** + * @class SolidBody + * @brief Declaration of solidbody which is used for Solid BCs and derived from RealBody. + */ + class SolidBody : public RealBody + { + public: + SolidBody(SPHSystem &system, std::string body_name, + ParticleAdaptation* particle_adaptation = new ParticleAdaptation(1.15), + ParticleGenerator* particle_generator = new ParticleGeneratorLattice()); + virtual ~SolidBody() {}; + virtual SolidBody* ThisObjectPtr() override {return this;}; + }; + + /** + * @class ThinStructure + * @brief Declaration of thin structure solidbody. + */ + class ThinStructure : public SolidBody + { + public: + ThinStructure(SPHSystem& system, std::string body_name, + ParticleAdaptation* particle_adaptation = new ParticleAdaptation(1.15), + ParticleGenerator* particle_generator = new ParticleGeneratorLattice()); + virtual ~ThinStructure() {}; + virtual ThinStructure* ThisObjectPtr() override {return this;}; + }; + + /** + * @class SolidBodyPartForSimbody + * @brief A SolidBodyPart for coupling with Simbody. + * The mass, origin, and unit inertial matrix are computed. + * Note: In Simbody, all spatial vectors are three dimensional. + */ + class SolidBodyPartForSimbody : public BodyPartByParticle + { + public: + Vec3d initial_mass_center_; + SimTK::MassProperties* body_part_mass_properties_; + + SolidBodyPartForSimbody(SPHBody* body, std::string solid_body_part_name); + virtual~SolidBodyPartForSimbody() {}; + protected: + Real solid_body_density_; + SolidParticles* solid_particles_; + + virtual void tagBodyPart() override; + }; +} +#endif //SOLID_BODY_H diff --git a/SPHINXsys/src/shared/common/CMakeLists.txt b/SPHINXsys/src/shared/common/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/common/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/common/array_allocation.h b/SPHINXsys/src/shared/common/array_allocation.h new file mode 100644 index 0000000000..6ace3c622d --- /dev/null +++ b/SPHINXsys/src/shared/common/array_allocation.h @@ -0,0 +1,91 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +#ifndef ARRAY_ALLOCATION_H +#define ARRAY_ALLOCATION_H + +#include "small_vectors.h" + +namespace SPH { + //------------------------------------------------------------------------------------------------- + //Allocate and deallocate 3d array + //------------------------------------------------------------------------------------------------- + template + void Allocate3dArray(T*** &matrix, Vec3u res) + { + matrix = new T**[res[0]]; + for (size_t i = 0; i < res[0]; i++) { + matrix[i] = new T*[res[1]]; + for (size_t j = 0; j < res[1]; j++) { + matrix[i][j] = new T[res[2]]; + } + } + } + + template + void Delete3dArray(T*** matrix, Vec3u res) + { + for (size_t i = 0; i < res[0]; i++) { + for (size_t j = 0; j < res[1]; j++) { + delete[] matrix[i][j]; + } + delete[] matrix[i]; + } + delete[] matrix; + } + //------------------------------------------------------------------------------------------------- + // Allocate 2d array + //------------------------------------------------------------------------------------------------- + template + void Allocate2dArray(T** &matrix, Vec2u res) + { + matrix = new T*[res[0]]; + for (size_t i = 0; i < res[0]; i++) { + matrix[i] = new T[res[1]]; + } + } + template + void Delete2dArray(T** matrix, Vec2u res) + { + for (size_t i = 0; i < res[0]; i++) { + delete[] matrix[i]; + } + delete[] matrix; + } + + //------------------------------------------------------------------------------------------------- + // Allocate 1d array + //------------------------------------------------------------------------------------------------- + template + void Allocate1dArray(T* &matrix, size_t res) + { + matrix = new T[res]; + } + template + void Delete1dArray(T* matrix, size_t res) + { + delete[] matrix; + } + +} + +#endif //ARRAY_ALLOCATION_H diff --git a/SPHINXsys/src/shared/common/base_data_package.h b/SPHINXsys/src/shared/common/base_data_package.h new file mode 100644 index 0000000000..a9b2c9dbdd --- /dev/null +++ b/SPHINXsys/src/shared/common/base_data_package.h @@ -0,0 +1,34 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +#ifndef BASE_DATA_PACKAGE_H +#define BASE_DATA_PACKAGE_H + +#include "scalar_functions.h" +#include "data_type.h" +#include "small_vectors.h" +#include "array_allocation.h" +#include "large_data_containers.h" + +#define TBB_PARALLEL true + +#endif //BASE_DATA_PACKAGE_H diff --git a/SPHINXsys/src/shared/common/base_data_type.h b/SPHINXsys/src/shared/common/base_data_type.h new file mode 100644 index 0000000000..a9672b7f7c --- /dev/null +++ b/SPHINXsys/src/shared/common/base_data_type.h @@ -0,0 +1,363 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +#ifndef BASE_DATA_TYPE_H +#define BASE_DATA_TYPE_H + +#include "Simbody.h" +#include "SimTKcommon.h" +#include "SimTKmath.h" +#include "scalar_functions.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace SPH { + + template + class SVec + { + private: + + T v[N]; + + public: + + SVec() + { + for (int i = 0; i < N; ++i) + v[i] = (T)0; + } + + explicit SVec(T value_for_all) + { + for (int i = 0; i < N; ++i) + v[i] = value_for_all; + } + + template + explicit SVec(const S *source) + { + for (int i = 0; i < N; ++i) + v[i] = (T)source[i]; + } + + template + explicit SVec(const SVec& source) + { + for (int i = 0; i < N; ++i) + v[i] = (T)source[i]; + } + + template + explicit SVec(const SimTK::Vec& source) + { + for (int i = 0; i < N; ++i) + v[i] = (T)source[i]; + } + + SVec(T v0, T v1) + { + v[0] = v0; v[1] = v1; + } + + SVec(T v0, T v1, T v2) + { + v[0] = v0; v[1] = v1; v[2] = v2; + } + + T &operator[](int index) + { + assert(index >= 0 && index < N); + return v[index]; + } + + const T &operator[](int index) const + { + assert(index >= 0 && index < N); + return v[index]; + } + + bool nonzero(void) const + { + for (int i = 0; i < N; ++i) + if (v[i]) return true; + return false; + } + + bool operator==(const SVec& b) const + { + bool res = true; + for (int i = 0; i < N; ++i) + res = res && (v[i] == b[i]); + return res; + } + + bool operator!=(const SVec& b) const + { + bool res = false; + for (int i = 0; i < N; ++i) + res = res || (v[i] != b[i]); + return res; + } + + // Arithmetic operators + SVec operator=(const SVec& b) + { + for (int i = 0; i < N; ++i) + v[i] = b.v[i]; + return *this; + } + + SVec operator+(void) const + { + return SVec(*this); + } + + SVec operator+=(T a) + { + for (int i = 0; i < N; ++i) + v[i] += a; + return *this; + } + + SVec operator+(T a) const + { + SVec w(*this); + w += a; + return w; + } + + SVec operator+=(const SVec &w) + { + for (int i = 0; i < N; ++i) + v[i] += w[i]; + return *this; + } + + SVec operator+(const SVec &w) const + { + SVec sum(*this); + sum += w; + return sum; + } + + SVec operator-=(T a) + { + for (int i = 0; i < N; ++i) + v[i] -= a; + return *this; + } + + SVec operator-(T a) const + { + SVec w(*this); + w -= a; + return w; + } + + SVec operator-=(const SVec &w) + { + for (int i = 0; i < N; ++i) + v[i] -= w[i]; + return *this; + } + + // unary minus + SVec operator-(void) const + { + SVec negative; + for (int i = 0; i < N; ++i) + negative.v[i] = -v[i]; + return negative; + } + + // minus + SVec operator-(const SVec &w) const + { + SVec diff(*this); + diff -= w; + return diff; + } + + // scalar product + SVec operator*=(T a) + { + for (int i = 0; i < N; ++i) + v[i] *= a; + return *this; + } + + SVec operator*(T a) const + { + SVec w(*this); + w *= a; + return w; + } + + SVec operator*=(const SVec &w) + { + for (int i = 0; i < N; ++i) + v[i] *= w.v[i]; + return *this; + } + + SVec operator*(const SVec &w) const + { + SVec componentwise_product; + for (int i = 0; i < N; ++i) + componentwise_product[i] = v[i] * w.v[i]; + return componentwise_product; + } + + SVec operator/=(T a) + { + for (int i = 0; i < N; ++i) + v[i] /= a; + return *this; + } + + SVec operator/(T a) const + { + SVec w(*this); + w /= a; + return w; + } + + SVec operator/=(const SVec &w) + { + for (int i = 0; i < N; ++i) + v[i] /= w.v[i]; + return *this; + } + + SVec operator/(const SVec &w) const + { + SVec componentwise_divide; + for (int i = 0; i < N; ++i) + componentwise_divide[i] = v[i] / w.v[i]; + return componentwise_divide; + } + }; + + template + std::ostream &operator<<(std::ostream &out, const SVec &v) + { + out << '[' << v[0]; + for (int i = 1; i < N; ++i) + out << ' ' << v[i]; + out << ']'; + return out; + } + + template + std::istream &operator >> (std::istream &in, SVec &v) + { + in >> v[0]; + for (int i = 1; i < N; ++i) + in >> v[i]; + return in; + } + + //vector with integers + using Vec2i = SVec<2, int>; + using Vec3i = SVec<3, int>; + + //vector with unsigned int + using Vec2u = SVec<2, size_t>; + using Vec3u = SVec<3, size_t>; + + //float point number + using Real = SimTK::Real; + + //useful float point constants s + const Real Pi = Real(M_PI); + using SimTK::Infinity; + using SimTK::Eps; + using SimTK::TinyReal; + constexpr size_t MaxSize_t = std::numeric_limits::max(); + + //vector with float point number + using Vec2d = SimTK::Vec2; + using Vec3d = SimTK::Vec3; + + //small matrix with float point number + using Mat2d = SimTK::Mat22; + using Mat3d = SimTK::Mat33; + //small symmetric matrix with float point number + using SymMat2d = SimTK::SymMat22; + using SymMat3d = SimTK::SymMat33; + + //particle data type index + const int indexScalar = 0; + const int indexVector = 1; + const int indexMatrix = 2; + const int indexInteger = 3; + + //verbal boolean for positive and negative axis directions + const int xAxis = 0; + const int yAxis = 1; + const int zAxis = 2; + const bool positiveDirection = true; + const bool negativeDirection = false; + + + /** + * @class Transform2d + * @brief Coordinate transfrom in 2D + */ + class Transform2d + { + Real rotation_angle_; + Vec2d translation_; + public: + Transform2d(SimTK::Real rotation_angle) + : rotation_angle_(rotation_angle), translation_(0) {}; + Transform2d(SimTK::Real rotation_angle, Vec2d translation) + : rotation_angle_(rotation_angle), translation_(translation) {}; + /** Forward tranformation. */ + Vec2d imposeTransform(Vec2d& origin) { + Vec2d target(origin[0] * cos(rotation_angle_) - origin[1] * sin(rotation_angle_), + origin[1] * cos(rotation_angle_) + origin[0] * sin(rotation_angle_)); + return target + translation_; + }; + /** Inverse tranformation. */ + Vec2d imposeInverseTransform(Vec2d& target) { + Vec2d origin(target[0] * cos(-rotation_angle_) - target[1] * sin(-rotation_angle_), + target[1] * cos(-rotation_angle_) + target[0] * sin(-rotation_angle_)); + return origin - translation_; + }; + }; + + /** + * @class Transform3d + * @brief Coordinate transfrom in 3D from SimTK + */ + using Transform3d = SimTK::Transform; +} + +#endif //BASE_DATA_TYPE_H diff --git a/SPHINXsys/src/shared/common/large_data_containers.h b/SPHINXsys/src/shared/common/large_data_containers.h new file mode 100644 index 0000000000..4a2a3fe9e2 --- /dev/null +++ b/SPHINXsys/src/shared/common/large_data_containers.h @@ -0,0 +1,58 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +#ifndef LARGE_DATA_CONTAINER_H +#define LARGE_DATA_CONTAINER_H + +#include "tbb/tbb.h" +#include "tbb/blocked_range.h" +#include "tbb/blocked_range2d.h" +#include "tbb/blocked_range3d.h" +#include "tbb/parallel_for.h" +#include "tbb/parallel_reduce.h" +#include "tbb/tick_count.h" +#include "tbb/scalable_allocator.h" +#include "tbb/concurrent_unordered_set.h" +#include "tbb/concurrent_vector.h" +#include "tbb/cache_aligned_allocator.h" + +#include + +using namespace tbb; +static tbb::affinity_partitioner ap; + +namespace SPH { + + template + using LargeVec = tbb::concurrent_vector; + + template + using StdLargeVec = std::vector>; + + template + using StdVec = std::vector; + + template + using DataVec = std::vector>; +} + +#endif //LARGE_DATA_CONTAINER_H diff --git a/SPHINXsys/src/shared/common/scalar_functions.cpp b/SPHINXsys/src/shared/common/scalar_functions.cpp new file mode 100644 index 0000000000..768b3893f2 --- /dev/null +++ b/SPHINXsys/src/shared/common/scalar_functions.cpp @@ -0,0 +1,15 @@ +/** + * @file scalar_functions.cpp + * @author Xiangyu Hu + * @version 0.1 + */ + +#include "scalar_functions.h" +//=================================================================================================// +namespace SPH { + //=================================================================================================// + int ThirdAxis(int axis_direction) { + return SecondAxis(SecondAxis(axis_direction)); + } + +} diff --git a/SPHINXsys/src/shared/common/scalar_functions.h b/SPHINXsys/src/shared/common/scalar_functions.h new file mode 100644 index 0000000000..c23003504b --- /dev/null +++ b/SPHINXsys/src/shared/common/scalar_functions.h @@ -0,0 +1,184 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +#ifndef SCALAR_FUNCTIONS_H +#define SCALAR_FUNCTIONS_H + +#include +#include +#include +#include + +namespace SPH { + + template + inline T sqr(const T& x) + { + return x * x; + } + + template + inline T cube(const T& x) + { + return x * x * x; + } + + //Get the nth power + template + T powerN(const T& a, int n) + { + T res = 1; + for (int i = 0; i < n; i++) + res *= a; + return res; + } + + //Get the minimum + template + inline T SMIN(T a1, T a2) + { + return (a1 <= a2 ? a1 : a2); + } + + template + inline T SMIN(T a1, T a2, T a3) + { + return SMIN(a1, SMIN(a2, a3)); + } + + template + inline T SMIN(T a1, T a2, T a3, T a4) + { + return SMIN(SMIN(a1, a2), SMIN(a3, a4)); + } + + template + inline T SMIN(T a1, T a2, T a3, T a4, T a5) + { + return SMIN(SMIN(a1, a2), SMIN(a3, a4), a5); + } + + template + inline T SMIN(T a1, T a2, T a3, T a4, T a5, T a6) + { + return SMIN(SMIN(a1, a2), SMIN(a3, a4), SMIN(a5, a6)); + } + + //Get the maximum + template + inline T SMAX(T a1, T a2) + { + return (a1 >= a2 ? a1 : a2); + } + + template + inline T SMAX(T a1, T a2, T a3) + { + return SMAX(a1, SMAX(a2, a3)); + } + + template + inline T SMAX(T a1, T a2, T a3, T a4) + { + return SMAX(SMAX(a1, a2), SMAX(a3, a4)); + } + + template + inline T SMAX(T a1, T a2, T a3, T a4, T a5) + { + return SMAX(SMAX(a1, a2), SMAX(a3, a4), a5); + } + + template + inline T SMAX(T a1, T a2, T a3, T a4, T a5, T a6) + { + return SMAX(SMAX(a1, a2), SMAX(a3, a4), SMAX(a5, a6)); + } + + template + inline void update_minmax(T a1, T& amin, T& amax) + { + amin = SMIN(a1, amin); + amax = SMAX(a1, amax); + } + + template + inline void update_minmax(T a1, T a2, T& amin, T& amax) + { + if (a1 > a2) { + amin = a2; amax = a1; + } + else { + amin = a1; amax = a2; + } + } + + //Get the absolute + template + inline T ABS(const T& x) + { + return SMAX(x, -x); + } + + template + inline T SGN(const T& x) + { + return (x < 0) ? -1 : ((x > 0) ? 1 : 0); + } + /** Heaviside step function */ + template + inline T HSF(const T& x) + { + return 0.5 * (1.0 + SGN(x)); + } + + template + inline T clamp(T a, T lower, T upper) + { + if (a < lower) return lower; + else if (a > upper) return upper; + else return a; + } + + template + inline bool Not_a_number(T a) + { + return (std::isnan(a) || !(std::isfinite(a))) ? true : false; + } + + inline double rand_norm(double u, double std) + { + unsigned seed = (unsigned)std::chrono::system_clock::now().time_since_epoch().count(); + std::default_random_engine generator (seed); + std::normal_distribution distribution(u, std); + return distribution(generator); + } + /** rotating axis once according to right hand rule. + * The axis_direction must be 0, 1 for 2d and 0, 1, 2 for 3d + */ + int SecondAxis(int axis_direction); + /** rotating axis twice according to right hand rule. + * The axis_direction must be 0, 1 for 2d and 0, 1, 2 for 3d + */ + int ThirdAxis(int axis_direction); +} +#endif //SCALAR_FUNCTIONS_H diff --git a/SPHINXsys/src/shared/common/small_vectors.cpp b/SPHINXsys/src/shared/common/small_vectors.cpp new file mode 100644 index 0000000000..cabcf1cf0d --- /dev/null +++ b/SPHINXsys/src/shared/common/small_vectors.cpp @@ -0,0 +1,217 @@ +/** + * @file small_vectors.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + * @version 0.1 + */ + +#include "small_vectors.h" +//=================================================================================================// +namespace SPH { + + //=================================================================================================// + Vec2d FirstAxisVector(const Vec2d& zero_vector) + { + return Vec2d(1.0, 0.0); + } + //=================================================================================================// + Vec3d FirstAxisVector(const Vec3d& zero_vector) + { + return Vec3d(1.0, 0.0, 0.0); + }; + //=================================================================================================// + Real getMaxAbsoluteElement(const Vec2d& input) + { + Real max = 0.0; + for (int n = 0; n != input.size(); n++) max = SMAX(fabs(input[n]), max); + return max; + } + //=================================================================================================// + Real getMaxAbsoluteElement(const Vec3d& input) + { + Real max = 0.0; + for (int n = 0; n != input.size(); n++) max = SMAX(fabs(input[n]), max); + return max; + } + //=================================================================================================// + Vec3d upgradeToVector3D(const Real& input) { + return Vec3d(input, 0.0, 0.0); + } + //=================================================================================================// + Vec3d upgradeToVector3D(const Vec2d& input) { + return Vec3d(input[0], input[1], 0.0); + } + //=================================================================================================// + Vec3d upgradeToVector3D(const Vec3d& input) { + return input; + } + //=================================================================================================// + Mat3d upgradeToMatrix3D(const Mat2d& input) { + Mat3d output(0); + output.col(0) = upgradeToVector3D(input.col(0)); + output.col(1) = upgradeToVector3D(input.col(1)); + return output; + } + //=================================================================================================// + Mat3d upgradeToMatrix3D(const Mat3d& input) { + return input; + } + //=================================================================================================// + Mat2d getInverse(const Mat2d& A) + { + Mat2d minv(0); + SimTK::Real det = A(0, 0) * A(1, 1) - A(0, 1) * A(1, 0); + SimTK::Real invdet = 1.0 / det; + minv(0, 0) = A(1, 1) * invdet; + minv(0, 1) = -A(0, 1) * invdet; + minv(1, 0) = -A(1, 0) * invdet; + minv(1, 1) = A(0, 0) * invdet; + return minv; + } + //=================================================================================================// + Mat3d getInverse(const Mat3d& A) + { + SimTK::Real det = A(0, 0) * (A(1, 1) * A(2, 2) - A(2, 1) * A(1, 2)) - + A(0, 1) * (A(1, 0) * A(2, 2) - A(1, 2) * A(2, 0)) + + A(0, 2) * (A(1, 0) * A(2, 1) - A(1, 1) * A(2, 0)); + + SimTK::Real invdet = 1 / det; + Mat3d minv(0); // inverse of matrix m + minv(0, 0) = (A(1, 1) * A(2, 2) - A(2, 1) * A(1, 2)) * invdet; + minv(0, 1) = (A(0, 2) * A(2, 1) - A(0, 1) * A(2, 2)) * invdet; + minv(0, 2) = (A(0, 1) * A(1, 2) - A(0, 2) * A(1, 1)) * invdet; + minv(1, 0) = (A(1, 2) * A(2, 0) - A(1, 0) * A(2, 2)) * invdet; + minv(1, 1) = (A(0, 0) * A(2, 2) - A(0, 2) * A(2, 0)) * invdet; + minv(1, 2) = (A(1, 0) * A(0, 2) - A(0, 0) * A(1, 2)) * invdet; + minv(2, 0) = (A(1, 0) * A(2, 1) - A(2, 0) * A(1, 1)) * invdet; + minv(2, 1) = (A(2, 0) * A(0, 1) - A(0, 0) * A(2, 1)) * invdet; + minv(2, 2) = (A(0, 0) * A(1, 1) - A(1, 0) * A(0, 1)) * invdet; + + return minv; + } + //=================================================================================================// + Mat2d getAverageValue(const Mat2d& A, const Mat2d& B) + { + Mat2d C(1.0); + for (int i = 0; i < 2; i++) + { + for (int j = 0; j < 2; j++) + { + C(i, j) = 2.0 * A(i, j) * B(i, j) / (A(i, j) + B(i, j) + TinyReal); + } + } + return C; + } + //=================================================================================================// + Mat3d getAverageValue(const Mat3d& A, const Mat3d& B) + { + Mat3d C(1.0); + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + C(i, j) = 2.0 * A(i, j) * B(i, j) / (A(i, j) + B(i, j) + TinyReal); + } + } + return C; + } + //=================================================================================================// + Mat2d inverseCholeskyDecomposition(const Mat2d& A) + { + Mat2d lower(0); + int n = 2; + /** Decomposing a matrix into Lower Triangular. */ + for (int i = 0; i < n; i++) + { + for (int j = 0; j < (i + 1); j++) + { + double sum = 0; + for (int k = 0; k < j; k++) + { + sum += lower(i, k) * lower(j, k); + } + if (i == j) + { + lower(i, j) = sqrt(A(i, i) - sum); + } + else + { + lower(i, j) = (1.0 / lower(j, j) * (A(i, j) - sum)); + } + } + } + Mat2d inverse_lower = getInverse(lower); + return inverse_lower; + } + //=================================================================================================// + Mat3d inverseCholeskyDecomposition(const Mat3d& A) + { + Mat3d lower(0); + int n = 3; + /** Decomposing a matrix into Lower Triangular. */ + for (int i = 0; i < n; i++) + { + for (int j = 0; j < (i + 1); j++) + { + double sum = 0; + for (int k = 0; k < j; k++) + { + sum += lower(i, k) * lower(j, k); + } + if (i == j) + { + lower(i, j) = sqrt(A(i, i) - sum); + } + else + { + lower(i, j) = (1.0 / lower(j, j) * (A(i, j) - sum)); + } + } + } + Mat3d inverse_lower = getInverse(lower); + return inverse_lower; + } + //=================================================================================================// + Mat2d getTransformationMatrix(const Vec2d& direction_of_y) + { + Mat2d transformation_matrix(0.0); + transformation_matrix[0][0] = direction_of_y[1]; + transformation_matrix[0][1] = -direction_of_y[0]; + transformation_matrix[1][0] = direction_of_y[0]; + transformation_matrix[1][1] = direction_of_y[1]; + return transformation_matrix; + } + //=================================================================================================// + Mat3d getTransformationMatrix(const Vec3d& direction_of_z) + { + Mat3d transformation_matrix(0.0); + transformation_matrix[0][0] = direction_of_z[2] + powerN(direction_of_z[1], 2) / (1 + direction_of_z[2] + Eps); + transformation_matrix[0][1] = -direction_of_z[0] * direction_of_z[1] / (1 + direction_of_z[2] + Eps); + transformation_matrix[0][2] = -direction_of_z[0]; + transformation_matrix[1][0] = transformation_matrix[0][1]; + transformation_matrix[1][1] = direction_of_z[2] + powerN(direction_of_z[0], 2) / (1 + direction_of_z[2] + Eps); + transformation_matrix[1][2] = -direction_of_z[1]; + transformation_matrix[2][0] = direction_of_z[0]; + transformation_matrix[2][1] = direction_of_z[1]; + transformation_matrix[2][2] = direction_of_z[2]; + return transformation_matrix; + } + //=================================================================================================// + Mat2d getDiagonal(const Mat2d& A) + { + Mat2d diag(1.0); + diag[0][0] = A[0][0]; + diag[1][1] = A[1][1]; + + return diag; + } + Mat3d getDiagonal(const Mat3d& A) + { + Mat3d diag(1.0); + diag[0][0] = A[0][0]; + diag[1][1] = A[1][1]; + diag[2][2] = A[2][2]; + + return diag; + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/common/small_vectors.h b/SPHINXsys/src/shared/common/small_vectors.h new file mode 100644 index 0000000000..83593b4c20 --- /dev/null +++ b/SPHINXsys/src/shared/common/small_vectors.h @@ -0,0 +1,79 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +#ifndef SMALL_VECTORS_H +#define SMALL_VECTORS_H + +#include "base_data_type.h" + +namespace SPH { + + Vec2d FirstAxisVector(const Vec2d& zero_vector); + Vec3d FirstAxisVector(const Vec3d& zero_vector); + Real getMaxAbsoluteElement(const Vec2d& input); + Real getMaxAbsoluteElement(const Vec3d& input); + Vec3d upgradeToVector3D(const Real& input); + Vec3d upgradeToVector3D(const Vec2d& input); + Vec3d upgradeToVector3D(const Vec3d& input); + Mat3d upgradeToMatrix3D(const Mat2d& input); + Mat3d upgradeToMatrix3D(const Mat3d& input); + + template + OutVectorType upgradeVector(const Real& input) + { + OutVectorType out_vector(0); + out_vector[0] = input; + return out_vector; + }; + template + OutVectorType upgradeVector(const Vec2d& input) + { + OutVectorType out_vector(0); + out_vector[0] = input[0]; + out_vector[1] = input[1]; + return out_vector; + }; + template + OutVectorType upgradeVector(const Vec3d& input) + { + OutVectorType out_vector(0); + out_vector[0] = input[0]; + out_vector[1] = input[1]; + out_vector[2] = input[2]; + return out_vector; + }; + + Mat2d getInverse(const Mat2d& A); + Mat3d getInverse(const Mat3d& A); + Mat2d getAverageValue(const Mat2d &A, const Mat2d& B); + Mat3d getAverageValue(const Mat3d &A, const Mat3d& B); + Mat2d inverseCholeskyDecomposition(const Mat2d& A); + Mat3d inverseCholeskyDecomposition(const Mat3d& A); + Mat2d getDiagonal(const Mat2d& A); + Mat3d getDiagonal(const Mat3d& A); + + /** get transformation matrix. */ + Mat2d getTransformationMatrix(const Vec2d& direction_of_y); + Mat3d getTransformationMatrix(const Vec3d& direction_of_z); +} + +#endif //SMALL_VECTORS_H diff --git a/SPHINXsys/src/shared/common/sph_data_conainers.h b/SPHINXsys/src/shared/common/sph_data_conainers.h new file mode 100644 index 0000000000..f32a75835c --- /dev/null +++ b/SPHINXsys/src/shared/common/sph_data_conainers.h @@ -0,0 +1,115 @@ +/** + * @file sph_data_conainers.h + * @brief Set up of basic data structure. + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#ifndef SPH_DATA_CONTAINERS_H +#define SPH_DATA_CONTAINERS_H + +#include "base_data_package.h" + +namespace SPH { + /** + * @brief Preclaimed classes. + */ + class BaseMaterial; + class SPHBody; + class RealBody; + class SolidBody; + class BodyPart; + class FictitiousBody; + class CellList; + class BaseParticles; + + /** Bounding box for system, body, body part and shape, first: lower bound, second: upper bound. */ + typedef std::pair BoundingBox; + /** Generalized particle data type */ + typedef std::tuple*>, StdVec*>, StdVec*>, + StdVec*>> ParticleData; + /** Generalized particle variable to index map */ + typedef std::array, 4> ParticleDataMap; + /** Generalized particle variable list */ + typedef std::array>, 4> ParticleVariableList; + /** Vector of Material. Note that vector of references are not allowed in c++.*/ + using MaterialVector = StdVec; + /** Vector of bodies */ + using SPHBodyVector = StdVec; + using SolidBodyVector = StdVec; + using RealBodyVector = StdVec; + using BodyPartVector = StdVec; + using FictitiousBodyVector = StdVec; + + /** Index container with elements of size_t. */ + using IndexVector = StdVec; + /** Concurrent particle indexes .*/ + using ConcurrentIndexVector = LargeVec; + + /** List data pair */ + using ListData = std::pair; + /** Vector of list data pair */ + using ListDataVector = StdLargeVec; + /** Cell lists*/ + using CellLists = StdLargeVec; + + /** Concurrent vector .*/ + template + using ConcurrentVector = LargeVec; + /** concurrent cell lists*/ + using ConcurrentCellLists = LargeVec; + /** Split cell list for split algorithms. */ + using SplitCellLists = StdVec; + /** Pair of point and volume. */ + using PositionsAndVolumes = StdVec>; + + /** loop particle data with operations */ + template typename OperationType, + typename... ParticleArgs> + void loopParticleData(ParticleData& particle_data, ParticleArgs... particle_args) + { + OperationType scalar_operation; + OperationType vector_operation; + OperationType matrix_operation; + OperationType integer_operation; + + scalar_operation(particle_data, particle_args...); + vector_operation(particle_data, particle_args...); + matrix_operation(particle_data, particle_args...); + integer_operation(particle_data, particle_args...); + }; + + /** operation by looping or going through a particle data map */ + template + struct loopParticleDataMap + { + template + void operator () (ParticleData& particle_data, + ParticleDataMap& particle_data_map, VariableOperation& variable_operation) const + { + for (auto const& name_index : particle_data_map[DataTypeIndex]) + { + std::string variable_name = name_index.first; + StdLargeVec& variable = *(std::get(particle_data)[name_index.second]); + variable_operation(variable_name, variable); + } + }; + }; + + /** operation by looping or going through a variable name list */ + template + struct loopVariabaleNameList + { + template + void operator () (ParticleData& particle_data, + ParticleVariableList& variable_name_list, VariableOperation& variable_operation) const + { + for (std::pair& name_index : variable_name_list[DataTypeIndex]) + { + std::string variable_name = name_index.first; + StdLargeVec& variable = *(std::get(particle_data)[name_index.second]); + variable_operation(variable_name, variable); + } + }; + }; +} +#endif //SPH_DATA_CONTAINERS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/generative_structures/generative_structures.cpp b/SPHINXsys/src/shared/generative_structures/generative_structures.cpp new file mode 100644 index 0000000000..e9a219c633 --- /dev/null +++ b/SPHINXsys/src/shared/generative_structures/generative_structures.cpp @@ -0,0 +1,267 @@ +/** + * @file generative_structures.cpp + * @author Xiangyu Hu + */ + +#include "generative_structures.h" + +#include "base_body.h" +#include "base_particles.h" +#include "particle_adaptation.h" + +namespace SPH +{ + //=================================================================================================// + GenerativeStructure::GenerativeStructure(SPHBody *sph_body) + : sph_body_(sph_body), + spacing_ref_(sph_body_->particle_adaptation_->ReferenceSpacing()), + base_particles_(sph_body->base_particles_), + neighbor_relation_inner_(sph_body), + pos_n_(base_particles_->pos_n_), + Vol_(base_particles_->Vol_){}; + //=================================================================================================// + GenerativeTree::GenerativeTree(SPHBody *sph_body) + : GenerativeStructure(sph_body), last_branch_id_(0) + { + root_ = new Branch(this); + } + //=================================================================================================// + GenerativeTree::~GenerativeTree() + { + for (size_t i = 0; i != branches_.size(); i++) + if(branches_[i] != nullptr) delete branches_[i]; + } + //=================================================================================================// + void GenerativeTree::buildParticleConfiguration(BaseParticles &base_particles, + ParticleConfiguration &particle_configuration) + { + size_t particle_id; + size_t parent_branch_id; + size_t child_branch_id; + size_t num_ele; + std::vector neighboring_ids; + std::vector child_ids; + /** First branch + * Note that the first branch has only one particle. + * Find the neighbors in child branch, the first branch only have one child, id = 1. + */ + particle_id = branches_[0]->inner_particles_.front(); + neighboring_ids.clear(); + neighboring_ids.push_back(branches_[1]->inner_particles_[0]); + neighboring_ids.push_back(branches_[1]->inner_particles_[1]); + /** Build configuration. */ + Neighborhood &neighborhood = particle_configuration[particle_id]; + for (size_t n = 0; n != neighboring_ids.size(); ++n) + { + Vecd displacement = base_particles.pos_n_[particle_id] - base_particles.pos_n_[neighboring_ids[n]]; + neighbor_relation_inner_(neighborhood, displacement, particle_id, neighboring_ids[n]); + } + /** Second branch. + * The second branch has special parent branch, branch 0, consisting only one point. + * The child branch are two normal branch. + */ + num_ele = branches_[1]->inner_particles_.size(); + child_ids.clear(); + for (size_t k = 0; k < branches_[1]->out_edge_.size(); ++k) + { + child_ids.push_back(branches_[1]->out_edge_[k]); + } + + for (size_t i = 0; i != num_ele; i++) + { + neighboring_ids.clear(); + particle_id = branches_[1]->inner_particles_.front() + i; + if (i == 0) + { + neighboring_ids.push_back(branches_[0]->inner_particles_.front()); + neighboring_ids.push_back(particle_id + 1); + neighboring_ids.push_back(particle_id + 2); + } + else if (i == 1) + { + neighboring_ids.push_back(branches_[0]->inner_particles_.front()); + neighboring_ids.push_back(particle_id - 1); + neighboring_ids.push_back(particle_id + 1); + neighboring_ids.push_back(particle_id + 2); + } + else if (2 <= i && i <= (num_ele - 3)) + { + neighboring_ids.push_back(particle_id - 1); + neighboring_ids.push_back(particle_id - 2); + neighboring_ids.push_back(particle_id + 1); + neighboring_ids.push_back(particle_id + 2); + } + else if (i == (num_ele - 2)) + { + neighboring_ids.push_back(particle_id - 2); + neighboring_ids.push_back(particle_id - 1); + neighboring_ids.push_back(particle_id + 1); + + for (size_t k = 0; k < branches_[1]->out_edge_.size(); ++k) + { + child_branch_id = branches_[1]->out_edge_[k]; + neighboring_ids.push_back(branches_[child_branch_id]->inner_particles_.front()); + } + } + else if (i == (num_ele - 1)) + { + neighboring_ids.push_back(particle_id - 1); + neighboring_ids.push_back(particle_id - 2); + + for (size_t k = 0; k < branches_[1]->out_edge_.size(); ++k) + { + child_branch_id = branches_[1]->out_edge_[k]; + neighboring_ids.push_back(branches_[child_branch_id]->inner_particles_.front()); + neighboring_ids.push_back(branches_[child_branch_id]->inner_particles_.front() + 1); + } + } + + Neighborhood &neighborhood = particle_configuration[particle_id]; + for (size_t n = 0; n != neighboring_ids.size(); ++n) + { + Vecd displacement = base_particles.pos_n_[particle_id] - base_particles.pos_n_[neighboring_ids[n]]; + neighbor_relation_inner_(neighborhood, displacement, particle_id, neighboring_ids[n]); + } + } + /** Other branches. + * They are may normal branch (fully growed, has child and parent) or non-fully growed branch + */ + for (size_t branch_idx = 2; branch_idx != branches_.size(); ++branch_idx) + { + num_ele = branches_[branch_idx]->inner_particles_.size(); + parent_branch_id = branches_[branch_idx]->in_edge_; + if (!branches_[branch_idx]->is_terminated_) + { + /** This branch is fully growed. */ + for (size_t i = 0; i != num_ele; i++) + { + neighboring_ids.clear(); + particle_id = branches_[branch_idx]->inner_particles_.front() + i; + if (i == 0) + { + neighboring_ids.push_back(branches_[parent_branch_id]->inner_particles_.back()); + neighboring_ids.push_back(branches_[parent_branch_id]->inner_particles_.back() - 1); + + neighboring_ids.push_back(particle_id + 1); + neighboring_ids.push_back(particle_id + 2); + } + else if (i == 1) + { + neighboring_ids.push_back(branches_[parent_branch_id]->inner_particles_.back()); + neighboring_ids.push_back(particle_id - 1); + neighboring_ids.push_back(particle_id + 1); + neighboring_ids.push_back(particle_id + 2); + } + else if (2 <= i && i <= (num_ele - 3)) + { + neighboring_ids.push_back(particle_id - 1); + neighboring_ids.push_back(particle_id - 2); + neighboring_ids.push_back(particle_id + 1); + neighboring_ids.push_back(particle_id + 2); + } + else if (i == (num_ele - 2)) + { + neighboring_ids.push_back(particle_id - 2); + neighboring_ids.push_back(particle_id - 1); + neighboring_ids.push_back(particle_id + 1); + + for (size_t k = 0; k < branches_[branch_idx]->out_edge_.size(); ++k) + { + child_branch_id = branches_[branch_idx]->out_edge_[k]; + neighboring_ids.push_back(branches_[child_branch_id]->inner_particles_.front()); + } + } + else if (i == (num_ele - 1)) + { + neighboring_ids.push_back(particle_id - 1); + neighboring_ids.push_back(particle_id - 2); + + for (size_t k = 0; k < branches_[branch_idx]->out_edge_.size(); ++k) + { + child_branch_id = branches_[branch_idx]->out_edge_[k]; + neighboring_ids.push_back(branches_[child_branch_id]->inner_particles_.front()); + if (branches_[child_branch_id]->inner_particles_.size() >= 2) + { + neighboring_ids.push_back(branches_[child_branch_id]->inner_particles_.front() + 1); + } + } + } + + Neighborhood &neighborhood = particle_configuration[particle_id]; + for (size_t n = 0; n != neighboring_ids.size(); ++n) + { + Vecd displacement = base_particles.pos_n_[particle_id] - base_particles.pos_n_[neighboring_ids[n]]; + neighbor_relation_inner_(neighborhood, displacement, particle_id, neighboring_ids[n]); + } + } + } + else + { + /** This branch is not fully growed. */ + for (size_t i = 0; i != num_ele; i++) + { + neighboring_ids.clear(); + particle_id = branches_[branch_idx]->inner_particles_.front() + i; + if (i == 0) + { + neighboring_ids.push_back(branches_[parent_branch_id]->inner_particles_.back()); + if (branches_[parent_branch_id]->inner_particles_.size() >= 2) + neighboring_ids.push_back(branches_[parent_branch_id]->inner_particles_.back() - 1); + } + else if (i == 1) + { + neighboring_ids.push_back(branches_[parent_branch_id]->inner_particles_.back()); + neighboring_ids.push_back(particle_id - 1); + } + else + { + neighboring_ids.push_back(particle_id - 1); + neighboring_ids.push_back(particle_id - 2); + } + + if (i + 1 < num_ele) + neighboring_ids.push_back(particle_id + 1); + if (i + 2 < num_ele) + neighboring_ids.push_back(particle_id + 2); + + Neighborhood &neighborhood = particle_configuration[particle_id]; + for (size_t n = 0; n != neighboring_ids.size(); ++n) + { + Vecd displacement = base_particles.pos_n_[particle_id] - base_particles.pos_n_[neighboring_ids[n]]; + neighbor_relation_inner_(neighborhood, displacement, particle_id, neighboring_ids[n]); + } + } + } + } + } + //=================================================================================================// + void GenerativeTree:: + growAParticleOnBranch(Branch *branch, const Vecd &new_point, const Vecd &end_direction) + { + base_particles_->initializeABaseParticle(new_point, spacing_ref_); + branch_locations_.push_back(branch->id_); + branch->inner_particles_.push_back(pos_n_.size() - 1); + branch->end_direction_ = end_direction; + } + //=================================================================================================// + size_t GenerativeTree::BranchLocation(size_t particle_idx) + { + return particle_idx < pos_n_.size() ? branch_locations_[particle_idx] : MaxSize_t; + } + //=================================================================================================// + GenerativeTree::Branch::Branch(GenerativeTree *tree) + : Edge(tree), is_terminated_(false) + { + tree->branches_.push_back(this); + tree->last_branch_id_ = id_; + } + //=================================================================================================// + GenerativeTree::Branch::Branch(size_t parent_id, GenerativeTree *tree) + : Edge(parent_id, tree), is_terminated_(false) + { + tree->branches_[parent_id]->out_edge_.push_back(id_); + tree->branches_.push_back(this); + tree->last_branch_id_ = id_; + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/generative_structures/generative_structures.h b/SPHINXsys/src/shared/generative_structures/generative_structures.h new file mode 100644 index 0000000000..8fde4e3b5c --- /dev/null +++ b/SPHINXsys/src/shared/generative_structures/generative_structures.h @@ -0,0 +1,119 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file generative_structures.h +* @brief In generative structures, the particles and, if necessary, +* and their neighnor relations or particle configuration +* can be generated directly form the information of the structure. +* @author Xiangyu Hu +*/ + +#ifndef GENERATIVE_STRUCTURES_H +#define GENERATIVE_STRUCTURES_H + +#include "base_geometry.h" +#include "neighbor_relation.h" + +namespace SPH +{ + + class SPHBody; + class BaseParticles; + + /** + * @class GenerativeStructure + * @brief Abstract class as interface for all generative structures. + * It is linked with particles by particle position and volume + */ + class GenerativeStructure + { + public: + explicit GenerativeStructure(SPHBody *sph_body); + virtual ~GenerativeStructure(){}; + + virtual void buildParticleConfiguration(BaseParticles &base_particles, + ParticleConfiguration &particle_configuration) = 0; + + protected: + SPHBody *sph_body_; + Real spacing_ref_; + BaseParticles *base_particles_; + NeighborRelationInner neighbor_relation_inner_; + + public: + StdLargeVec &pos_n_; /**< current position */ + StdLargeVec &Vol_; /**< particle volume */ + }; + + /** + * @class GenerativeTree + * @brief The tree is composed of a root (the first branch) + * and other branch generated sequentially. + */ + class GenerativeTree : public GenerativeStructure + { + public: + class Branch; + StdVec branches_; /**< Contanier of all branches */ + IndexVector branch_locations_; /**< in which branch are the particles located */ + size_t last_branch_id_; + Branch *root_; + + explicit GenerativeTree(SPHBody *sph_body); + virtual ~GenerativeTree(); + + void growAParticleOnBranch(Branch *branch, const Vecd &new_point, const Vecd &end_direction); + size_t BranchLocation(size_t particle_idx); + Branch *LastBranch() { return branches_[last_branch_id_]; }; + + virtual void buildParticleConfiguration(BaseParticles &base_particles, + ParticleConfiguration &particle_configuration) override; + size_t ContainerSize() { return branches_.size(); }; + }; + + /** + * @class GenerativeTree::Branch + * @brief Each branch (excapt the root) has a parent and several children, and geometric information. + * It is a realized edge and has multi inner particles. + * The first is the last particle from the parent or root, + * and the last is the first particle of all its child branches. + * Many connected branches compose a tree. */ + class GenerativeTree::Branch : public Edge + { + public: + /** construct the root branch */ + Branch(GenerativeTree *tree); + /** construct an branch connecting with its parent */ + Branch(size_t parent_id, GenerativeTree *tree); + virtual ~Branch(){}; + + Vecd end_direction_; /**< the direction pointing to the last particle */ + /** The indexes of particle within this branch. + * The first is the last particle from the parent or root, + * and the last is the first of all its child branches. */ + IndexVector inner_particles_; + bool is_terminated_; /**< whether is an terminate branch or not */ + }; + +} +#endif //GENERATIVE_STRUCTURES_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/geometries/CMakeLists.txt b/SPHINXsys/src/shared/geometries/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/geometries/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/geometries/all_geometries.h b/SPHINXsys/src/shared/geometries/all_geometries.h new file mode 100644 index 0000000000..8c2adbb87f --- /dev/null +++ b/SPHINXsys/src/shared/geometries/all_geometries.h @@ -0,0 +1,12 @@ + +#ifndef ALL_GEOMETRIES_H +#define ALL_GEOMETRIES_H + +/** @file +This is the header file that user code should include to pick up all +geometry classes used in SPHinXsys. **/ + +#include "geometry.h" +#include "geometry_level_set.h" + +#endif //ALL_GEOMETRIES_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/geometries/base_geometry.h b/SPHINXsys/src/shared/geometries/base_geometry.h new file mode 100644 index 0000000000..a2469aae18 --- /dev/null +++ b/SPHINXsys/src/shared/geometries/base_geometry.h @@ -0,0 +1,96 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file base_geometry.h +* @brief Shape is the base class for all geometries. +* @details Several pure virtual functions +* are defined here. (a) closet point on surface: to find the closet point on shape +* surface to a given point. (b) find the lower and upper bounds. +* @author Chi ZHang and Xiangyu Hu +*/ + + +#ifndef BASE_GEOMETRY_H +#define BASE_GEOMETRY_H + + +#include "base_particles.h" +#include "base_data_package.h" +#include "sph_data_conainers.h" + +#include + +namespace SPH +{ + class Tree; + class Neighborhood; + /** + * @class ShapeBooleanOps + * @brief Boolian operation for generate complex shapes + * @details Note that, for 3D applications, only add and sub boolean operation have been defined right now + */ + enum class ShapeBooleanOps { add, sub, sym_diff, intersect }; + + /** + * @class Shape + * @brief Base class for all geometries + */ + class Shape + { + public: + Shape(std::string shape_name) : name_(shape_name) {}; + virtual ~Shape() {}; + + std::string getName() { return name_; }; + virtual BoundingBox findBounds() = 0; + protected: + std::string name_; + }; + + /** + * @class Edge + * @brief template base class of linear structure only with topology information. + * Note that a edge is defined together with a structure which is composed of edges. + * Such structure should have an interface function ContainerSize() returning + * the curent total amount of edges. + */ + template + class Edge + { + public: + /** constructor without specifying a leading-in edge */ + template + Edge(EdgeStructureType *structure) + : id_(structure->ContainerSize()), in_edge_(MaxSize_t) {}; + /** constructor with specifying a leading-in edge */ + template + Edge(InEdgeType in_edge, EdgeStructureType *structure) + : id_(structure->ContainerSize()), in_edge_(in_edge) {}; + virtual ~Edge() {}; + + size_t id_; /**< id of this edge */ + InEdgeType in_edge_; /**< id(s) of parent edge(s) */ + OutEdgeType out_edge_; /**< id(s) of child edge(s) */ + }; +} +#endif //BASE_GEOMETRY_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/geometries/geometry_level_set.cpp b/SPHINXsys/src/shared/geometries/geometry_level_set.cpp new file mode 100644 index 0000000000..10c0f5c937 --- /dev/null +++ b/SPHINXsys/src/shared/geometries/geometry_level_set.cpp @@ -0,0 +1,69 @@ +/** + * @file geometry_level_set.cpp + * @author Chi ZHang and Xiangyu Hu + */ + +#include "geometry_level_set.h" + +#include "level_set.h" +#include "base_body.h" +#include "in_output.h" +#include "sph_system.h" + +namespace SPH +{ + //=================================================================================================// + LevelSetComplexShape:: + LevelSetComplexShape(SPHBody *sph_body, ComplexShape &complex_shape, bool isCleaned) + : ComplexShape(complex_shape), level_set_(nullptr) + { + name_ = sph_body->getBodyName(); + level_set_ = sph_body->particle_adaptation_->createLevelSet(complex_shape); + if (isCleaned) + level_set_->cleanInterface(); + + In_Output *in_output = sph_body->getSPHSystem().in_output_; + MeshRecordingToPlt write_level_set(*in_output, sph_body, level_set_); + write_level_set.writeToFile(0); + } + //=================================================================================================// + bool LevelSetComplexShape::checkContain(const Vecd &input_pnt, bool BOUNDARY_INCLUDED) + { + return level_set_->probeSignedDistance(input_pnt) < 0.0 ? true : false; + } + //=================================================================================================// + bool LevelSetComplexShape::checkNearSurface(const Vecd &input_pnt, Real threshold) + { + if (!checkNotFar(input_pnt, threshold)) + return false; + return getMaxAbsoluteElement(findSignedDistance(input_pnt) * + findNormalDirection(input_pnt)) < threshold + ? true + : false; + } + //=================================================================================================// + Real LevelSetComplexShape::findSignedDistance(const Vecd &input_pnt) + { + return level_set_->probeSignedDistance(input_pnt); + } + //=================================================================================================// + Vecd LevelSetComplexShape::findNormalDirection(const Vecd &input_pnt) + { + return level_set_->probeNormalDirection(input_pnt); + } + //=================================================================================================// + bool LevelSetComplexShape::checkNotFar(const Vecd &input_pnt, Real threshold) + { + return level_set_->probeIsWithinMeshBound(input_pnt); + } + //=================================================================================================// + Real LevelSetComplexShape::computeKernelIntegral(const Vecd &input_pnt, Real h_ratio) + { + return level_set_->probeKernelIntegral(input_pnt, h_ratio); + } + //=================================================================================================// + Vecd LevelSetComplexShape::computeKernelGradientIntegral(const Vecd &input_pnt, Real h_ratio) + { + return level_set_->probeKernelGradientIntegral(input_pnt, h_ratio); + } +} diff --git a/SPHINXsys/src/shared/geometries/geometry_level_set.h b/SPHINXsys/src/shared/geometries/geometry_level_set.h new file mode 100644 index 0000000000..48576c6c83 --- /dev/null +++ b/SPHINXsys/src/shared/geometries/geometry_level_set.h @@ -0,0 +1,43 @@ +/** +* @file geometry_level_set.h +* @brief Here, we define geometry based on level set technique. +* @author Luhui Han, Chi ZHang and Xiangyu Hu +*/ + +#ifndef GEOMETRY_LEVEL_SET_H +#define GEOMETRY_LEVEL_SET_H + +#include "geometry.h" + +#include + +namespace SPH +{ + + class SPHBody; + class BaseLevelSet; + /** + * @class LevelSetComplexShape + * @brief the final geomtrical definition of the SPHBody based on a narrow band level set function + * generated from the original ComplexShape + */ + class LevelSetComplexShape : public ComplexShape + { + public: + LevelSetComplexShape(SPHBody *sph_body, ComplexShape &complex_shape, bool isCleaned = false); + virtual ~LevelSetComplexShape(){}; + + virtual bool checkContain(const Vecd &input_pnt, bool BOUNDARY_INCLUDED = true) override; + virtual bool checkNotFar(const Vecd &input_pnt, Real threshold) override; + virtual bool checkNearSurface(const Vecd &input_pnt, Real threshold) override; + virtual Real findSignedDistance(const Vecd &input_pnt) override; + virtual Vecd findNormalDirection(const Vecd &input_pnt) override; + virtual Real computeKernelIntegral(const Vecd &input_pnt, Real h_ratio = 1.0); + virtual Vecd computeKernelGradientIntegral(const Vecd &input_pnt, Real h_ratio = 1.0); + + protected: + BaseLevelSet *level_set_; /**< narrow bounded levelset mesh. */ + }; +} + +#endif //GEOMETRY_LEVEL_SET_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/geometries/level_set.cpp b/SPHINXsys/src/shared/geometries/level_set.cpp new file mode 100644 index 0000000000..36356a6c0a --- /dev/null +++ b/SPHINXsys/src/shared/geometries/level_set.cpp @@ -0,0 +1,273 @@ +/** + * @file level_set.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#include "level_set.h" +#include "base_body.h" +#include "particle_adaptation.h" + +namespace SPH +{ + //=================================================================================================// + LevelSetDataPackage:: + LevelSetDataPackage() : BaseDataPackage<4, 6>(), is_core_pkg_(false) + { + initializePackageDataAddress(phi_, phi_addrs_); + initializePackageDataAddress(n_, n_addrs_); + initializePackageDataAddress(kernel_weight_, kernel_weight_addrs_); + initializePackageDataAddress(kernel_gradient_, kernel_gradient_addrs_); + initializePackageDataAddress(near_interface_id_, near_interface_id_addrs_); + } + //=================================================================================================// + void LevelSetDataPackage:: + assignAllPackageDataAddress(Vecu addrs_index, LevelSetDataPackage *src_pkg, Vecu data_index) + { + assignPackageDataAddress(phi_addrs_, addrs_index, src_pkg->phi_, data_index); + assignPackageDataAddress(n_addrs_, addrs_index, src_pkg->n_, data_index); + assignPackageDataAddress(kernel_weight_addrs_, addrs_index, src_pkg->kernel_weight_, data_index); + assignPackageDataAddress(kernel_gradient_addrs_, addrs_index, src_pkg->kernel_gradient_, data_index); + assignPackageDataAddress(near_interface_id_addrs_, addrs_index, src_pkg->near_interface_id_, data_index); + } + //=================================================================================================// + void LevelSetDataPackage::computeNormalDirection() + { + computeNormalizedGradient(phi_addrs_, n_addrs_); + } + //=================================================================================================// + BaseLevelSet ::BaseLevelSet(ComplexShape &complex_shape, ParticleAdaptation &particle_adaptation) + : BaseMeshField("LevelSet"), + complex_shape_(complex_shape), particle_adaptation_(particle_adaptation) {} + //=================================================================================================// + Real BaseLevelSet::computeHeaviside(Real phi, Real half_width) + { + Real heaviside = 0.0; + Real normalized_phi = phi / half_width; + if (phi < half_width && phi > -half_width) + heaviside = (0.5 + 0.5 * normalized_phi) + 0.5 * sin(Pi * normalized_phi) / Pi; + if (normalized_phi > 1.0) + heaviside = 1.0; + return heaviside; + } + //=================================================================================================// + LevelSet::LevelSet(BoundingBox tentative_bounds, Real data_spacing, + ComplexShape &complex_shape, ParticleAdaptation &particle_adaptation) + : MeshWithDataPackages(tentative_bounds, data_spacing, 4, + complex_shape, particle_adaptation), + global_h_ratio_(particle_adaptation.ReferenceSpacing() / data_spacing), + kernel_(*particle_adaptation.getKernel()) + { + Real far_field_distance = grid_spacing_ * (Real)buffer_width_; + LevelSetDataPackage *negative_far_field = new LevelSetDataPackage(); + negative_far_field->initializeWithUniformData(-far_field_distance); + singular_data_pkgs_addrs.push_back(negative_far_field); + LevelSetDataPackage *positive_far_field = new LevelSetDataPackage(); + positive_far_field->initializeWithUniformData(far_field_distance); + singular_data_pkgs_addrs.push_back(positive_far_field); + initializeDataPackages(); + } + //=================================================================================================// + void LevelSet::initializeDataPackages() + { + MeshFunctor initialize_data_in_a_cell = std::bind(&LevelSet::initializeDataInACell, this, _1, _2); + MeshIterator_parallel(Vecu(0), number_of_cells_, initialize_data_in_a_cell); + MeshFunctor tag_a_cell_inner_pkg = std::bind(&LevelSet::tagACellIsInnerPackage, this, _1, _2); + MeshIterator_parallel(Vecu(0), number_of_cells_, tag_a_cell_inner_pkg); + MeshFunctor initial_address_in_a_cell = std::bind(&LevelSet::initializeAddressesInACell, this, _1, _2); + MeshIterator_parallel(Vecu(0), number_of_cells_, initial_address_in_a_cell); + updateNormalDirection(); + updateKernelIntegrals(); + } + //=================================================================================================// + void LevelSet::initializeAddressesInACell(const Vecu &cell_index, Real dt) + { + initializePackageAddressesInACell(cell_index); + } + //=================================================================================================// + void LevelSet::updateNormalDirection() + { + PackageFunctor update_normal_diraction = + std::bind(&LevelSet::updateNormalDirectionForAPackage, this, _1, _2); + PackageIterator_parallel(inner_data_pkgs_, update_normal_diraction); + } + //=================================================================================================// + void LevelSet::updateKernelIntegrals() + { + PackageFunctor update_kernel_value = + std::bind(&LevelSet::updateKernelIntegralsForAPackage, this, _1, _2); + PackageIterator_parallel(inner_data_pkgs_, update_kernel_value); + } + //=================================================================================================// + Vecd LevelSet::probeNormalDirection(const Vecd &position) + { + return probeMesh, + &LevelSetDataPackage::n_addrs_>(position); + } + //=================================================================================================// + Real LevelSet::probeSignedDistance(const Vecd &position) + { + return probeMesh, + &LevelSetDataPackage::phi_addrs_>(position); + } + //=================================================================================================// + Real LevelSet::probeKernelIntegral(const Vecd &position, Real h_ratio) + { + return probeMesh, + &LevelSetDataPackage::kernel_weight_addrs_>(position); + } + //=================================================================================================// + Vecd LevelSet::probeKernelGradientIntegral(const Vecd &position, Real h_ratio) + { + return probeMesh, + &LevelSetDataPackage::kernel_gradient_addrs_>(position); + } + //=================================================================================================// + void LevelSet:: + updateNormalDirectionForAPackage(LevelSetDataPackage *inner_data_pkg, Real dt) + { + inner_data_pkg->computeNormalDirection(); + } + //=================================================================================================// + void LevelSet:: + updateKernelIntegralsForAPackage(LevelSetDataPackage *inner_data_pkg, Real dt) + { + inner_data_pkg->computeKernelIntegrals(*this); + } + //=================================================================================================// + void LevelSet:: + stepReinitializationForAPackage(LevelSetDataPackage *inner_data_pkg, Real dt) + { + inner_data_pkg->stepReinitialization(); + } + //=============================================================================================// + void LevelSet::reinitializeLevelSet() + { + PackageFunctor reinitialize_levelset = + std::bind(&LevelSet::stepReinitializationForAPackage, this, _1, _2); + for (size_t i = 0; i < 50; ++i) + PackageIterator_parallel(inner_data_pkgs_, reinitialize_levelset); + } + //=================================================================================================// + void LevelSet::markNearInterface() + { + PackageFunctor mark_cutcell_by_levelset = + std::bind(&LevelSet::markNearInterfaceForAPackage, this, _1, _2); + PackageIterator_parallel(core_data_pkgs_, mark_cutcell_by_levelset); + } + //=================================================================================================// + void LevelSet::markNearInterfaceForAPackage(LevelSetDataPackage *core_data_pkg, Real dt) + { + core_data_pkg->markNearInterface(); + } + //=================================================================================================// + void LevelSet::redistanceInterface() + { + PackageFunctor clean_levelset = + std::bind(&LevelSet::redistanceInterfaceForAPackage, this, _1, _2); + PackageIterator_parallel(core_data_pkgs_, clean_levelset); + } + //=================================================================================================// + void LevelSet::cleanInterface(bool isSmoothed) + { + markNearInterface(); + redistanceInterface(); + reinitializeLevelSet(); + updateNormalDirection(); + updateKernelIntegrals(); + } + //=================================================================================================// + bool LevelSet::probeIsWithinMeshBound(const Vecd &position) + { + bool is_bounded = true; + Vecu cell_pos = CellIndexFromPosition(position); + for (int i = 0; i != position.size(); ++i) + { + if (cell_pos[i] < 2) + is_bounded = false; + if (cell_pos[i] > (number_of_cells_[i] - 2)) + is_bounded = false; + } + return is_bounded; + } + //=============================================================================================// + MultilevelLevelSet:: + MultilevelLevelSet(BoundingBox tentative_bounds, Real reference_data_spacing, + size_t total_levels, Real maximum_spacing_ratio, + ComplexShape &complex_shape, ParticleAdaptation &particle_adaptation) + : MultilevelMesh(tentative_bounds, reference_data_spacing, + total_levels, maximum_spacing_ratio, + complex_shape, particle_adaptation) {} + //=================================================================================================// + size_t MultilevelLevelSet::getMeshLevel(Real h_ratio) + { + for (size_t level = total_levels_; level != 0; --level) + if (h_ratio - mesh_levels_[level - 1]->global_h_ratio_ > -Eps) + return level - 1; //jump out the loop! + + std::cout << "\n Error: LevelSet level searching out of bound!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + return 999; //means an error in level searching + }; + //=============================================================================================// + Real MultilevelLevelSet::probeSignedDistance(const Vecd &position) + { + return mesh_levels_[getProbeLevel(position)]->probeSignedDistance(position); + } + //=============================================================================================// + Vecd MultilevelLevelSet::probeNormalDirection(const Vecd &position) + { + return mesh_levels_[getProbeLevel(position)]->probeNormalDirection(position); + } + //=============================================================================================// + size_t MultilevelLevelSet::getProbeLevel(const Vecd &position) + { + for (size_t level = total_levels_; level != 0; --level) + if (mesh_levels_[level - 1]->isWithinCorePackage(position)) + return level - 1; //jump out of the loop! + return 0; + } + //=================================================================================================// + void MultilevelLevelSet::cleanInterface(bool isSmoothed) + { + //current only implement this, to be update later together with particle adaptation + return mesh_levels_[total_levels_ - 1]->cleanInterface(isSmoothed); + } + //=================================================================================================// + Real MultilevelLevelSet::probeKernelIntegral(const Vecd &position, Real h_ratio) + { + size_t coarse_level = getMeshLevel(h_ratio); + Real alpha = (mesh_levels_[coarse_level + 1]->global_h_ratio_ - h_ratio) / + (mesh_levels_[coarse_level + 1]->global_h_ratio_ - mesh_levels_[coarse_level]->global_h_ratio_); + Real coarse_level_value = mesh_levels_[coarse_level]->probeKernelIntegral(position); + Real fine_level_value = mesh_levels_[coarse_level + 1]->probeKernelIntegral(position); + + return alpha * coarse_level_value + (1.0 - alpha) * fine_level_value; + } + //=================================================================================================// + Vecd MultilevelLevelSet::probeKernelGradientIntegral(const Vecd &position, Real h_ratio) + { + size_t coarse_level = getMeshLevel(h_ratio); + Real alpha = (mesh_levels_[coarse_level + 1]->global_h_ratio_ - h_ratio) / + (mesh_levels_[coarse_level + 1]->global_h_ratio_ - mesh_levels_[coarse_level]->global_h_ratio_); + Vecd coarse_level_value = mesh_levels_[coarse_level]->probeKernelGradientIntegral(position); + Vecd fine_level_value = mesh_levels_[coarse_level + 1]->probeKernelGradientIntegral(position); + + return alpha * coarse_level_value + (1.0 - alpha) * fine_level_value; + } + //=================================================================================================// + bool MultilevelLevelSet::probeIsWithinMeshBound(const Vecd &position) + { + bool is_bounded = true; + for (size_t l = 0; l != total_levels_; ++l) + { + if (!mesh_levels_[l]->probeIsWithinMeshBound(position)) + { + is_bounded = false; + break; + }; + } + return is_bounded; + } + //=============================================================================================// +} diff --git a/SPHINXsys/src/shared/geometries/level_set.h b/SPHINXsys/src/shared/geometries/level_set.h new file mode 100644 index 0000000000..67e3a4a5f1 --- /dev/null +++ b/SPHINXsys/src/shared/geometries/level_set.h @@ -0,0 +1,179 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file level_set.h +* @brief This is the base classes of mesh, which describe ordered and indexed +* data sets. Depending on application, there are different data +* saved on the mesh. The intersection points of mesh lines are called +* grid points, the element enclosed by mesh lines (2D) or faces (3D) called +* cells. The mesh line or face are also called cell faces. Grid points are +* also called cell corners. +* @author Chi ZHang and Xiangyu Hu +*/ + +#ifndef LEVEL_SET_H +#define LEVEL_SET_H + +#include "mesh_with_data_packages.h" +#include "mesh_with_data_packages.hpp" +#include "geometry.h" + +namespace SPH +{ + class LevelSet; + class Kernel; + /** + * @class LevelSetDataPackage + * @brief Fixed memory level set data packed in a package. + * Level set is the signed distance to an interface, + * here, the surface of a body. + */ + class LevelSetDataPackage : public BaseDataPackage<4, 6> + { + public: + bool is_core_pkg_; /**< If true, the package is near to zero level set. */ + PackageData phi_; /**< the level set or signed distance. */ + PackageDataAddress phi_addrs_; /**< address for the level set. */ + PackageData n_; /**< level set normalized gradient, to approximate interface normal direction */ + PackageDataAddress n_addrs_; + PackageData kernel_weight_; + PackageDataAddress kernel_weight_addrs_; + PackageData kernel_gradient_; + PackageDataAddress kernel_gradient_addrs_; + /** mark the near interface cells. 0 for zero level set cut cells, + * -1 and 1 for negative and positive cut cells, + * 0 can also be for other cells in the region closed + * by negative and positive cut cells + */ + PackageData near_interface_id_; + PackageDataAddress near_interface_id_addrs_; + + LevelSetDataPackage(); + virtual ~LevelSetDataPackage(){}; + + void assignAllPackageDataAddress(Vecu data_index, LevelSetDataPackage *src_pkg, Vecu addrs_index); + void initializeBasicData(ComplexShape &complex_shape); + void initializeWithUniformData(Real level_set); + void computeKernelIntegrals(LevelSet &level_set); + void computeNormalDirection(); + void stepReinitialization(); + void markNearInterface(); + }; + + /** + * @class BaseLevelSet + * @brief A abstract describes a level set field defined on a mesh. + */ + class BaseLevelSet : public BaseMeshField + { + public: + BaseLevelSet(ComplexShape &complex_shape, ParticleAdaptation &particle_adaptation); + virtual ~BaseLevelSet(){}; + + virtual bool probeIsWithinMeshBound(const Vecd &position) = 0; + virtual Real probeSignedDistance(const Vecd &position) = 0; + virtual Vecd probeNormalDirection(const Vecd &position) = 0; + virtual Real probeKernelIntegral(const Vecd &position, Real h_ratio = 1.0) = 0; + virtual Vecd probeKernelGradientIntegral(const Vecd &position, Real h_ratio = 1.0) = 0; + virtual void cleanInterface(bool isSmoothed = false) = 0; + + protected: + ComplexShape &complex_shape_; /**< the geometry is described by the level set. */ + ParticleAdaptation &particle_adaptation_; + + /** for computing volume fraction occupied by a shape.*/ + Real computeHeaviside(Real phi, Real half_width); + }; + + /** + * @class LevelSet + * @brief Mesh with level set data as packages. + * Note that the mesh containing the data packages are cell-based + * but within the data package, the data is grid-based. + */ + class LevelSet + : public MeshWithDataPackages + { + public: + ConcurrentVector core_data_pkgs_; /**< packages near to zero level set. */ + Real global_h_ratio_; + + LevelSet(BoundingBox tentative_bounds, Real data_spacing, + ComplexShape &complex_shape, ParticleAdaptation &particle_adaptation); + virtual ~LevelSet(){}; + + virtual bool probeIsWithinMeshBound(const Vecd &position) override; + virtual Real probeSignedDistance(const Vecd &position) override; + virtual Vecd probeNormalDirection(const Vecd &position) override; + virtual Real probeKernelIntegral(const Vecd &position, Real h_ratio = 1.0) override; + virtual Vecd probeKernelGradientIntegral(const Vecd &position, Real h_ratio = 1.0) override; + virtual void cleanInterface(bool isSmoothed = false) override; + virtual void writeMeshFieldToPlt(std::ofstream &output_file) override; + bool isWithinCorePackage(Vecd position); + Real computeKernelIntegral(const Vecd &position); + Vecd computeKernelGradientIntegral(const Vecd &position); + + protected: + Kernel &kernel_; + + void reinitializeLevelSet(); + void markNearInterface(); + void redistanceInterface(); + void updateNormalDirection(); + void updateNormalDirectionForAPackage(LevelSetDataPackage *inner_data_pkg, Real dt = 0.0); + void updateKernelIntegrals(); + void updateKernelIntegralsForAPackage(LevelSetDataPackage *inner_data_pkg, Real dt = 0.0); + void stepReinitializationForAPackage(LevelSetDataPackage *inner_data_pkg, Real dt = 0.0); + void markNearInterfaceForAPackage(LevelSetDataPackage *core_data_pkg, Real dt = 0.0); + void redistanceInterfaceForAPackage(LevelSetDataPackage *core_data_pkg, Real dt = 0.0); + virtual void initializeDataInACell(const Vecu &cell_index, Real dt) override; + virtual void initializeAddressesInACell(const Vecu &cell_index, Real dt) override; + virtual void tagACellIsInnerPackage(const Vecu &cell_index, Real dt) override; + virtual void initializeDataPackages() override; + }; + + /** + * @class MultilevelCellLinkedList + * @brief Defining a multilevel level set for a complex region. + */ + class MultilevelLevelSet : public MultilevelMesh + { + public: + MultilevelLevelSet(BoundingBox tentative_bounds, Real reference_data_spacing, + size_t total_levels, Real maximum_spacing_ratio, + ComplexShape &complex_shape, ParticleAdaptation &particle_adaptation); + virtual ~MultilevelLevelSet(){}; + + virtual bool probeIsWithinMeshBound(const Vecd &position) override; + virtual Real probeSignedDistance(const Vecd &position) override; + virtual Vecd probeNormalDirection(const Vecd &position) override; + virtual Real probeKernelIntegral(const Vecd &position, Real h_ratio = 1.0) override; + virtual Vecd probeKernelGradientIntegral(const Vecd &position, Real h_ratio = 1.0) override; + virtual void cleanInterface(bool isSmoothed = false) override; + + protected: + inline size_t getProbeLevel(const Vecd &position); + inline size_t getMeshLevel(Real h_ratio); + }; +} +#endif //LEVEL_SET_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/include/CMakeLists.txt b/SPHINXsys/src/shared/include/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/include/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/include/sphinxsys.h b/SPHINXsys/src/shared/include/sphinxsys.h new file mode 100644 index 0000000000..4f16c8d35e --- /dev/null +++ b/SPHINXsys/src/shared/include/sphinxsys.h @@ -0,0 +1,44 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ + +#ifndef SPHINXSYS_H +#define SPHINXSYS_H + +/** @file +This is the header file that user code should include to pick up all sphinxsys +capabilities. **/ + +#include "all_kernels.h" +#include "all_particles.h" +#include "all_geometries.h" +#include "all_bodies.h" +#include "generative_structures.h" +#include "sph_system.h" +#include "all_materials.h" +#include "all_physical_dynamics.h" +#include "all_simbody.h" +#include "in_output.h" +#include "parameterization.h" +#include "regression_testing.h" + +#endif //SPHINXSYS_H diff --git a/SPHINXsys/src/shared/io_system/CMakeLists.txt b/SPHINXsys/src/shared/io_system/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/io_system/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/io_system/in_output.cpp b/SPHINXsys/src/shared/io_system/in_output.cpp new file mode 100644 index 0000000000..ca5568ce8e --- /dev/null +++ b/SPHINXsys/src/shared/io_system/in_output.cpp @@ -0,0 +1,377 @@ +/** + * @file in_output.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#include "in_output.h" +#include "all_bodies.h" +#include "level_set.h" +#include "sph_system.h" + +namespace SPH +{ + //=============================================================================================// + In_Output::In_Output(SPHSystem& sph_system) + : sph_system_(sph_system), + input_folder_("./input"), output_folder_("./output"), + restart_folder_("./restart"), reload_folder_("./reload") + { + if (!fs::exists(input_folder_)) + { + fs::create_directory(input_folder_); + } + + if (!fs::exists(output_folder_)) + { + fs::create_directory(output_folder_); + } + + if (!fs::exists(restart_folder_)) + { + fs::create_directory(restart_folder_); + } + + if (sph_system.restart_step_ == 0) + { + fs::remove_all(restart_folder_); + fs::create_directory(restart_folder_); + + fs::remove_all(output_folder_); + fs::create_directory(output_folder_); + } + + restart_step_ = std::to_string(sph_system.restart_step_); + + sph_system.in_output_ = this; + } + //=============================================================================================// + void PltEngine:: + writeAQuantityHeader(std::ofstream& out_file, const Real& quantity, std::string quantity_name) + { + out_file << "\"" << quantity_name<< "\"" << " "; + } + //=============================================================================================// + void PltEngine:: + writeAQuantityHeader(std::ofstream& out_file, const Vecd& quantity, std::string quantity_name) + { + for (int i = 0; i != Dimensions; ++i) + out_file << "\"" << quantity_name << "[" << i << "]\"" << " "; + } + //=============================================================================================// + void PltEngine::writeAQuantity(std::ofstream& out_file, const Real& quantity) + { + out_file << std::fixed << std::setprecision(9) << quantity << " "; + } + //=============================================================================================// + void PltEngine::writeAQuantity(std::ofstream& out_file, const Vecd& quantity) + { + for (int i = 0; i < Dimensions; ++i) + out_file << std::fixed << std::setprecision(9) << quantity[i] << " "; + } + //=============================================================================================// + std::string BodyStatesIO::convertPhysicalTimeToString(Real convertPhysicalTimeToStream) + { + int i_time = int(GlobalStaticVariables::physical_time_ * 1.0e6); + std::stringstream s_time; + s_time << std::setw(10) << std::setfill('0') << i_time; + return s_time.str(); + } + //=============================================================================================// + void BodyStatesRecordingToVtu::writeWithFileName(const std::string& sequence) + { + for (SPHBody* body : bodies_) + { + if (body->checkNewlyUpdated()) + { + std::string filefullpath = in_output_.output_folder_ + "/SPHBody_" + body->getBodyName() + "_" + sequence + ".vtu"; + if (fs::exists(filefullpath)) + { + fs::remove(filefullpath); + } + std::ofstream out_file(filefullpath.c_str(), std::ios::trunc); + //begin of the XML file + out_file << "\n"; + out_file << "\n"; + out_file << " \n"; + + BaseParticles* base_particles = body->base_particles_; + size_t total_real_particles = base_particles->total_real_particles_; + out_file << " getBodyName() << "\" NumberOfPoints=\"" << total_real_particles << "\" NumberOfCells=\"0\">\n"; + + body->writeParticlesToVtuFile(out_file); + + out_file << " \n"; + + //write empty cells + out_file << " \n"; + out_file << " \n"; + out_file << " \n"; + out_file << " \n"; + out_file << " \n"; + out_file << " \n"; + out_file << " \n"; + out_file << " \n"; + + out_file << " \n"; + + out_file << " \n"; + out_file << "\n"; + + out_file.close(); + } + body->setNotNewlyUpdated(); + } + } + //=============================================================================================// + void BodyStatesRecordingToPlt::writeWithFileName(const std::string& sequence) + { + for (SPHBody* body : bodies_) + { + if (body->checkNewlyUpdated()) + { + std::string filefullpath = in_output_.output_folder_ + "/SPHBody_" + body->getBodyName()+ "_" + sequence +".plt"; + if (fs::exists(filefullpath)) + { + fs::remove(filefullpath); + } + std::ofstream out_file(filefullpath.c_str(), std::ios::trunc); + + //begin of the plt file writing + + body->writeParticlesToPltFile(out_file); + + out_file.close(); + } + body->setNotNewlyUpdated(); + } + } + //=============================================================================================// + WriteToVtuIfVelocityOutOfBound + ::WriteToVtuIfVelocityOutOfBound(In_Output& in_output, + SPHBodyVector bodies, Real velocity_bound) + : BodyStatesRecordingToVtu(in_output, bodies), out_of_bound_(false) + { + for (SPHBody* body : bodies_) + { + check_bodies_.push_back(new VelocityBoundCheck(body, velocity_bound)); + } + } + //=============================================================================================// + void WriteToVtuIfVelocityOutOfBound::writeWithFileName(const std::string& sequence) + { + for (auto check_body : check_bodies_) + { + out_of_bound_ = out_of_bound_ || check_body->parallel_exec(); + } + + if (out_of_bound_) { + BodyStatesRecordingToVtu::writeWithFileName(sequence); + std::cout << "\n Velocity is out of bound at iteration step " << sequence + << "\n The body states have been outputted and the simulation terminates here. \n"; + } + } + //=============================================================================================// + MeshRecordingToPlt + ::MeshRecordingToPlt(In_Output& in_output, SPHBody* body, BaseMeshField* mesh_field) + : BodyStatesRecording(in_output, body), mesh_field_(mesh_field) + { + filefullpath_ = in_output_.output_folder_ + "/" + body->getBodyName() + "_" + mesh_field_->Name() + ".dat"; + } + //=============================================================================================// + void MeshRecordingToPlt::writeWithFileName(const std::string& sequence) + { + std::ofstream out_file(filefullpath_.c_str(), std::ios::app); + mesh_field_->writeMeshFieldToPlt(out_file); + out_file.close(); + } + //=============================================================================================// + ReloadParticleIO::ReloadParticleIO(In_Output& in_output, SPHBodyVector bodies) : + BodyStatesIO(in_output, bodies) + { + if (!fs::exists(in_output.reload_folder_)) + { + fs::create_directory(in_output.reload_folder_); + } + + for (SPHBody* body : bodies) + { + file_paths_.push_back(in_output.reload_folder_ + "/SPHBody_" + body->getBodyName() + "_rld.xml"); + } + }; + //=============================================================================================// + ReloadParticleIO::ReloadParticleIO(In_Output& in_output, SPHBodyVector bodies, + StdVec given_body_names) : ReloadParticleIO(in_output, bodies) + { + for (size_t i = 0; i != bodies.size(); ++i) + { + file_paths_[i] = in_output.reload_folder_ + "/SPHBody_" + given_body_names[i] + "_rld.xml"; + } + } + //=============================================================================================// + void ReloadParticleIO::writeToFile(size_t iteration_step) + { + for (size_t i = 0; i < bodies_.size(); ++i) + { + std::string filefullpath = file_paths_[i]; + + if (fs::exists(filefullpath)) + { + fs::remove(filefullpath); + } + bodies_[i]->writeToXmlForReloadParticle(filefullpath); + } + } + //=============================================================================================// + void ReloadParticleIO::readFromFile(size_t restart_step) + { + std::cout << "\n Reloading particles from files." << std::endl; + for (size_t i = 0; i < bodies_.size(); ++i) + { + std::string filefullpath = file_paths_[i]; + + if (!fs::exists(filefullpath)) + { + std::cout << "\n Error: the input file:" << filefullpath << " is not exists" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + + bodies_[i]->readFromXmlForReloadParticle(filefullpath); + } + } + //=============================================================================================// + RestartIO::RestartIO(In_Output& in_output, SPHBodyVector bodies) : + BodyStatesIO(in_output, bodies) + { + overall_file_path_ = in_output.restart_folder_ + "/Restart_time_"; + for (SPHBody* body : bodies) + { + file_paths_.push_back(in_output.restart_folder_ + "/SPHBody_" + body->getBodyName() + "_rst_"); + } + } + //=============================================================================================// + void RestartIO::writeToFile(size_t iteration_step) + { + std::string overall_filefullpath = overall_file_path_ + std::to_string(iteration_step) + ".dat"; + if (fs::exists(overall_filefullpath)) + { + fs::remove(overall_filefullpath); + } + std::ofstream out_file(overall_filefullpath.c_str(), std::ios::app); + out_file << std::fixed << std::setprecision(9) << GlobalStaticVariables::physical_time_ << " \n"; + out_file.close(); + + for (size_t i = 0; i < bodies_.size(); ++i) + { + std::string filefullpath = file_paths_[i] + std::to_string(iteration_step) + ".xml"; + + if (fs::exists(filefullpath)) + { + fs::remove(filefullpath); + } + bodies_[i]->writeParticlesToXmlForRestart(filefullpath); + } + } + //=============================================================================================// + Real RestartIO::readRestartTime(size_t restart_step) + { + std::cout << "\n Reading restart files from the restart step = " << restart_step << std::endl; + std::string overall_filefullpath = overall_file_path_ + std::to_string(restart_step) + ".dat"; + if (!fs::exists(overall_filefullpath)) + { + std::cout << "\n Error: the input file:" << overall_filefullpath << " is not exists" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + Real restart_time; + std::ifstream in_file(overall_filefullpath.c_str()); + in_file >> restart_time; + in_file.close(); + + return restart_time; + } + //=============================================================================================// + void RestartIO::readFromFile(size_t restart_step) + { + for (size_t i = 0; i < bodies_.size(); ++i) + { + std::string filefullpath = file_paths_[i] + std::to_string(restart_step) + ".xml"; + + if (!fs::exists(filefullpath)) + { + std::cout << "\n Error: the input file:" << filefullpath << " is not exists" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + + bodies_[i]->readParticlesFromXmlForRestart(filefullpath); + } + } + //=============================================================================================// + WriteSimBodyPinData:: + WriteSimBodyPinData(In_Output& in_output, SimTK::RungeKuttaMersonIntegrator& integ, SimTK::MobilizedBody::Pin& pinbody) : + WriteSimBodyStates(in_output, integ, pinbody) + { + filefullpath_ = in_output_.output_folder_ + "/mb_pinbody_data.dat"; + std::ofstream out_file(filefullpath_.c_str(), std::ios::app); + + out_file << "\"time\"" << " "; + out_file << " " << "angles" << " "; + out_file << " " << "angle_rates" << " "; + out_file << "\n"; + + out_file.close(); + }; + //=============================================================================================// + void WriteSimBodyPinData::writeToFile(size_t iteration_step) + { + std::ofstream out_file(filefullpath_.c_str(), std::ios::app); + out_file << GlobalStaticVariables::physical_time_ << " "; + const SimTK::State& state = integ_.getState(); + + out_file << " " << mobody_.getAngle(state) << " " << mobody_.getRate(state) << " "; + + out_file << "\n"; + out_file.close(); + }; + //=================================================================================================// + ReloadMaterialParameterIO::ReloadMaterialParameterIO(In_Output& in_output, BaseMaterial* material) : + in_output_(in_output), material_(material) + { + file_path_ = in_output.reload_folder_ + "/Material_" + material->LocalParametersName() + "_rld.xml"; + } + //=================================================================================================// + ReloadMaterialParameterIO:: + ReloadMaterialParameterIO(In_Output& in_output, BaseMaterial* material, std::string given_parameters_name) : + in_output_(in_output), material_(material) + { + file_path_ = in_output.reload_folder_ + "/Material_" + given_parameters_name + "_rld.xml"; + } + //=================================================================================================// + void ReloadMaterialParameterIO::writeToFile(size_t iteration_step) + { + std::string reload_material_folder = in_output_.reload_folder_; + if (!fs::exists(reload_material_folder)) + { + fs::create_directory(reload_material_folder); + } + + if (fs::exists(file_path_)) + { + fs::remove(file_path_); + } + material_->writeToXmlForReloadLocalParameters(file_path_); + } + //=================================================================================================// + void ReloadMaterialParameterIO::readFromFile(size_t restart_step) + { + if (!fs::exists(file_path_)) + { + std::cout << "\n Error: the reloading material property file:" << file_path_ << " is not exists" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + material_->readFromXmlForLocalParameters(file_path_); + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/io_system/in_output.h b/SPHINXsys/src/shared/io_system/in_output.h new file mode 100644 index 0000000000..7a2844563f --- /dev/null +++ b/SPHINXsys/src/shared/io_system/in_output.h @@ -0,0 +1,654 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file in_output.h + * @brief Classes for input and output functions. + * @author Chi Zhang and Xiangyu Hu + */ + +#pragma once +#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING + +#include "base_data_package.h" +#include "sph_data_conainers.h" +#include "all_physical_dynamics.h" +#include "xml_engine.h" + +#include "SimTKcommon.h" +#include "SimTKmath.h" +#include "Simbody.h" + +#include +#include +#include +/** Macro for APPLE compilers*/ +#ifdef __APPLE__ +#include +namespace fs = boost::filesystem; +#else +#include +namespace fs = std::experimental::filesystem; +#endif + +namespace SPH { + + /** + * @brief preclaimed classes. + */ + class BaseLevelSet; + + /** + * @class In_Output + * @brief The base class which defines folders for output, + * restart and particle reload folders. + */ + class In_Output + { + public: + In_Output(SPHSystem &sph_system); + virtual ~In_Output() {}; + + SPHSystem &sph_system_; + std::string input_folder_; + std::string output_folder_; + std::string restart_folder_; + std::string reload_folder_; + + std::string restart_step_; + }; + + /** + * @class PltEngine + * @brief The base class which defines Tecplot file related operation. + */ + class PltEngine + { + public: + PltEngine() {}; + virtual ~PltEngine() {}; + + void writeAQuantityHeader(std::ofstream& out_file, const Real& quantity, std::string quantity_name); + void writeAQuantityHeader(std::ofstream& out_file, const Vecd& quantity, std::string quantity_name); + void writeAQuantity(std::ofstream& out_file, const Real& quantity); + void writeAQuantity(std::ofstream& out_file, const Vecd& quantity); + }; + + /** + * @class BodyStatesIO + * @brief base class for write and read body states. + */ + class BodyStatesIO + { + protected: + In_Output &in_output_; + SPHBody *body_; + SPHBodyVector bodies_; + + std::string convertPhysicalTimeToString(Real physical_time); + public: + BodyStatesIO(In_Output &in_output, SPHBody *body) + : in_output_(in_output), body_(body), bodies_({body}) {}; + BodyStatesIO(In_Output& in_output, SPHBodyVector bodies) + : in_output_(in_output), body_(bodies[0]), bodies_(bodies) {}; + virtual ~BodyStatesIO() {}; + }; + + /** + * @class BodyStatesRecording + * @brief base class for write body states. + */ + class BodyStatesRecording : public BodyStatesIO + { + public: + BodyStatesRecording(In_Output &in_output, SPHBody *body) + : BodyStatesIO(in_output, body) {}; + BodyStatesRecording(In_Output &in_output, SPHBodyVector bodies) + : BodyStatesIO(in_output, bodies) {}; + virtual ~BodyStatesRecording() {}; + + /** write with filename indicated by physical time */ + void writeToFile() + { + writeWithFileName(convertPhysicalTimeToString(GlobalStaticVariables::physical_time_)); + }; + + /** write with filename indicated by iteration step */ + virtual void writeToFile(size_t iteration_step) + { + writeWithFileName(std::to_string(iteration_step)); + }; + + protected: + virtual void writeWithFileName(const std::string& sequence) = 0; + }; + + /** + * @class SimBodyStatesIO + * @brief base class for write and read SimBody states. + */ + template + class SimBodyStatesIO + { + protected: + In_Output &in_output_; + SimTK::RungeKuttaMersonIntegrator& integ_; + MobilizedBodyType& mobody_; + public: + SimBodyStatesIO(In_Output& in_output, SimTK::RungeKuttaMersonIntegrator& integ, MobilizedBodyType& mobody) + : in_output_(in_output),integ_(integ), mobody_(mobody) {}; + virtual ~SimBodyStatesIO() {}; + }; + + /** + * @class WriteSimBodyStates + * @brief base class for write SimBody states. + */ + template + class WriteSimBodyStates : public SimBodyStatesIO + { + public: + WriteSimBodyStates(In_Output& in_output, SimTK::RungeKuttaMersonIntegrator& integ, MobilizedBodyType& mobody) + : SimBodyStatesIO(in_output,integ, mobody) {}; + virtual ~WriteSimBodyStates() {}; + + virtual void writeToFile(size_t iteration_step) = 0; + }; + + /** + * @class ReadSimBodyStates + * @brief base class for read SimBody states. + */ + template + class ReadSimBodyStates : public SimBodyStatesIO + { + public: + ReadSimBodyStates(In_Output& in_output, MobilizedBodyType* mobody) + : SimBodyStatesIO(in_output, mobody) {}; + ReadSimBodyStates(In_Output& in_output, StdVec mobodies) + : SimBodyStatesIO(in_output, mobodies) {}; + virtual ~ReadSimBodyStates() {}; + + virtual void readFromFile(size_t iteration_step) = 0; + }; + + /** + * @class BodyStatesRecordingToVtu + * @brief Write files for bodies + * the output file is VTK XML format can visualized by ParaView + * the data type vtkUnstructedGrid + */ + class BodyStatesRecordingToVtu : public BodyStatesRecording + { + public: + BodyStatesRecordingToVtu(In_Output& in_output, SPHBodyVector bodies) + : BodyStatesRecording(in_output, bodies) {}; + virtual ~BodyStatesRecordingToVtu() {}; + + protected: + virtual void writeWithFileName(const std::string& sequence) override; + }; + + /** + * @class BodyStatesRecordingToPlt + * @brief Write files for bodies + * the output file is dat format can visualized by TecPlot + */ + class BodyStatesRecordingToPlt : public BodyStatesRecording + { + public: + BodyStatesRecordingToPlt(In_Output& in_output, SPHBodyVector bodies) + : BodyStatesRecording(in_output, bodies) {}; + virtual ~BodyStatesRecordingToPlt() {}; + + protected: + virtual void writeWithFileName(const std::string& sequence) override; + }; + + /** + * @class WriteToVtuIfVelocityOutOfBound + * @brief output body sates if particle velocity is + * out of a bound + */ + class WriteToVtuIfVelocityOutOfBound + : public BodyStatesRecordingToVtu + { + protected: + bool out_of_bound_; + StdVec check_bodies_; + virtual void writeWithFileName(const std::string& sequence) override; + public: + WriteToVtuIfVelocityOutOfBound(In_Output& in_output, + SPHBodyVector bodies, Real velocity_bound); + virtual ~WriteToVtuIfVelocityOutOfBound() {}; + }; + + /** + * @class MeshRecordingToPlt + * @brief write the background mesh data for relax body + */ + class MeshRecordingToPlt : public BodyStatesRecording + { + protected: + std::string filefullpath_; + BaseMeshField* mesh_field_; + virtual void writeWithFileName(const std::string& sequence) override; + public: + MeshRecordingToPlt(In_Output& in_output, SPHBody* body, BaseMeshField* mesh_field); + virtual ~MeshRecordingToPlt() {}; + }; + + /** + * @class ObservedQuantityRecording + * @brief write files for observed quantity + */ + template + class ObservedQuantityRecording : public BodyStatesRecording, + public observer_dynamics::InterpolatingAQuantity + { + protected: + SPHBody* observer_; + PltEngine plt_engine_; + BaseParticles* base_particles_; + std::string body_name_; + std::string quantity_name_; + XmlEngine observe_xml_engine_; + std::string filefullpath_input_; + std::string filefullpath_output_; + + DataVec current_result_; /* the container of the current result. */ + StdVec element_tag_; /* the container of the current tag. */ + + void WriteDataToXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, + string element_name, size_t particle_n, const Real& quantity) + { + SimTK::Xml::element_iterator ele_ite = element.element_begin(element_name); + std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); + xmlengine.setAttributeToElement(ele_ite, attribute_name_, quantity); + }; + void WriteDataToXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, + string element_name, size_t particle_n, const Vecd& quantity) + { + SimTK::Xml::element_iterator ele_ite = element.element_begin(element_name); + std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); + xmlengine.setAttributeToElement(ele_ite, attribute_name_, quantity); + }; + void WriteDataToXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, + string element_name, size_t particle_n, const Matd& quantity) + { + SimTK::Xml::element_iterator ele_ite = element.element_begin(element_name); + std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); + xmlengine.setAttributeToElement(ele_ite, attribute_name_, quantity); + }; + + void ReadDataFromXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, + size_t particle_n, DataVec& container) + { + size_t index_i_ = 0; + SimTK::Xml::element_iterator ele_ite = element.element_begin(); + for (; ele_ite != element.element_end(); ++ele_ite) + { + std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); + xmlengine.getRequiredAttributeValue(ele_ite, attribute_name_, container[index_i_][particle_n]); + index_i_++; + } + }; + void ReadDataFromXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, + size_t particle_n, DataVec& container) + { + size_t index_i_ = 0; + SimTK::Xml::element_iterator ele_ite = element.element_begin(); + for (; ele_ite != element.element_end(); ++ele_ite) + { + std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); + xmlengine.getRequiredAttributeValue(ele_ite, attribute_name_, container[index_i_][particle_n]); + index_i_++; + } + }; + void ReadDataFromXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, + size_t particle_n, DataVec& container) + { + size_t index_i_ = 0; + SimTK::Xml::element_iterator ele_ite = element.element_begin(); + for (; ele_ite != element.element_end(); ++ele_ite) + { + std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); + xmlengine.getRequiredAttributeMatrixValue(ele_ite, attribute_name_, container[index_i_][particle_n]); + index_i_++; + } + }; + + void ReadTagFromXmlMemory(SimTK::Xml::Element element, StdVec& element_tag) + { + size_t index_i_ = 0; + SimTK::Xml::element_iterator ele_ite = element.element_begin(); + for (; ele_ite != element.element_end(); ++ele_ite) + { + element_tag[index_i_] = ele_ite->getElementTag(); + index_i_++; + } + }; + + public: + ObservedQuantityRecording(std::string quantity_name, In_Output& in_output, + BaseBodyRelationContact* body_contact_relation) : + BodyStatesRecording(in_output, body_contact_relation->sph_body_), + observer_dynamics::InterpolatingAQuantity(body_contact_relation, quantity_name), + observer_(body_contact_relation->sph_body_), plt_engine_(), base_particles_(observer_->base_particles_), + body_name_(body_contact_relation->sph_body_->getBodyName()), quantity_name_(quantity_name), + observe_xml_engine_("xml_observe", quantity_name_) + { + /** Output for .dat file. */ + filefullpath_output_ = in_output_.output_folder_ + "/" + body_name_ + + "_" + quantity_name + "_" + in_output_.restart_step_ + ".dat"; + std::ofstream out_file(filefullpath_output_.c_str(), std::ios::app); + out_file << "run_time" << " "; + for (size_t i = 0; i != base_particles_->total_real_particles_; ++i) + { + std::string quantity_name_i = quantity_name + "[" + std::to_string(i) + "]"; + plt_engine_.writeAQuantityHeader(out_file, this->interpolated_quantities_[i], quantity_name_i); + } + out_file << "\n"; + out_file.close(); + + /** Output for .xml file. */ + filefullpath_input_ = in_output_.input_folder_ + "/" + body_name_ + + "_" + quantity_name + "_" + in_output_.restart_step_ + ".xml"; + }; + virtual ~ObservedQuantityRecording() {}; + + VariableType type_indicator_; /*< this is an indicator to identify the variable type. */ + + void WriteXmlToXmlFile() { observe_xml_engine_.writeToXmlFile(filefullpath_input_); } + + virtual void writeWithFileName(const std::string& sequence) override + { + this->parallel_exec(); + std::ofstream out_file(filefullpath_output_.c_str(), std::ios::app); + out_file << GlobalStaticVariables::physical_time_ << " "; + for (size_t i = 0; i != base_particles_->total_real_particles_; ++i) + { + plt_engine_.writeAQuantity(out_file, this->interpolated_quantities_[i]); + } + out_file << "\n"; + out_file.close(); + }; + + void WriteToXml(size_t iteration = 0) + { + this->parallel_exec(); + std::string element_name_ = "Snapshot_" + std::to_string(iteration); + SimTK::Xml::Element element_ = observe_xml_engine_.root_element_; + observe_xml_engine_.addElementToXmlDoc(element_name_); + for (size_t i = 0; i != base_particles_->total_real_particles_; ++i) + { + WriteDataToXmlMemory(observe_xml_engine_, element_, element_name_, i, this->interpolated_quantities_[i]); + }; + }; + + void ReadFromXml() + { + observe_xml_engine_.loadXmlFile(filefullpath_input_); + size_t number_of_particle_ = base_particles_->total_real_particles_; + size_t number_of_snapshot_ = std::distance(observe_xml_engine_.root_element_.element_begin(), + observe_xml_engine_.root_element_.element_end()); + DataVec current_result_temp_(number_of_snapshot_, StdVec(number_of_particle_)); + StdVec element_tag_temp_(number_of_snapshot_); + current_result_ = current_result_temp_; + element_tag_ = element_tag_temp_; + SimTK::Xml::Element element_ = observe_xml_engine_.root_element_; + for (size_t j = 0; j != number_of_particle_; ++j) + { + ReadDataFromXmlMemory(observe_xml_engine_, element_, j, current_result_); + ReadTagFromXmlMemory(element_, element_tag_); + } + }; + }; + + /** + * @class BodyReducedQuantityRecording + * @brief write reduced quantity of a body + */ + template + class BodyReducedQuantityRecording + { + protected: + In_Output& in_output_; + PltEngine plt_engine_; + ReduceMethodType reduce_method_; + std::string body_name_; + std::string quantity_name_; + XmlEngine observe_xml_engine_; + std::string filefullpath_input_; + std::string filefullpath_output_; + + /*< deduce variable type from reduce method. */ + using VariableType = decltype(reduce_method_.InitialReference()); + DataVec current_result_; /* the container of the current result. */ + StdVec element_tag_; /* the container of the current tag. */ + + void WriteDataToXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, + string element_name, size_t particle_n, const Real& quantity) + { + SimTK::Xml::element_iterator ele_ite = element.element_begin(element_name); + std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); + xmlengine.setAttributeToElement(ele_ite, attribute_name_, quantity); + }; + void WriteDataToXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, + string element_name, size_t particle_n, const Vecd& quantity) + { + SimTK::Xml::element_iterator ele_ite = element.element_begin(element_name); + std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); + xmlengine.setAttributeToElement(ele_ite, attribute_name_, quantity); + }; + void WriteDataToXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, + string element_name, size_t particle_n, const Matd& quantity) + { + SimTK::Xml::element_iterator ele_ite = element.element_begin(element_name); + std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); + xmlengine.setAttributeToElement(ele_ite, attribute_name_, quantity); + }; + + void ReadDataFromXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element_name, + size_t particle_n, DataVec& container) + { + size_t index_i_ = 0; + SimTK::Xml::element_iterator ele_ite = element_name.element_begin(); + for (; ele_ite != element_name.element_end(); ++ele_ite) + { + std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); + xmlengine.getRequiredAttributeValue(ele_ite, attribute_name_, container[index_i_][particle_n]); + index_i_++; + } + }; + void ReadDataFromXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element_name, + size_t particle_n, DataVec& container) + { + size_t index_i_ = 0; + SimTK::Xml::element_iterator ele_ite = element_name.element_begin(); + for (; ele_ite != element_name.element_end(); ++ele_ite) + { + std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); + xmlengine.getRequiredAttributeValue(ele_ite, attribute_name_, container[index_i_][particle_n]); + index_i_++; + } + }; + void ReadDataFromXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element_name, + size_t particle_n, DataVec& container) + { + size_t index_i_ = 0; + SimTK::Xml::element_iterator ele_ite = element_name.element_begin(); + for (; ele_ite != element_name.element_end(); ++ele_ite) + { + std::string attribute_name_ = quantity_name_ + "_" + std::to_string(particle_n); + xmlengine.getRequiredAttributeMatrixValue(ele_ite, attribute_name_, container[index_i_][particle_n]); + index_i_++; + } + }; + + void ReadTagFromXmlMemory(SimTK::Xml::Element element, StdVec& element_tag) + { + size_t index_i_ = 0; + SimTK::Xml::element_iterator ele_ite = element.element_begin(); + for (; ele_ite != element.element_end(); ++ele_ite) + { + element_tag[index_i_] = ele_ite->getElementTag(); + index_i_++; + } + }; + + public: + template + BodyReducedQuantityRecording(In_Output& in_output, ConstructorArgs... constructor_args) : + in_output_(in_output), plt_engine_(), reduce_method_(constructor_args...), + body_name_(reduce_method_.getSPHBody()->getBodyName()), + quantity_name_(reduce_method_.QuantityName()), + observe_xml_engine_("xml_reduce", quantity_name_) + { + /** output for .dat file. */ + filefullpath_output_ = in_output_.output_folder_ + "/" + body_name_ + + "_" + quantity_name_ + "_" + in_output_.restart_step_ + ".dat"; + std::ofstream out_file(filefullpath_output_.c_str(), std::ios::app); + out_file << "\"run_time\"" << " "; + plt_engine_.writeAQuantityHeader(out_file, reduce_method_.InitialReference(), quantity_name_); + out_file << "\n"; + out_file.close(); + + /** output for .xml file. */ + filefullpath_input_ = in_output_.input_folder_ + "/" + body_name_ + + "_" + quantity_name_ + "_" + in_output_.restart_step_ + ".xml"; + }; + virtual ~BodyReducedQuantityRecording() {}; + + VariableType type_indicator_; /*< this is an indicator to identify the variable type. */ + + void writeXmlToXmlFile() { observe_xml_engine_.writeToXmlFile(filefullpath_input_); } + + virtual void writeToFile(size_t iteration_step = 0) + { + std::ofstream out_file(filefullpath_output_.c_str(), std::ios::app); + out_file << GlobalStaticVariables::physical_time_ << " "; + plt_engine_.writeAQuantity(out_file, reduce_method_.parallel_exec()); + out_file << "\n"; + out_file.close(); + }; + + void WriteToXml(size_t iteration = 0) + { + std::string element_name_ = "Snapshot_" + std::to_string(iteration); + SimTK::Xml::Element element_ = observe_xml_engine_.root_element_; + observe_xml_engine_.addElementToXmlDoc(element_name_); + WriteDataToXmlMemory(observe_xml_engine_, element_, element_name_, 0, reduce_method_.parallel_exec()); + }; + + void ReadFromXml() + { + observe_xml_engine_.loadXmlFile(filefullpath_input_); + size_t number_of_particle_ = 1; + size_t number_of_snapshot_ = std::distance(observe_xml_engine_.root_element_.element_begin(), + observe_xml_engine_.root_element_.element_end()); + DataVec current_result_temp_(number_of_snapshot_, StdVec(number_of_particle_)); + StdVec element_tag_temp_(number_of_snapshot_); + current_result_ = current_result_temp_; + element_tag_ = element_tag_temp_; + SimTK::Xml::Element element_ = observe_xml_engine_.root_element_; + for (size_t j = 0; j != number_of_particle_; ++j) + { + ReadDataFromXmlMemory(observe_xml_engine_, element_, j, current_result_); + ReadTagFromXmlMemory(element_, element_tag_); + } + }; + }; + + /** + * @class ReloadParticleIO + * @brief Write the reload particles file in XML format. + */ + class ReloadParticleIO : public BodyStatesIO + { + protected: + StdVec file_paths_; + public: + ReloadParticleIO(In_Output& in_output, SPHBodyVector bodies); + ReloadParticleIO(In_Output& in_output, SPHBodyVector bodies, StdVec given_body_names); + virtual ~ReloadParticleIO() {}; + + virtual void writeToFile(size_t iteration_step = 0); + virtual void readFromFile(size_t iteration_step = 0); + }; + + /** + * @class RestartIO + * @brief Write the restart file in XML format. + */ + class RestartIO : public BodyStatesIO + { + protected: + std::string overall_file_path_; + StdVec file_paths_; + + Real readRestartTime(size_t restart_step); + public: + RestartIO(In_Output& in_output, SPHBodyVector bodies); + virtual ~RestartIO() {}; + + virtual void writeToFile(size_t iteration_step = 0); + virtual void readFromFile(size_t iteration_step = 0); + virtual Real readRestartFiles(size_t restart_step) { + readFromFile(restart_step); + return readRestartTime(restart_step); + }; + }; + + /** + * @class WriteSimBodyPinData + * @brief Write total force acting a solid body. + */ + class WriteSimBodyPinData : public WriteSimBodyStates + { + protected: + std::string filefullpath_; + public: + WriteSimBodyPinData(In_Output& in_output, SimTK::RungeKuttaMersonIntegrator& integ, SimTK::MobilizedBody::Pin& pinbody); + virtual ~WriteSimBodyPinData() {}; + virtual void writeToFile(size_t iteration_step = 0) override; + }; + + /** + * @class ReloadMaterialParameterIO + * @brief For write and read material property. + */ + class ReloadMaterialParameterIO + { + protected: + In_Output& in_output_; + BaseMaterial *material_; + std::string file_path_; + public: + ReloadMaterialParameterIO(In_Output& in_output, BaseMaterial* material); + ReloadMaterialParameterIO(In_Output& in_output, BaseMaterial *material, std::string given_parameters_name); + virtual ~ReloadMaterialParameterIO() {}; + + virtual void writeToFile(size_t iteration_step = 0); + virtual void readFromFile(size_t iteration_step = 0); + }; +} \ No newline at end of file diff --git a/SPHINXsys/src/shared/io_system/parameterization.cpp b/SPHINXsys/src/shared/io_system/parameterization.cpp new file mode 100644 index 0000000000..d64318bb90 --- /dev/null +++ b/SPHINXsys/src/shared/io_system/parameterization.cpp @@ -0,0 +1,23 @@ +/** + * @file parameterization.cpp + * @author Xiangyu Hu + */ + +#include "parameterization.h" + +namespace SPH +{ + //=============================================================================================// + ParameterizationIO::ParameterizationIO(In_Output& in_output) : + xml_paremeters_("xml_parameters", "parameters") + { + filefullpath_ = in_output.input_folder_ + "/" + "project_parameters.dat"; + xml_paremeters_.loadXmlFile(filefullpath_); + } + //=============================================================================================// + void ParameterizationIO::writeProjectParameters() + { + xml_paremeters_.writeToXmlFile(filefullpath_); + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/io_system/parameterization.h b/SPHINXsys/src/shared/io_system/parameterization.h new file mode 100644 index 0000000000..457421a66d --- /dev/null +++ b/SPHINXsys/src/shared/io_system/parameterization.h @@ -0,0 +1,96 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file parameterization.h + * @brief This is the base classes for introducing the parameterization + * of a class or method. + * @author Xiangyu Hu + */ + +#ifndef SPHINXSYS_PARAMETERIZATION_H +#define SPHINXSYS_PARAMETERIZATION_H + +#include "base_data_package.h" +#include "in_output.h" +#include "xml_engine.h" + +#include + +namespace SPH +{ + class ParameterizationIO + { + public: + XmlEngine xml_paremeters_; + std::string filefullpath_; + + ParameterizationIO(In_Output& in_output); + ~ParameterizationIO() {}; + + void writeProjectParameters(); + }; + + template + class BaseParameterization : public BaseClassType + { + public: + template + explicit BaseParameterization(ParameterizationIO& parameterization_io, ConstructorArgs... constructor_args) : + BaseClassType(constructor_args...), xml_paremeters_(parameterization_io.xml_paremeters_), + filefullpath_(parameterization_io.filefullpath_) {}; + ~BaseParameterization() {}; + protected: + XmlEngine& xml_paremeters_; + std::string filefullpath_; + + template + void getAParameter(const std::string& element_name, const std::string& variable_name, VariableType& variable_addrs) + { + SimTK::Xml::element_iterator ele_ite = + xml_paremeters_.root_element_.element_begin(element_name); + if(ele_ite != xml_paremeters_.root_element_.element_end()) + { + xml_paremeters_.getRequiredAttributeValue(ele_ite, variable_name, variable_addrs); + } + else { + std::cout << "\n Error: the variable '" << variable_name << "' is given not in project_parameters.dat !" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + }; + + template + void setAParameter(const std::string& element_name, const std::string& variable_name, VariableType& variable_addrs) + { + SimTK::Xml::element_iterator ele_ite = + xml_paremeters_.root_element_.element_begin(element_name); + if (ele_ite == xml_paremeters_.root_element_.element_end()) + { + xml_paremeters_.addElementToXmlDoc(element_name); + ele_ite = xml_paremeters_.root_element_.element_begin(element_name); + } + xml_paremeters_.setAttributeToElement(ele_ite, variable_name, variable_addrs); + }; + }; +} +#endif //SPHINXSYS_PARAMETERIZATION_H diff --git a/SPHINXsys/src/shared/kernels/CMakeLists.txt b/SPHINXsys/src/shared/kernels/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/kernels/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/kernels/all_kernels.h b/SPHINXsys/src/shared/kernels/all_kernels.h new file mode 100644 index 0000000000..4a6e66db0f --- /dev/null +++ b/SPHINXsys/src/shared/kernels/all_kernels.h @@ -0,0 +1,10 @@ + +#ifndef ALL_KERNELS_H +#define ALL_KERNELS_H + + + +#include "kernel_wenland_c2.h" +#include "kernel_hyperbolic.h" +#include "kernel_tabulated.hpp" +#endif //ALL_KERNELS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/kernels/base_kernel.cpp b/SPHINXsys/src/shared/kernels/base_kernel.cpp new file mode 100644 index 0000000000..ceeee838b8 --- /dev/null +++ b/SPHINXsys/src/shared/kernels/base_kernel.cpp @@ -0,0 +1,180 @@ +/** + * @file base_kernel.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#include "base_kernel.h" + +namespace SPH +{ + //=================================================================================================// + Kernel::Kernel(std::string kernel_name) + : kernel_name_(kernel_name), h_(1.0), inv_h_(1.0) {}; + //=================================================================================================// + void Kernel::initialize(Real h) + { + h_ = h; + inv_h_ = 1.0 / h; + if (h <= 0.0) + { + std::cout << "\n FAILURE: The Kernel gets a non-positive smoothing length \"" + << h << "\"!\n"; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + + setBasicParameters(); + setDerivativeParameters(); + } + //=================================================================================================// + void Kernel::setDerivativeParameters() + { + cutoff_radius_ref_ = KernelSize()*h_; + factor_dW_1D_ = inv_h_ * factor_W_1D_; + factor_dW_2D_ = inv_h_ * factor_W_2D_; + factor_dW_3D_ = inv_h_ * factor_W_3D_; + factor_d2W_1D_ = inv_h_ * factor_dW_1D_; + factor_d2W_2D_ = inv_h_ * factor_dW_2D_; + factor_d2W_3D_ = inv_h_ * factor_dW_3D_; + } + //=================================================================================================// + Real Kernel::W(const Real& r_ij, const Real& displacement) const + { + Real q = r_ij * inv_h_; + return factor_W_1D_ * W_1D(q); + } + //=================================================================================================// + Real Kernel::W(const Real& r_ij, const Vec2d& displacement) const + { + Real q = r_ij * inv_h_; + return factor_W_2D_ * W_2D(q); + } + //=================================================================================================// + Real Kernel::W(const Real& r_ij, const Vec3d& displacement) const + { + Real q = r_ij * inv_h_; + return factor_W_3D_ * W_3D(q); + } + //=================================================================================================// + Real Kernel::dW(const Real& r_ij, const Real& displacement) const + { + Real q = r_ij * inv_h_; + return factor_dW_1D_ * dW_1D(q); + } + //=================================================================================================// + Real Kernel::dW(const Real& r_ij, const Vec2d& displacement) const + { + Real q = r_ij * inv_h_; + return factor_dW_2D_ * dW_2D(q); + } + //=================================================================================================// + Real Kernel::dW(const Real& r_ij, const Vec3d& displacement) const + { + Real q = r_ij * inv_h_; + return factor_dW_3D_ * dW_3D(q); + } + //=================================================================================================// + Real Kernel::d2W(const Real& r_ij, const Real& displacement) const + { + Real q = r_ij * inv_h_; + return factor_d2W_1D_ * d2W_1D(q); + } + //=================================================================================================// + Real Kernel::d2W(const Real& r_ij, const Vec2d& displacement) const + { + Real q = r_ij * inv_h_; + return factor_d2W_2D_ * d2W_2D(q); + } + //=================================================================================================// + Real Kernel::d2W(const Real& r_ij, const Vec3d& displacement) const + { + Real q = r_ij * inv_h_; + return factor_d2W_3D_ * d2W_3D(q); + } + //=================================================================================================// + Real Kernel::W(const Real& h_ratio, const Real& r_ij, const Real& displacement) const + { + Real q = r_ij * inv_h_ * h_ratio; + return factor_W_1D_ * W_1D(q) * SmoothingLengthFactor1D(h_ratio); + } + //=================================================================================================// + Real Kernel::W(const Real& h_ratio, const Real& r_ij, const Vec2d& displacement) const + { + Real q = r_ij * inv_h_ * h_ratio; + return factor_W_2D_ * W_2D(q) * SmoothingLengthFactor2D(h_ratio); + } + //=================================================================================================// + Real Kernel::W(const Real& h_ratio, const Real& r_ij, const Vec3d& displacement) const + { + Real q = r_ij * inv_h_ * h_ratio; + return factor_W_3D_ * W_3D(q) * SmoothingLengthFactor3D(h_ratio); + } + //=================================================================================================// + Real Kernel::W0(const Real& h_ratio, const Real& point_i) const + { + return factor_W_1D_ * SmoothingLengthFactor1D(h_ratio); + }; + //=================================================================================================// + Real Kernel::W0(const Real& h_ratio, const Vec2d& point_i) const + { + return factor_W_2D_ * SmoothingLengthFactor2D(h_ratio); + }; + //=================================================================================================// + Real Kernel::W0(const Real& h_ratio, const Vec3d& point_i) const + { + return factor_W_3D_ * SmoothingLengthFactor3D(h_ratio); + }; + //=================================================================================================// + Real Kernel::dW(const Real& h_ratio, const Real& r_ij, const Real& displacement) const + { + Real q = r_ij * inv_h_ * h_ratio; + return factor_dW_1D_ * dW_1D(q) * SmoothingLengthFactor1D(h_ratio); + } + //=================================================================================================// + Real Kernel::dW(const Real& h_ratio, const Real& r_ij, const Vec2d& displacement) const + { + Real q = r_ij * inv_h_ * h_ratio; + return factor_dW_2D_ * dW_2D(q) * SmoothingLengthFactor2D(h_ratio); + } + //=================================================================================================// + Real Kernel::dW(const Real& h_ratio, const Real& r_ij, const Vec3d& displacement) const + { + Real q = r_ij * inv_h_ * h_ratio; + return factor_dW_3D_ * dW_3D(q) * SmoothingLengthFactor3D(h_ratio); + } + //=================================================================================================// + Real Kernel::d2W(const Real& h_ratio, const Real& r_ij, const Real& displacement) const + { + Real q = r_ij * inv_h_ * h_ratio; + return factor_d2W_1D_ * d2W_1D(q) * SmoothingLengthFactor1D(h_ratio); + } + //=================================================================================================// + Real Kernel::d2W(const Real& h_ratio, const Real& r_ij, const Vec2d& displacement) const + { + Real q = r_ij * inv_h_ * h_ratio; + return factor_d2W_2D_ * d2W_2D(q) * SmoothingLengthFactor2D(h_ratio); + } + //=================================================================================================// + Real Kernel::d2W(const Real& h_ratio, const Real& r_ij, const Vec3d& displacement) const + { + Real q = r_ij * inv_h_ * h_ratio; + return factor_d2W_3D_ * d2W_3D(q) * SmoothingLengthFactor3D(h_ratio); + } + //=================================================================================================// + void Kernel::reduceOnce() + { + factor_W_3D_ = factor_W_2D_; + factor_W_2D_ = factor_W_1D_; + factor_W_1D_ = 0.0; + setDerivativeParameters(); + } + //=================================================================================================// + void Kernel::reduceTwice() + { + factor_W_3D_ = factor_W_1D_; + factor_W_2D_ = 0.0; + factor_W_1D_ = 0.0; + setDerivativeParameters(); + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/kernels/base_kernel.h b/SPHINXsys/src/shared/kernels/base_kernel.h new file mode 100644 index 0000000000..b3e672ae57 --- /dev/null +++ b/SPHINXsys/src/shared/kernels/base_kernel.h @@ -0,0 +1,176 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file base_kernel.h +* @brief This is the base classes of kernel functions. Implementation will be +* implemented in derived classes. The kernal function define the relevance +* between two neighboring particles. Basically, the further the two +* particles, the less relevance they have. +* @author Luhui Han, Chi ZHang and Xiangyu Hu +* @version 0.1 +* @version 0.3.0 +* Add the reduced kernel for reduced dynamics of linear structure. +* -- Chi ZHANG +*/ + + +#ifndef BASE_KERNELS_H +#define BASE_KERNELS_H + + + +#include "base_data_package.h" + +#include + +namespace SPH +{ + /** + * @class Kernel + * @brief Abstract base class of a general SPH kernel function which + * is a smoothed Dirac delta function, + * a kernel function is radial symmetric, and has a scaling factor. + * Based on difference data type in 2d or 3d buildings, + * the kernel is defined for 2 and 3 dimensions. + * The kernel gives value one at the origin. + * The naming of kernel function follows the stand SPH literature. + * Currently, only constant smoothing length is applied. + * Basically, one can assign different kernel for different particle interactions. + */ + class Kernel + { + protected: + const std::string kernel_name_; + Real h_, inv_h_; /**< reference smoothing length and its inverse **/ + Real cutoff_radius_ref_; /** reference cut off radius **/ + /** Normalization factors for the kernel function **/ + Real factor_W_1D_, factor_W_2D_, factor_W_3D_; + /** Auxiliary factors for the derivative of kernel function **/ + Real factor_dW_1D_, factor_dW_2D_, factor_dW_3D_; + /** Auxiliary factors for the second order derivative of kernel function **/ + Real factor_d2W_1D_, factor_d2W_2D_, factor_d2W_3D_; + + virtual void setBasicParameters() = 0; + void setDerivativeParameters(); + public: + /** empty initialization in constructor, initialization will be carried out later. */ + Kernel(std::string kernel_name = "Kernel"); + virtual ~Kernel() {}; + + void initialize(Real h); + std::string Name() const { return kernel_name_; }; + Real SmoothingLength() const { return h_; }; + /**< non-dimensional size of the kernel, generally 2.0 **/ + virtual Real KernelSize() const { return 2.0; }; + Real CutOffRadius() const { return cutoff_radius_ref_; }; + Real FactorW1D() const { return factor_W_1D_; }; + Real FactorW2D() const { return factor_W_2D_; }; + Real FactorW3D() const { return factor_W_3D_; }; + + /** Calculates the kernel value for the given displacement of two particles + * r_ij pointing from particle j to particle i + */ + virtual Real W(const Real& r_ij, const Real& displacement) const; + virtual Real W(const Real& r_ij, const Vec2d& displacement) const; + virtual Real W(const Real& r_ij, const Vec3d& displacement) const; + + /** this value could be use to calculate the value of W + * they are realized in specific kernel implementations + */ + virtual Real W_1D(const Real q) const = 0; + virtual Real W_2D(const Real q) const = 0; + virtual Real W_3D(const Real q) const = 0; + + /** Calculates the kernel value at the origin **/ + virtual Real W0(const Real& point_i) const { return factor_W_1D_; }; + virtual Real W0(const Vec2d& point_i) const { return factor_W_2D_; }; + virtual Real W0(const Vec3d& point_i) const { return factor_W_3D_; }; + + /** Calculates the kernel derivation for + * the given distance of two particles + */ + virtual Real dW(const Real& r_ij, const Real& displacement) const; + virtual Real dW(const Real& r_ij, const Vec2d& displacement) const; + virtual Real dW(const Real& r_ij, const Vec3d& displacement) const; + + /** this value could be use to calculate the value of dW + * they are realized in specific kernel implementations + */ + virtual Real dW_1D(const Real q) const = 0; + virtual Real dW_2D(const Real q) const = 0; + virtual Real dW_3D(const Real q) const = 0; + + /** Calculates the kernel second order derivation for + * the given distance of two particles + */ + virtual Real d2W(const Real& r_ij, const Real& displacement) const; + virtual Real d2W(const Real& r_ij, const Vec2d& displacement) const; + virtual Real d2W(const Real& r_ij, const Vec3d& displacement) const; + + /** this value could be use to calculate the value of d2W + * they are realized in specific kernel implementations + */ + virtual Real d2W_1D(const Real q) const = 0; + virtual Real d2W_2D(const Real q) const = 0; + virtual Real d2W_3D(const Real q) const = 0; + + //---------------------------------------------------------------------- + // Below are for variable smoothing length. + // Note that we input the ratio between the reference smoothing length + // to the variable smoothing length. + //---------------------------------------------------------------------- + protected: + Real SmoothingLengthFactor1D(const Real& h_ratio) const { return h_ratio; }; + Real SmoothingLengthFactor2D(const Real& h_ratio) const { return h_ratio * h_ratio; }; + Real SmoothingLengthFactor3D(const Real& h_ratio) const { return h_ratio * h_ratio * h_ratio; }; + + public: + Real CutOffRadius(Real h_ratio) const { return cutoff_radius_ref_ / h_ratio; }; + + Real W(const Real& h_ratio, const Real& r_ij, const Real& displacement) const; + Real W(const Real& h_ratio, const Real& r_ij, const Vec2d& displacement) const; + Real W(const Real& h_ratio, const Real& r_ij, const Vec3d& displacement) const; + + /** Calculates the kernel value at the origin **/ + Real W0(const Real& h_ratio, const Real& point_i) const; + Real W0(const Real& h_ratio, const Vec2d& point_i) const; + Real W0(const Real& h_ratio, const Vec3d& point_i) const; + + /** Calculates the kernel derivation for the given distance of two particles **/ + Real dW(const Real& h_ratio, const Real& r_ij, const Real& displacement) const; + Real dW(const Real& h_ratio, const Real& r_ij, const Vec2d& displacement) const; + Real dW(const Real& h_ratio, const Real& r_ij, const Vec3d& displacement) const; + + /** Calculates the kernel second order derivation for the given distance of two particles **/ + Real d2W(const Real& h_ratio, const Real& r_ij, const Real& displacement) const; + Real d2W(const Real& h_ratio, const Real& r_ij, const Vec2d& displacement) const; + Real d2W(const Real& h_ratio, const Real& r_ij, const Vec3d& displacement) const; + //---------------------------------------------------------------------- + // Below are for reduced kernels. + //---------------------------------------------------------------------- + public: + void reduceOnce(); /** reduce for thin structures or films */ + void reduceTwice(); /** reduce for linear structures or filaments */ + }; +} +#endif //BASE_KERNELS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/kernels/kernel_hyperbolic.cpp b/SPHINXsys/src/shared/kernels/kernel_hyperbolic.cpp new file mode 100644 index 0000000000..9232deaac6 --- /dev/null +++ b/SPHINXsys/src/shared/kernels/kernel_hyperbolic.cpp @@ -0,0 +1,84 @@ +/** + * @file kernel_hyperbolic.cpp + * @author Luhui Han, Chi ZHang, Yongchuan Yu and Xiangyu Hu + */ + +#include "kernel_hyperbolic.h" + +#include + +namespace SPH +{ + //=================================================================================================// + KernelHyperbolic::KernelHyperbolic() + : Kernel("HyperbolicKernel") {} + //=================================================================================================// + void KernelHyperbolic::setBasicParameters() + { + factor_W_1D_ = inv_h_ / 7.0; + factor_W_2D_ = inv_h_ * inv_h_ / (3.0 * Pi); + factor_W_3D_ = inv_h_ * inv_h_ * inv_h_ * 15.0 / (62.0 *Pi); + } + //=================================================================================================// + Real KernelHyperbolic::W_1D(const Real q) const + { + if (q < 1.0) { + return (6.0 - 6.0 * q + powerN(q, 3)); + } + else { + return powerN(2.0 - q, 3); + } + } + //=================================================================================================// + Real KernelHyperbolic::W_2D(const Real q) const + { + return W_1D(q); + } + //=================================================================================================// + Real KernelHyperbolic::W_3D(const Real q) const + { + return W_1D(q); + } + //=================================================================================================// + Real KernelHyperbolic::dW_1D(const Real q) const + { + if (q < 1.0) { + return (-6.0 + 3.0 * powerN(q, 2)); + } + else { + return powerN(2.0 - q, 2) * (-1.0); + } + } + //=================================================================================================// + Real KernelHyperbolic::dW_2D(const Real q) const + { + return dW_1D(q); + } + //=================================================================================================// + Real KernelHyperbolic::dW_3D(const Real q) const + { + return dW_1D(q); + } + //=================================================================================================// + Real KernelHyperbolic::d2W_1D(const Real q) const + { + if (q < 1.0) { + return 6.0 * q; + } + else { + return 2.0 * (2.0 - q); + } + } + //=================================================================================================// + Real KernelHyperbolic::d2W_2D(const Real q) const + { + return d2W_1D(q); + } + //=================================================================================================// + Real KernelHyperbolic::d2W_3D(const Real q) const + { + return d2W_1D(q); + } + //=================================================================================================// +} + \ No newline at end of file diff --git a/SPHINXsys/src/shared/kernels/kernel_hyperbolic.h b/SPHINXsys/src/shared/kernels/kernel_hyperbolic.h new file mode 100644 index 0000000000..aa7384f5e2 --- /dev/null +++ b/SPHINXsys/src/shared/kernels/kernel_hyperbolic.h @@ -0,0 +1,67 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file kernel_hyperbolic.h +* @brief Here, we define hyperbolic kernel functions. +* @details Numerical experiments suggests +* the this kernel is more stable than gaussian like kernel due to its spike +* at the origin. However, it is also found that such kernels give bad density +* predictions. Therefore, the application of this kernel should be clarified. +* @author Chi ZHang and Xiangyu Hu +*/ + + +#ifndef KERNEL_HYPERBOLIC_H +#define KERNEL_HYPERBOLIC_H + + + +#include "base_kernel.h" + +namespace SPH +{ + /** + * @class KernelHyperbolic + * @brief Kernel from Yang el al. + */ + class KernelHyperbolic : public Kernel + { + protected: + virtual void setBasicParameters() override; + public: + KernelHyperbolic(); + + virtual Real W_1D(const Real q) const override; + virtual Real W_2D(const Real q) const override; + virtual Real W_3D(const Real q) const override; + + virtual Real dW_1D(const Real q) const override; + virtual Real dW_2D(const Real q) const override; + virtual Real dW_3D(const Real q) const override; + + virtual Real d2W_1D(const Real q) const override; + virtual Real d2W_2D(const Real q) const override; + virtual Real d2W_3D(const Real q) const override; + }; +} +#endif //KERNEL_HYPERBOLIC_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/kernels/kernel_quadratic.cpp b/SPHINXsys/src/shared/kernels/kernel_quadratic.cpp new file mode 100644 index 0000000000..be98c30d62 --- /dev/null +++ b/SPHINXsys/src/shared/kernels/kernel_quadratic.cpp @@ -0,0 +1,84 @@ +/** + * @file kernel_hyperbolic.cpp + * @author Luhui Han, Chi ZHang, Yongchuan Yu and Xiangyu Hu + */ + +#include "kernel_quadratic.h" + +#include + +namespace SPH +{ + //=================================================================================================// + KernelQuadratic::KernelQuadratic() + : Kernel("QuadraticKernel") + { + std::cout << "\n Error: The KernelQuadratic is not implemented yet! \n"; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + //=================================================================================================// + void KernelQuadratic::setBasicParameters() + { + factor_W_1D_ = inv_h_ / 7.0; + factor_W_2D_ = inv_h_ * inv_h_ / (3.0 * Pi); + factor_W_3D_ = inv_h_ * inv_h_ * inv_h_ / Pi; + } + //=================================================================================================// + Real KernelQuadratic::W_1D(const Real q) const + { + return 5.0 * (3.0 * q * q - 12.0 * q + 12.0) / 64.0; + } + //=================================================================================================// + Real KernelQuadratic::W_2D(const Real q) const + { + return W_1D(q); + } + //=================================================================================================// + Real KernelQuadratic::W_3D(const Real q) const + { + return W_1D(q); + } + //=================================================================================================// + Real KernelQuadratic::dW_1D(const Real q) const + { + if (q < 1.0) { + return (-6.0 + 3.0 * powerN(q, 2)); + } + else { + return powerN(2.0 - q, 2) * (-1.0); + } + } + //=================================================================================================// + Real KernelQuadratic::dW_2D(const Real q) const + { + return dW_1D(q); + } + //=================================================================================================// + Real KernelQuadratic::dW_3D(const Real q) const + { + return 15.0 * (q - 2.0) / 32.0; + } + //=================================================================================================// + Real KernelQuadratic::d2W_1D(const Real q) const + { + if (q < 1.0) { + return 6.0 * q; + } + else { + return 2.0 * (2.0 - q); + } + } + //=================================================================================================// + Real KernelQuadratic::d2W_2D(const Real q) const + { + return d2W_1D(q); + } + //=================================================================================================// + Real KernelQuadratic::d2W_3D(const Real q) const + { + return 15.0 / 32.0; + } + //=================================================================================================// +} + \ No newline at end of file diff --git a/SPHINXsys/src/shared/kernels/kernel_quadratic.h b/SPHINXsys/src/shared/kernels/kernel_quadratic.h new file mode 100644 index 0000000000..991eec2dee --- /dev/null +++ b/SPHINXsys/src/shared/kernels/kernel_quadratic.h @@ -0,0 +1,67 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file kernel_quadratic.h +* @brief Here, we define quadratic kernel functions but not fully implemented it yet. +* @details This kernel function is very simple. +* I put here because some references use it for solid dynamics. +* However, I am quite skeptical on it application in fluid dynamics, +* in which the first order consistency correction is not applied. +* @author Xiangyu Hu +*/ + + +#ifndef KERNEL_QUADRATIC_H +#define KERNEL_QUADRATIC_H + + + +#include "base_kernel.h" + +namespace SPH +{ + /** + * @class KernelQuadratic + * @brief Kernel from Yang el al. + */ + class KernelQuadratic : public Kernel + { + protected: + virtual void setBasicParameters() override; + public: + KernelQuadratic(); + + virtual Real W_1D(const Real q) const override; + virtual Real W_2D(const Real q) const override; + virtual Real W_3D(const Real q) const override; + + virtual Real dW_1D(const Real q) const override; + virtual Real dW_2D(const Real q) const override; + virtual Real dW_3D(const Real q) const override; + + virtual Real d2W_1D(const Real q) const override; + virtual Real d2W_2D(const Real q) const override; + virtual Real d2W_3D(const Real q) const override; + }; +} +#endif //KERNEL_QUADRATIC_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/kernels/kernel_tabulated.hpp b/SPHINXsys/src/shared/kernels/kernel_tabulated.hpp new file mode 100644 index 0000000000..6332b4f071 --- /dev/null +++ b/SPHINXsys/src/shared/kernels/kernel_tabulated.hpp @@ -0,0 +1,152 @@ +/** +* @file kerneltabulated.hpp +* @brief This is the class for tabulated kernels using template. +* @details This kernel tabulate a kernel function +* so that computing any kernel will have cost the same amount of time. +* @author Yongchuan Yu, Massoud Rezevand, Chi ZHang and Xiangyu Hu +*/ + + +#ifndef KERNEL_TABULATED_HPP +#define KERNEL_TABULATED_HPP + + + +#include "base_kernel.h" +#include + +namespace SPH +{ + template + class KernelTabulated : public Kernel + { + protected: + KernelType *original_kernel_; + int kernel_resolution_; + Real dq_ , delta_q_0_, delta_q_1_, delta_q_2_, delta_q_3_; + StdVec w_1d, w_2d, w_3d; + StdVec dw_1d, dw_2d, dw_3d; + StdVec d2w_1d, d2w_2d, d2w_3d; + + virtual void setBasicParameters() override; + /** interpolation function, Four-point Lagrangian interpolation. */ + Real InterpolationCubic(const StdVec &data, Real q) const { + int location = (int)floor(q / dq_); + int i = location + 1; + Real fraction_1 = q - Real(location)*dq_; //fraction_1 correspond to i + Real fraction_0 = fraction_1 + dq_; //fraction_0 correspond to i-1 + Real fraction_2 = fraction_1 - dq_; //fraction_2 correspond to i+1 + Real fraction_3 = fraction_1 - 2 * dq_; ////fraction_3 correspond to i+2 + + return ((fraction_1 * fraction_2 * fraction_3) / delta_q_0_ * data[i - 1] + + (fraction_0 * fraction_2 * fraction_3) / delta_q_1_ * data[i] + + (fraction_0 * fraction_1 * fraction_3) / delta_q_2_ * data[i + 1] + + (fraction_0 * fraction_1 * fraction_2) / delta_q_3_ * data[i + 2]); + }; + public: + KernelTabulated(int kernel_resolution); + + virtual Real KernelSize() const { return original_kernel_->KernelSize(); }; + + virtual Real W_1D(const Real q) const override; + virtual Real W_2D(const Real q) const override; + virtual Real W_3D(const Real q) const override; + + virtual Real dW_1D(const Real q) const override; + virtual Real dW_2D(const Real q) const override; + virtual Real dW_3D(const Real q) const override; + + virtual Real d2W_1D(const Real q) const override; + virtual Real d2W_2D(const Real q) const override; + virtual Real d2W_3D(const Real q) const override; + }; + //=================================================================================================// + template + KernelTabulated::KernelTabulated(int kernel_resolution) + : Kernel("KernelTabulated"), kernel_resolution_(kernel_resolution) {} + //=================================================================================================// + template + void KernelTabulated::setBasicParameters() + { + original_kernel_ = new KernelType(); + original_kernel_->initialize(h_); + factor_W_1D_ = original_kernel_->FactorW1D(); + factor_W_2D_ = original_kernel_->FactorW2D(); + factor_W_3D_ = original_kernel_->FactorW3D(); + + dq_ = KernelSize() / Real(kernel_resolution_); + for (int i = 0; i < kernel_resolution_ + 4; i++) + { + w_1d.push_back(original_kernel_->W_1D(Real(i - 1)*dq_)); + w_2d.push_back(original_kernel_->W_2D(Real(i - 1)*dq_)); + w_3d.push_back(original_kernel_->W_3D(Real(i - 1)*dq_)); + dw_1d.push_back(original_kernel_->dW_1D(Real(i - 1)*dq_)); + dw_2d.push_back(original_kernel_->dW_2D(Real(i - 1)*dq_)); + dw_3d.push_back(original_kernel_->dW_3D(Real(i - 1)*dq_)); + d2w_1d.push_back(original_kernel_->d2W_1D(Real(i - 1) * dq_)); + d2w_2d.push_back(original_kernel_->d2W_2D(Real(i - 1) * dq_)); + d2w_3d.push_back(original_kernel_->d2W_3D(Real(i - 1) * dq_)); + } + + delta_q_0_ = (-1.0 * dq_) * (-2.0 * dq_) * (-3.0 * dq_); + delta_q_1_ = dq_ * (-1.0 * dq_) * (-2.0 * dq_); + delta_q_2_ = (2.0 * dq_) * dq_ * (-1.0 * dq_); + delta_q_3_ = (3.0 * dq_) * (2.0 * dq_) * dq_; + } + //=================================================================================================// + template + Real KernelTabulated ::W_1D(Real q) const + { + return InterpolationCubic(w_1d, q); + } + //=================================================================================================// + template + Real KernelTabulated::W_2D(Real q) const + { + return InterpolationCubic(w_2d, q); + } + //=================================================================================================// + template + Real KernelTabulated::W_3D(Real q) const + { + return InterpolationCubic(w_3d, q); + } + //=================================================================================================// + template + Real KernelTabulated::dW_1D(Real q) const + { + return InterpolationCubic(dw_1d, q); + } + //=================================================================================================// + template + Real KernelTabulated::dW_2D(Real q) const + { + return InterpolationCubic(dw_2d, q); + } + //=================================================================================================// + template + Real KernelTabulated::dW_3D(Real q) const + { + return InterpolationCubic(dw_3d, q); + } + //=================================================================================================// + template + Real KernelTabulated::d2W_1D(Real q) const + { + return InterpolationCubic(d2w_1d, q); + } + //=================================================================================================// + template + Real KernelTabulated::d2W_2D(Real q) const + { + return InterpolationCubic(d2w_2d, q); + } + //=================================================================================================// + template + Real KernelTabulated::d2W_3D(Real q) const + { + return InterpolationCubic(d2w_3d, q); + } + //=================================================================================================// +} +#endif //KERNEL_TABULATED_HPP \ No newline at end of file diff --git a/SPHINXsys/src/shared/kernels/kernel_wenland_c2.cpp b/SPHINXsys/src/shared/kernels/kernel_wenland_c2.cpp new file mode 100644 index 0000000000..4904a92fa3 --- /dev/null +++ b/SPHINXsys/src/shared/kernels/kernel_wenland_c2.cpp @@ -0,0 +1,67 @@ +/** + * @file kernel_wenland.cpp + * @author Luhui Han, Chi ZHang, Yongchuan Yu and Xiangyu Hu + */ +#include "kernel_wenland_c2.h" + +#include + +namespace SPH +{ + //=================================================================================================// + KernelWendlandC2::KernelWendlandC2() + : Kernel("Wendland2CKernel") {} + //=================================================================================================// + void KernelWendlandC2::setBasicParameters() + { + factor_W_1D_ = inv_h_ * 3.0 / 4.0; + factor_W_2D_ = inv_h_ * inv_h_ * 7.0 / (4.0 * Pi); + factor_W_3D_ = inv_h_ * inv_h_ * inv_h_ * 21.0 / (16.0 * Pi); + } + //=================================================================================================// + Real KernelWendlandC2::W_1D(const Real q) const + { + return powerN(1.0 - 0.5*q, 4) * (1.0 + 2.0 * q); + } + //=================================================================================================// + Real KernelWendlandC2::W_2D(const Real q) const + { + return W_1D(q); + } + //=================================================================================================// + Real KernelWendlandC2::W_3D(const Real q) const + { + return W_2D(q); + } + //=================================================================================================// + Real KernelWendlandC2::dW_1D(const Real q) const + { + return 0.625 * powerN(q - 2.0, 3) * q; + } + //=================================================================================================// + Real KernelWendlandC2::dW_2D(const Real q) const + { + return dW_1D(q); + } + //=================================================================================================// + Real KernelWendlandC2::dW_3D(const Real q) const + { + return dW_2D(q); + } + //=================================================================================================// + Real KernelWendlandC2::d2W_1D(const Real q) const + { + return 1.25 * powerN(q - 2.0, 2) * (2.0 * q - 1.0); + } + //=================================================================================================// + Real KernelWendlandC2::d2W_2D(const Real q) const + { + return d2W_1D(q); + } + //=================================================================================================// + Real KernelWendlandC2::d2W_3D(const Real q) const + { + return d2W_2D(q); + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/kernels/kernel_wenland_c2.h b/SPHINXsys/src/shared/kernels/kernel_wenland_c2.h new file mode 100644 index 0000000000..a4dd23f857 --- /dev/null +++ b/SPHINXsys/src/shared/kernels/kernel_wenland_c2.h @@ -0,0 +1,67 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file kernel_wenland_c2.h +* @brief This is the class for Wenland kernel. +* @details NThis kernel has compact support of 2h. +* The smoothing length h can be variable when variable h functions are applied. +* @author Luhui Han, Chi ZHang and Xiangyu Hu +*/ + + +#ifndef KERNEL_WENLAND_C2_H +#define KERNEL_WENLAND_C2_H + + + +#include "base_kernel.h" + +namespace SPH +{ + /** + * @class KernelWendlandC2 + * @brief Kernel WendlandC2 + */ + class KernelWendlandC2 : public Kernel + { + protected: + virtual void setBasicParameters() override; + public: + KernelWendlandC2(); + + /** Calculates the kernel value for + the given distance of two particles */ + virtual Real W_1D(const Real q) const override; + virtual Real W_2D(const Real q) const override; + virtual Real W_3D(const Real q) const override; + + virtual Real dW_1D(const Real q) const override; + virtual Real dW_2D(const Real q) const override; + virtual Real dW_3D(const Real q) const override; + + virtual Real d2W_1D(const Real q) const override; + virtual Real d2W_2D(const Real q) const override; + virtual Real d2W_3D(const Real q) const override; + }; +} +#endif //KERNEL_WENLAND_C2_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/materials/CMakeLists.txt b/SPHINXsys/src/shared/materials/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/materials/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/materials/all_materials.h b/SPHINXsys/src/shared/materials/all_materials.h new file mode 100644 index 0000000000..fa1d3e70cd --- /dev/null +++ b/SPHINXsys/src/shared/materials/all_materials.h @@ -0,0 +1,16 @@ +/** +* @file all_materials.h +* @brief This is the header file that user code should include to pick up all +* material +* @author Xiangyu Hu and Chi Zhang +*/ + +#pragma once + +#include "weakly_compressible_fluid.h" +#include "elastic_solid.h" +#include "inelastic_solid.h" +#include "complex_solid.h" +#include "complex_solid.hpp" +#include "diffusion_reaction.h" +#include "compressible_fluid.h" \ No newline at end of file diff --git a/SPHINXsys/src/shared/materials/base_material.h b/SPHINXsys/src/shared/materials/base_material.h new file mode 100644 index 0000000000..ff86d4abb1 --- /dev/null +++ b/SPHINXsys/src/shared/materials/base_material.h @@ -0,0 +1,188 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file base_material.h + * @brief This is the base classes of all materials. + * A function in a derived material class returns a value with the inputs + * from the particle data. + * Basically, it is a interface from which + * one can access derived material by dynamic cast. + * Note that the derived material may have position dependent or + * local properties. + * @author Chi Zhang and Xiangyu Hu + */ + + +#ifndef BASE_MATERIAL_H +#define BASE_MATERIAL_H + + + +#include "base_data_package.h" +#include "base_particles.h" + +#include + +namespace SPH { + + class FluidParticles; + class SolidParticles; + + /** @class BaseMaterial + * @brief Base of all materials + * @details Note that the case dependent parameters of the material properties + * will be defined in applications. + */ + class BaseMaterial + { + protected: + std::string material_name_; + std::string parameters_name_; + Real rho0_; /**< reference density. */ + BaseParticles* base_particles_; + XmlEngine reload_material_xml_engine_; + ParticleVariableList reload_local_parameters_; + + virtual void assignDerivedMaterialParameters() {}; + public: + BaseMaterial() : material_name_("BaseMaterial"), parameters_name_("LocalParameters"), + rho0_(1.0), base_particles_(nullptr), + reload_material_xml_engine_("xml_material", "material_paramaters") {}; + virtual ~BaseMaterial() {}; + + /** This will be called in BaseParticle constructor + * and is important because particles are not defined in SPHBody constructor. + * For a composite material, i.e. there is a material pointer with another material, + * one need assign the base particle to that material too. */ + virtual void assignBaseParticles(BaseParticles* base_particles) + { + base_particles_ = base_particles; + }; + std::string MaterialName() { return material_name_; } + std::string LocalParametersName() { return parameters_name_; } + Real ReferenceDensity() { return rho0_; }; + + virtual void writeToXmlForReloadLocalParameters(std::string& filefullpath) + { + std::cout << "\n Material properties writing. " << std::endl; + size_t total_real_particles = base_particles_->total_real_particles_; + ParticleData& all_particle_data = base_particles_->all_particle_data_; + base_particles_->resizeXmlDocForParticles(reload_material_xml_engine_); + WriteAParticleVariableToXml + write_variable_to_xml(reload_material_xml_engine_, total_real_particles); + loopParticleData(all_particle_data, reload_local_parameters_, write_variable_to_xml); + reload_material_xml_engine_.writeToXmlFile(filefullpath); + std::cout << "\n Material properties writing finished. " << std::endl; + }; + + virtual void readFromXmlForLocalParameters(std::string& filefullpath) + { + reload_material_xml_engine_.loadXmlFile(filefullpath); + size_t total_real_particles = base_particles_->total_real_particles_; + ParticleData& all_particle_data = base_particles_->all_particle_data_; + ReadAParticleVariableFromXml + read_variable_from_xml(reload_material_xml_engine_, total_real_particles); + loopParticleData(all_particle_data, reload_local_parameters_, read_variable_from_xml); + + if (total_real_particles != reload_material_xml_engine_.SizeOfXmlDoc()) + { + std::cout << "\n Error: reload material properties does not match!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + else + { + std::cout << "\n Material properties reading finished." << std::endl; + } + }; + + virtual BaseMaterial* ThisObjectPtr() { return this; }; + }; + + /** @class Fluid + * @brief Base class of all fluids + */ + class Fluid : public BaseMaterial + { + protected: + Real c0_, mu_; /**< reference sound speed, viscosity. */ + FluidParticles* fluid_particles_; + + virtual void assignDerivedMaterialParameters() override + { + BaseMaterial::assignDerivedMaterialParameters(); + }; + public: + Fluid() : BaseMaterial(), c0_(1.0), mu_(0.0), + fluid_particles_(nullptr) { + material_name_ = "Fluid"; + }; + virtual ~Fluid() {}; + + void assignFluidParticles(FluidParticles* fluid_particles) + { + fluid_particles_ = fluid_particles; + }; + + Real ReferenceSoundSpeed() { return c0_; }; + Real ReferenceViscosity() { return mu_; }; + virtual Real getPressure(Real rho) = 0; + virtual Real getPressure(Real rho, Real rho_e) { return getPressure(rho); }; + virtual Real DensityFromPressure(Real p) = 0; + virtual Real getSoundSpeed(Real p = 0.0, Real rho = 1.0) = 0; + virtual Fluid* ThisObjectPtr() override { return this; }; + }; + + /** @class Solid + * @brief Base class of all solid materials + */ + class Solid : public BaseMaterial + { + public: + Solid() : BaseMaterial(), contact_stiffness_(1.0), + contact_friction_(0.0), solid_particles_(nullptr) + { + material_name_ = "Solid"; + }; + virtual ~Solid() {}; + + void assignSolidParticles(SolidParticles* solid_particles) + { + solid_particles_ = solid_particles; + }; + + Real ContactFriction() { return contact_friction_; }; + Real ContactStiffness() { return contact_stiffness_; }; + virtual Solid* ThisObjectPtr() override { return this; }; + protected: + Real contact_stiffness_; /**< contact-force stiffness related to bulk modulus*/ + Real contact_friction_; /**< friction property mimic fluid viscosity*/ + SolidParticles* solid_particles_; + + virtual void assignDerivedMaterialParameters() override + { + BaseMaterial::assignDerivedMaterialParameters(); + }; + }; +} +#endif //BASE_MATERIAL_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/materials/complex_solid.h b/SPHINXsys/src/shared/materials/complex_solid.h new file mode 100644 index 0000000000..610f384320 --- /dev/null +++ b/SPHINXsys/src/shared/materials/complex_solid.h @@ -0,0 +1,56 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file complex_solid.h +* @brief These are classes for define complex solid materials. +* @author Xiangyu Hu and Chi Zhang +*/ +#pragma once + +#include "elastic_solid.h" + +namespace SPH { + + class ActiveMuscleParticles; + + /** + * @class ActiveMuscle + * @brief Here, the active reponse is considered. + */ + template + class ActiveMuscle : public MuscleType + { + protected: + ActiveMuscleParticles* active_muscle_particles_; + + virtual void assignDerivedMaterialParameters() override; + public: + ActiveMuscle(); + virtual ~ActiveMuscle() {}; + + void assignActiveMuscleParticles(ActiveMuscleParticles* active_muscle_particles); + /** compute the stress through Constitutive relation. */ + virtual Matd ConstitutiveRelation(Matd& deformation, size_t index_i) override; + virtual ActiveMuscle* ThisObjectPtr() override { return this; }; + }; +} diff --git a/SPHINXsys/src/shared/materials/complex_solid.hpp b/SPHINXsys/src/shared/materials/complex_solid.hpp new file mode 100644 index 0000000000..a7ca769550 --- /dev/null +++ b/SPHINXsys/src/shared/materials/complex_solid.hpp @@ -0,0 +1,41 @@ +/** +* @file complex_solid.hpp +* @brief These are implementation of the classes for complex solids. +* @author Xiangyu Hu and Chi Zhang +*/ +#pragma once + +#include "complex_solid.h" +#include "solid_particles.h" + +using namespace std; + +namespace SPH { + //=============================================================================================// + template + ActiveMuscle::ActiveMuscle() : MuscleType(), active_muscle_particles_(nullptr) + { + MuscleType::material_name_ = "ActiveMuscle"; + } + //=============================================================================================// + template + void ActiveMuscle::assignDerivedMaterialParameters() + { + MuscleType::assignDerivedMaterialParameters(); + } + //=============================================================================================// + template + void ActiveMuscle:: + assignActiveMuscleParticles(ActiveMuscleParticles* active_muscle_particles) + { + active_muscle_particles_ = active_muscle_particles; + } + //=============================================================================================// + template + Matd ActiveMuscle::ConstitutiveRelation(Matd& deformation, size_t index_i) + { + return MuscleType::ConstitutiveRelation(deformation, index_i) + + active_muscle_particles_->active_contraction_stress_[index_i] * MuscleType::MuscleFiberDirection(index_i); + } + //=============================================================================================// + } diff --git a/SPHINXsys/src/shared/materials/compressible_fluid.cpp b/SPHINXsys/src/shared/materials/compressible_fluid.cpp new file mode 100644 index 0000000000..de0b4d3be7 --- /dev/null +++ b/SPHINXsys/src/shared/materials/compressible_fluid.cpp @@ -0,0 +1,22 @@ +/** + * @file compressible_fluid.cpp + * @author Luhui Han, Chi ZHang ,Xiangyu Hu and Zhentong Wang + */ + +#include "compressible_fluid.h" + +using namespace std; + +namespace SPH { + //===============================================================// + Real CompressibleFluid::getPressure(Real rho, Real rho_e) + { + return rho_e * (gamma_ - 1.0); + } + //===============================================================// + Real CompressibleFluid::getSoundSpeed(Real p, Real rho) + { + return sqrt(gamma_ * p / rho); + } + //===============================================================// +} diff --git a/SPHINXsys/src/shared/materials/compressible_fluid.h b/SPHINXsys/src/shared/materials/compressible_fluid.h new file mode 100644 index 0000000000..cbdbbe9bc0 --- /dev/null +++ b/SPHINXsys/src/shared/materials/compressible_fluid.h @@ -0,0 +1,70 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file compressible_fluid.h + * @brief Describe the compressible fluid which is used + * model compressible fluids. Here, we have ideal gas equation of states. + * @author Xiangyu Hu, Luhui Han, Chi Zhang and Zhentong Wang + */ + +#pragma once + +#include "base_material.h" + +namespace SPH { + + class CompressibleFluidParticles; + + /** + * @class CompressibleFluid + * @brief Ideal gas equation of state (EOS). + */ + class CompressibleFluid : public Fluid + { + protected: + Real gamma_; /** heat capacity ratio */ + CompressibleFluidParticles * compressible_fluid_particles_; + + virtual void assignDerivedMaterialParameters() override + { + Fluid::assignDerivedMaterialParameters(); + }; + public: + explicit CompressibleFluid() : Fluid(), gamma_(1.0) + { + material_name_ = "CompressibleFluid"; + }; + virtual ~CompressibleFluid() {}; + + void assignCompressibleFluidParticles(CompressibleFluidParticles* compressible_fluid_particles) + { + compressible_fluid_particles_ = compressible_fluid_particles; + }; + Real HeatCapacityRatio() { return gamma_; }; + virtual Real getPressure(Real rho, Real rho_e) override; + virtual Real getPressure(Real rho) override { return 0.0; }; + virtual Real DensityFromPressure(Real p) override { return 0.0; }; + virtual Real getSoundSpeed(Real p, Real rho) override; + virtual CompressibleFluid* ThisObjectPtr() override { return this; }; + }; +} diff --git a/SPHINXsys/src/shared/materials/diffusion_reaction.cpp b/SPHINXsys/src/shared/materials/diffusion_reaction.cpp new file mode 100644 index 0000000000..0777d523ec --- /dev/null +++ b/SPHINXsys/src/shared/materials/diffusion_reaction.cpp @@ -0,0 +1,149 @@ +/** + * @file diffusion_reaction.cpp + * @brief These are classes for diffusion and reaction properties + * @author Chi Zhang and Xiangyu Hu + */ + +#include "diffusion_reaction.h" + +#include "diffusion_reaction_particles.h" + +namespace SPH +{ +//=================================================================================================// + void DirectionalDiffusion::initializeDirectionalDiffusivity(Real diff_cf, Real bias_diff_cf, Vecd bias_direction) + { + bias_diff_cf_ = bias_diff_cf; + bias_direction_ = bias_direction; + Matd diff_i = diff_cf_* Matd(1.0) + + bias_diff_cf_ * SimTK::outer(bias_direction_, bias_direction_); + transformed_diffusivity_ = inverseCholeskyDecomposition(diff_i); + }; + //=================================================================================================// + void LocalDirectionalDiffusion::assignBaseParticles(BaseParticles* base_particles) + { + DirectionalDiffusion::assignBaseParticles(base_particles); + initializeFiberDirection(); + }; + //=================================================================================================// + void LocalDirectionalDiffusion::initializeFiberDirection() + { + base_particles_->registerAVariable(local_bias_direction_, "Fiber"); + base_particles_->addAVariableNameToList(reload_local_parameters_, "Fiber"); + } + //=================================================================================================// + void LocalDirectionalDiffusion::readFromXmlForLocalParameters(std::string& filefullpath) + { + BaseMaterial::readFromXmlForLocalParameters(filefullpath); + size_t total_real_particles = base_particles_->total_real_particles_; + for (size_t i = 0; i != total_real_particles; i++) + { + Matd diff_i = diff_cf_ * Matd(1.0) + + bias_diff_cf_ * SimTK::outer(local_bias_direction_[i], local_bias_direction_[i]); + local_transformed_diffusivity_.push_back(inverseCholeskyDecomposition(diff_i)); + } + std::cout << "\n Local diffusion parameters setup finished " << std::endl; + }; + //=================================================================================================// + void ElectroPhysiologyReaction::initializeElectroPhysiologyReaction(size_t voltage, size_t gate_variable, + size_t active_contraction_stress) + { + voltage_ = voltage; + gate_variable_ = gate_variable; + active_contraction_stress_ = active_contraction_stress; + + reactive_species_.push_back(voltage); + reactive_species_.push_back(gate_variable); + reactive_species_.push_back(active_contraction_stress); + + get_production_rates_.push_back(std::bind(&ElectroPhysiologyReaction::getProductionRateIonicCurrent, this, _1, _2)); + get_production_rates_.push_back(std::bind(&ElectroPhysiologyReaction::getProductionRateGateVariable, this, _1, _2)); + get_production_rates_.push_back(std::bind(&ElectroPhysiologyReaction::getProductionActiveContractionStress, this, _1, _2)); + + get_loss_rates_.push_back(std::bind(&ElectroPhysiologyReaction::getLossRateIonicCurrent, this, _1, _2)); + get_loss_rates_.push_back(std::bind(&ElectroPhysiologyReaction::getLossRateGateVariable, this, _1, _2)); + get_loss_rates_.push_back(std::bind(&ElectroPhysiologyReaction::getLossRateActiveContractionStress, this, _1, _2)); + }; +//=================================================================================================// + Real ElectroPhysiologyReaction:: + getProductionActiveContractionStress(StdVec>& species, size_t particle_i) + { + Real voltage_dim = species[voltage_][particle_i] * 100.0 - 80.0; + Real factor = 0.1 + (1.0 - 0.1) * exp(-exp(-voltage_dim)); + return factor * k_a_ * (voltage_dim + 80.0); + } +//=================================================================================================// + Real ElectroPhysiologyReaction:: + getLossRateActiveContractionStress(StdVec>& species, size_t particle_i) + { + Real voltage_dim = species[voltage_][particle_i] * 100.0 - 80.0; + return 0.1 + (1.0 - 0.1) * exp(-exp(-voltage_dim)); + } +//=================================================================================================// + Real AlievPanfilowModel:: + getProductionRateIonicCurrent(StdVec>& species, size_t particle_i) + { + Real voltage = species[voltage_][particle_i]; + return - k_ * voltage * (voltage * voltage - a_ * voltage - voltage) / c_m_; + } +//=================================================================================================// + Real AlievPanfilowModel:: + getLossRateIonicCurrent(StdVec>& species, size_t particle_i) + { + Real gate_variable = species[gate_variable_][particle_i]; + return (k_ * a_ + gate_variable) / c_m_; + } +//=================================================================================================// + Real AlievPanfilowModel:: + getProductionRateGateVariable(StdVec>& species, size_t particle_i) + { + Real voltage = species[voltage_][particle_i]; + Real gate_variable = species[gate_variable_][particle_i]; + Real temp = epsilon_ + mu_1_ * gate_variable / (mu_2_ + voltage + Eps); + return - temp * k_ * voltage * (voltage - b_ - 1.0); + } +//=================================================================================================// + Real AlievPanfilowModel:: + getLossRateGateVariable(StdVec>& species, size_t particle_i) + { + Real voltage = species[voltage_][particle_i]; + Real gate_variable = species[gate_variable_][particle_i]; + return epsilon_ + mu_1_ * gate_variable / (mu_2_ + voltage + Eps); + } +//=================================================================================================// + MonoFieldElectroPhysiology::MonoFieldElectroPhysiology(ElectroPhysiologyReaction* electro_physiology_reaction) + : DiffusionReactionMaterial(electro_physiology_reaction), diff_cf_(1.0), bias_diff_cf_(0.0), + bias_direction_(FirstAxisVector(Vecd(0))) + { + material_name_ = "MonoFieldElectroPhysiology"; + insertASpecies("Voltage"); + insertASpecies("GateVariable"); + insertASpecies("ActiveContractionStress"); + + electro_physiology_reaction->initializeElectroPhysiologyReaction(species_indexes_map_["Voltage"], + species_indexes_map_["GateVariable"], species_indexes_map_["ActiveContractionStress"]); + }; +//=================================================================================================// + void MonoFieldElectroPhysiology::initializeDiffusion() + { + DirectionalDiffusion* voltage_diffusion + = new DirectionalDiffusion(species_indexes_map_["Voltage"], species_indexes_map_["Voltage"], + diff_cf_, bias_diff_cf_, bias_direction_); + species_diffusion_.push_back(voltage_diffusion); + } + //=================================================================================================// + void LocalMonoFieldElectroPhysiology::initializeDiffusion() + { + LocalDirectionalDiffusion* voltage_diffusion + = new LocalDirectionalDiffusion(species_indexes_map_["Voltage"], species_indexes_map_["Voltage"], + diff_cf_, bias_diff_cf_, bias_direction_); + species_diffusion_.push_back(voltage_diffusion); + } +//=================================================================================================// + void LocalMonoFieldElectroPhysiology::readFromXmlForLocalParameters(std::string& filefullpath) + { + species_diffusion_[0]->readFromXmlForLocalParameters(filefullpath); + } +//=================================================================================================// +} +//=================================================================================================// diff --git a/SPHINXsys/src/shared/materials/diffusion_reaction.h b/SPHINXsys/src/shared/materials/diffusion_reaction.h new file mode 100644 index 0000000000..ebed5c81d1 --- /dev/null +++ b/SPHINXsys/src/shared/materials/diffusion_reaction.h @@ -0,0 +1,338 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file diffussion_reaction.h + * @brief Describe the diffusive and reaction in which + * the dynamics is characterized by diffusion equation and reactive source terms. + * Typical physical processes are diffusion, heat conduction + * and chemical and biological reactions. + * @author Xiangyu Hu, Chi Zhang + */ + +#ifndef DIFFUSION_REACTION_H +#define DIFFUSION_REACTION_H + + + +#include "base_material.h" +#include "solid_particles.h" + +#include +#include +using namespace std::placeholders; + +namespace SPH +{ + template + class DiffusionReactionParticles; + class ElectroPhysiologyParticles; + /** + * @class BaseDiffusion + * @brief diffusion property abstract base class. + */ + class BaseDiffusion : public BaseMaterial + { + public: + BaseDiffusion(size_t diffusion_species_index, size_t gradient_species_index) : + BaseMaterial(), diffusion_species_index_(diffusion_species_index), + gradient_species_index_(gradient_species_index) {}; + virtual ~BaseDiffusion() {}; + + size_t diffusion_species_index_; + size_t gradient_species_index_; + + virtual Real getReferenceDiffusivity() = 0; + virtual Real getInterParticleDiffusionCoff(size_t particle_i, size_t particle_j, Vecd& direction_from_j_to_i) = 0; + }; + + /** + * @class IsotropicDiffusion + * @brief isotropic diffusion property. + */ + class IsotropicDiffusion : public BaseDiffusion + { + protected: + Real diff_cf_; /**< diffusion coefficient. */ + + public: + IsotropicDiffusion(size_t diffusion_species_index, size_t gradient_species_index, + Real diff_cf = 1.0) : BaseDiffusion(diffusion_species_index, gradient_species_index), + diff_cf_(diff_cf) {}; + virtual ~IsotropicDiffusion() {}; + + virtual Real getReferenceDiffusivity() override { return diff_cf_; }; + virtual Real getInterParticleDiffusionCoff(size_t particle_i, size_t particle_j, Vecd& direction_from_j_to_i) override + { + return diff_cf_; + }; + }; + + /** + * @class DirectionalDiffusion + * @brief Diffussion is biased along a specific direction. + */ + class DirectionalDiffusion : public IsotropicDiffusion + { + protected: + Vecd bias_direction_; /**< Reference bias direction. */ + Real bias_diff_cf_; /**< The bias diffusion coefficient along the fiber direction. */ + Matd transformed_diffusivity_; /**< The transformed diffusivity with inverse Cholesky decomposition. */ + + void initializeDirectionalDiffusivity(Real diff_cf, Real bias_diff_cf, Vecd bias_direction); + public: + DirectionalDiffusion(size_t diffusion_species_index, size_t gradient_species_index, + Real diff_cf, Real bias_diff_cf, Vecd bias_direction) : + IsotropicDiffusion(diffusion_species_index, gradient_species_index, diff_cf), + bias_direction_(bias_direction), bias_diff_cf_(bias_diff_cf), + transformed_diffusivity_(1.0) + { + initializeDirectionalDiffusivity(diff_cf, bias_diff_cf, bias_direction); + }; + virtual ~DirectionalDiffusion() {}; + + virtual Real getReferenceDiffusivity() override + { + return SMAX(diff_cf_, diff_cf_ + bias_diff_cf_); + }; + + virtual Real getInterParticleDiffusionCoff(size_t particle_index_i, + size_t particle_index_j, Vecd& inter_particle_direction) override + { + Vecd grad_ij = transformed_diffusivity_ * inter_particle_direction; + return 1.0 / grad_ij.scalarNormSqr(); + }; + }; + + /** + * @class LocalDirectionalDiffusion + * @brief Diffusion is biased along a specific direction. + */ + class LocalDirectionalDiffusion : public DirectionalDiffusion + { + protected: + StdLargeVec local_bias_direction_; + StdLargeVec local_transformed_diffusivity_; + + void initializeFiberDirection(); + public: + LocalDirectionalDiffusion(size_t diffusion_species_index, size_t gradient_species_index, + Real diff_cf, Real bias_diff_cf, Vecd bias_direction) + : DirectionalDiffusion(diffusion_species_index, gradient_species_index, diff_cf, bias_diff_cf, bias_direction) {}; + virtual ~LocalDirectionalDiffusion() {}; + virtual Real getInterParticleDiffusionCoff(size_t particle_index_i, size_t particle_index_j, Vecd& inter_particle_direction) override + { + Matd trans_diffusivity = getAverageValue(local_transformed_diffusivity_[particle_index_i], local_transformed_diffusivity_[particle_index_j]); + Vecd grad_ij = trans_diffusivity * inter_particle_direction; + return 1.0 / grad_ij.scalarNormSqr(); + }; + virtual void assignBaseParticles(BaseParticles* base_particles); + virtual void readFromXmlForLocalParameters(std::string& filefullpath) override; + }; + + /** Reaction functor . */ + typedef std::function>&, size_t particle_i)> ReactionFunctor; + /** + * @class BaseReactionModel + * @brief Base class for all reaction models. + */ + class BaseReactionModel + { + protected: + virtual void assignDerivedReactionParameters() = 0; + public: + BaseReactionModel() {}; + virtual ~BaseReactionModel() {}; + + IndexVector reactive_species_; + StdVec get_production_rates_; + StdVec get_loss_rates_; + }; + + /** + * @class AlievPanfilowModel + * @brief The simplest Electrophysiology Reaction model, + * which reduces the complex of array of ion currents to two variables that + * describe excitation and recovery. + */ + class ElectroPhysiologyReaction : public BaseReactionModel + { + protected: + Real k_a_; + size_t voltage_; + size_t gate_variable_; + size_t active_contraction_stress_; + + virtual Real getProductionRateIonicCurrent(StdVec>& species, size_t particle_i) = 0; + virtual Real getLossRateIonicCurrent(StdVec>& species, size_t particle_i) = 0; + virtual Real getProductionRateGateVariable(StdVec>& species, size_t particle_i) = 0; + virtual Real getLossRateGateVariable(StdVec>& species, size_t particle_i) = 0; + virtual Real getProductionActiveContractionStress(StdVec>& species, size_t particle_i); + virtual Real getLossRateActiveContractionStress(StdVec>& species, size_t particle_i); + virtual void assignDerivedReactionParameters() override {}; + public: + ElectroPhysiologyReaction() : BaseReactionModel(), k_a_(1.0), + voltage_(0), gate_variable_(1), active_contraction_stress_(2) {}; + virtual ~ElectroPhysiologyReaction() {}; + void initializeElectroPhysiologyReaction(size_t voltage, + size_t gate_variable, size_t active_contraction_stress); + }; + + class AlievPanfilowModel : public ElectroPhysiologyReaction + { + protected: + /** Parameters for two variable cell model. */ + Real k_, a_, b_, mu_1_, mu_2_, epsilon_, c_m_; + + virtual Real getProductionRateIonicCurrent(StdVec>& species, size_t particle_i) override; + virtual Real getLossRateIonicCurrent(StdVec>& species, size_t particle_i) override; + virtual Real getProductionRateGateVariable(StdVec>& species, size_t particle_i) override; + virtual Real getLossRateGateVariable(StdVec>& species, size_t particle_i) override; + virtual void assignDerivedReactionParameters() override + { + ElectroPhysiologyReaction::assignDerivedReactionParameters(); + }; + public: + AlievPanfilowModel() : ElectroPhysiologyReaction(), + k_(0.0), a_(0.0), b_(0.0), mu_1_(0.0), mu_2_(0.0), + epsilon_(0.0), c_m_(0.0) {}; + virtual ~AlievPanfilowModel() {}; + }; + + /** + * @class DiffusionReactionMaterial + * @brief Complex material for diffusion or/and reactions. + */ + template + class DiffusionReactionMaterial : public BaseMaterialType + { + protected: + + size_t number_of_species_; + size_t number_of_diffusion_species_; + std::map species_indexes_map_; + StdVec species_diffusion_; + BaseReactionModel* species_reaction_; + DiffusionReactionParticles* diffusion_reaction_particles_; + + virtual void assignDerivedMaterialParameters() override + { + BaseMaterialType::assignDerivedMaterialParameters(); + }; + + void insertASpecies(std::string species_name) + { + species_indexes_map_.insert(make_pair(species_name, number_of_species_)); + number_of_species_++; + }; + public: + /** Constructor for material only with diffusion. */ + DiffusionReactionMaterial() + : BaseMaterialType(), number_of_species_(0), species_reaction_(nullptr) + { + BaseMaterialType::material_name_ = "DiffusionMaterial"; + }; + /** Constructor for material with diffusion and reaction. */ + DiffusionReactionMaterial(BaseReactionModel* species_reaction) + : BaseMaterialType(), number_of_species_(0), species_reaction_(species_reaction) + { + BaseMaterialType::material_name_ = "DiffusionReactionMaterial"; + }; + virtual ~DiffusionReactionMaterial() {}; + + size_t NumberOfSpecies() { return number_of_species_; }; + size_t NumberOfSpeciesDiffusion() { return species_diffusion_.size(); }; + StdVec SpeciesDiffusion() { return species_diffusion_; }; + BaseReactionModel* SpeciesReaction() { return species_reaction_; }; + std::map SpeciesIndexMap() { return species_indexes_map_; }; + void assignDiffusionReactionParticles(DiffusionReactionParticles* diffusion_reaction_particles) + { + diffusion_reaction_particles_ = diffusion_reaction_particles; + for (size_t k = 0; k < species_diffusion_.size(); ++k) + species_diffusion_[k]->assignBaseParticles(diffusion_reaction_particles); + }; + /** + * @brief Get diffusion time step size. Here, I follow the reference: + * https://www.uni-muenster.de/imperia/md/content/physik_tp/lectures/ws2016-2017/num_methods_i/heat.pdf + */ + Real getDiffusionTimeStepSize(Real smoothing_length) + { + Real diff_coff_max = 0.0; + for (size_t k = 0; k < species_diffusion_.size(); ++k) + diff_coff_max = SMAX(diff_coff_max, species_diffusion_[k]->getReferenceDiffusivity()); + Real dimension = Real(Vecd(0).size()); + return 0.5 * smoothing_length * smoothing_length / diff_coff_max / dimension; + }; + /** Initialize diffusion material. */ + virtual void initializeDiffusion() = 0; + virtual DiffusionReactionMaterial* + ThisObjectPtr() override { return this; }; + }; + + /** + * @class MonoFieldElectroPhysiology + * @brief material class for electro_physiology. + */ + class MonoFieldElectroPhysiology + : public DiffusionReactionMaterial + { + protected: + Real diff_cf_; + Real bias_diff_cf_; + Vecd bias_direction_; + + virtual void assignDerivedMaterialParameters() override + { + DiffusionReactionMaterial::assignDerivedMaterialParameters(); + }; + public: + MonoFieldElectroPhysiology(ElectroPhysiologyReaction* electro_physiology_reaction); + virtual ~MonoFieldElectroPhysiology() {}; + + virtual void initializeDiffusion() override; + }; + + /** + * @class LocalMonoFieldElectroPhysiology + * @brief material class for electro_physiology with locally oriented fibers. + */ + class LocalMonoFieldElectroPhysiology + : public MonoFieldElectroPhysiology + { + protected: + virtual void assignDerivedMaterialParameters() override + { + MonoFieldElectroPhysiology::assignDerivedMaterialParameters(); + }; + public: + LocalMonoFieldElectroPhysiology(ElectroPhysiologyReaction* electro_physiology_reaction) + :MonoFieldElectroPhysiology(electro_physiology_reaction) { + MonoFieldElectroPhysiology::material_name_ = "LocalMonoFieldElectroPhysiology"; + }; + virtual ~LocalMonoFieldElectroPhysiology() {}; + + virtual void initializeDiffusion() override; + virtual void readFromXmlForLocalParameters(std::string& filefullpath) override; + }; +} +#endif //DIFFUSION_REACTION_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/materials/elastic_solid.cpp b/SPHINXsys/src/shared/materials/elastic_solid.cpp new file mode 100644 index 0000000000..ca52b2b286 --- /dev/null +++ b/SPHINXsys/src/shared/materials/elastic_solid.cpp @@ -0,0 +1,269 @@ +/** + * @file elastic_solid.cpp + * @author Chi Zhang and Xiangyu Hu + */ + +#include "elastic_solid.h" + +#include "base_body.h" +#include "solid_particles.h" + +namespace SPH { + //=================================================================================================// + void ElasticSolid::assignElasticSolidParticles(ElasticSolidParticles* elastic_particles) + { + elastic_particles_ = elastic_particles; + } + //=================================================================================================// + void ElasticSolid::assignDerivedMaterialParameters() + { + Solid::assignDerivedMaterialParameters(); + setReferenceSoundSpeed(); + setTensileWaveSpeed(); + setShearWaveSpeed(); + setYoungsModulus(); + setShearModulus(); + setBulkModulus(); + setPoissonRatio(); + setContactStiffness(); + } + //=================================================================================================// + Matd ElasticSolid::NumericalDampingRightCauchy(Matd& F, Matd& dF_dt, Real smoothing_length, size_t particle_index_i) + { + Matd strain_rate = 0.5 * (~dF_dt * F + ~F * dF_dt); + Matd normal_rate = getDiagonal(strain_rate); + return 0.5 * rho0_ * (cs0_ * (strain_rate - normal_rate) + c0_ * normal_rate) * smoothing_length; + } + //=================================================================================================// + Matd ElasticSolid::NumericalDampingLeftCauchy(Matd& F, Matd& dF_dt, Real smoothing_length, size_t particle_index_i) + { + Matd strain_rate = 0.5 * (dF_dt * ~F + F * ~dF_dt); + Matd normal_rate = getDiagonal(strain_rate); + return 0.5 * rho0_ * (cs0_ * (strain_rate - normal_rate) + c0_ * normal_rate) * smoothing_length; + } + //=================================================================================================// + Real ElasticSolid::NumericalDamping(Real dE_dt_ij, Real smoothing_length) + { + return 0.5 * rho0_ * c0_ * dE_dt_ij * smoothing_length; + } + //=================================================================================================// + Real ElasticSolid::NumericalViscosity(Real smoothing_length) + { + return 0.5 * rho0_ * c0_ * smoothing_length; + } + //=================================================================================================// + Matd ElasticSolid::DeviatoricKirchhoff(const Matd& deviatoric_be) + { + return G0_ * deviatoric_be; + } + //=================================================================================================// + Real LinearElasticSolid::getBulkModulus() + { + return youngs_modulus_ / 3.0 / (1.0 - 2.0 * poisson_ratio_); + } + //=================================================================================================// + Real LinearElasticSolid::getShearModulus() + { + return 0.5 * youngs_modulus_ / (1.0 + poisson_ratio_); + } + //=================================================================================================// + Real LinearElasticSolid::getLambda() + { + return nu_ * youngs_modulus_ / (1.0 + poisson_ratio_) / (1.0 - 2.0 * poisson_ratio_); + } + //=================================================================================================// + void LinearElasticSolid::setReferenceSoundSpeed() + { + c0_ = sqrt(getBulkModulus() / rho0_); + } + //=================================================================================================// + void LinearElasticSolid::setTensileWaveSpeed() + { + ct0_ = sqrt(youngs_modulus_ / rho0_); + } + //=================================================================================================// + void LinearElasticSolid::setShearWaveSpeed() + { + cs0_ = sqrt(getShearModulus() / rho0_); + } + //=================================================================================================// + void LinearElasticSolid::setShearModulus() + { + G0_ = getShearModulus(); + } + //=================================================================================================// + void LinearElasticSolid::setBulkModulus() + { + K0_ = getBulkModulus(); + } + //=================================================================================================// + void LinearElasticSolid::assignDerivedMaterialParameters() + { + ElasticSolid::assignDerivedMaterialParameters(); + lambda0_ = getLambda(); + std::cout << "The speed of sound: " << c0_ << std::endl; + std::cout << "The Lambda: " << lambda0_ << std::endl; + std::cout << "Contact stiffness: " << contact_stiffness_ << std::endl; + }; + //=================================================================================================// + Matd LinearElasticSolid::ConstitutiveRelation(Matd& F, size_t particle_index_i) + { + Matd strain = 0.5 * (~F * F - Matd(1.0)); + Matd sigmaPK2 = lambda0_ * strain.trace() * Matd(1.0) + 2.0 * G0_ * strain; + return sigmaPK2; + } + //=================================================================================================// + Real LinearElasticSolid::VolumetricKirchhoff(Real J) + { + return K0_ * J * (J - 1); + } + //=================================================================================================// + Matd NeoHookeanSolid::ConstitutiveRelation(Matd& F, size_t particle_index_i) + { + Matd right_cauchy = ~F * F; + Matd sigmaPK2 = G0_ * Matd(1.0) + (lambda0_ * log(det(F)) - G0_) * inverse(right_cauchy); + return sigmaPK2; + } + //=================================================================================================// + Real NeoHookeanSolid::VolumetricKirchhoff(Real J) + { + return 0.5 * K0_ * (J * J - 1); + } + //=================================================================================================// + Matd FeneNeoHookeanSolid::ConstitutiveRelation(Matd& F, size_t particle_index_i) + { + Matd right_cauchy = ~F * F; + Matd strain = 0.5 * (right_cauchy - Matd(1.0)); + Matd sigmaPK2 = G0_ / (1.0 - 2.0 * strain.trace() / j1_m_) * Matd(1.0) + + (lambda0_ * log(det(F)) - G0_) * inverse(right_cauchy); + return sigmaPK2; + } + //=================================================================================================// + Real Muscle::getShearModulus() + { + return a0_[0] * b0_[0] + 2.0 * a0_[1] * b0_[1] + 2.0 * a0_[2] * b0_[2] + a0_[3] * b0_[3]; + } + //=================================================================================================// + Real Muscle::getPoissonRatio() + { + return 0.5 * (3.0 * bulk_modulus_ - 2.0 * getShearModulus()) / (3.0 * bulk_modulus_ + getShearModulus()); + } + //=================================================================================================// + Real Muscle::getLambda() + { + return bulk_modulus_ - 2.0 * getShearModulus() / 3.0; + } + //=================================================================================================// + Real Muscle::getYoungsModulus() + { + return 3.0 * bulk_modulus_ * (1.0 - 2.0 * getPoissonRatio()); + } + //=================================================================================================// + void Muscle::setReferenceSoundSpeed() + { + c0_ = sqrt(bulk_modulus_ / rho0_); + } + //=================================================================================================// + void Muscle::setTensileWaveSpeed() + { + ct0_ = sqrt(getYoungsModulus() / rho0_); + } + //=================================================================================================// + void Muscle::setShearWaveSpeed() + { + cs0_ = sqrt(getShearModulus() / rho0_); + } + //=================================================================================================// + void Muscle::setYoungsModulus() + { + E0_ = getYoungsModulus(); + } + //=================================================================================================// + void Muscle::setShearModulus() + { + G0_ = getShearModulus(); + } + //=================================================================================================// + void Muscle::setPoissonRatio() + { + nu_ = getPoissonRatio(); + } + //=================================================================================================// + void Muscle::assignDerivedMaterialParameters() + { + ElasticSolid::assignDerivedMaterialParameters(); + lambda0_ = getLambda(); + f0f0_ = SimTK::outer(f0_, f0_); + f0s0_ = SimTK::outer(f0_, s0_); + s0s0_ = SimTK::outer(s0_, s0_); + std::cout << "The speed of sound: " << c0_ << std::endl; + std::cout << "The Lambda: " << lambda0_ << std::endl; + std::cout << "Contact stiffness: " << contact_stiffness_ << std::endl; + } + //=================================================================================================// + Matd Muscle::ConstitutiveRelation(Matd& F, size_t i) + { + Matd right_cauchy = ~F * F; + Real I_ff_1 = SimTK::dot(right_cauchy * f0_, f0_) - 1.0; + Real I_ss_1 = SimTK::dot(right_cauchy * s0_, s0_) - 1.0; + Real I_fs = SimTK::dot(right_cauchy * f0_, s0_); + Real ln_J = log(det(F)); + Real I_1_1 = right_cauchy.trace() - Real(f0_.size()); + Matd sigmaPK2 = a0_[0] * exp(b0_[0] * I_1_1) * Matd(1.0) + + (lambda0_ * ln_J - a0_[0]) * inverse(right_cauchy) + + 2.0 * a0_[1] * I_ff_1 * exp(b0_[1] * I_ff_1 * I_ff_1) * f0f0_ + + 2.0 * a0_[2] * I_ss_1 * exp(b0_[2] * I_ss_1 * I_ss_1) * s0s0_ + + a0_[3] * I_fs * exp(b0_[3] * I_fs * I_fs) * f0s0_; + + return sigmaPK2; + } + //=================================================================================================// + Real Muscle::VolumetricKirchhoff(Real J) + { + return K0_ * J * (J - 1); + } + //=================================================================================================// + Matd LocallyOrthotropicMuscle::ConstitutiveRelation(Matd& F, size_t i) + { + Matd right_cauchy = ~F * F; + Real I_ff_1 = SimTK::dot(right_cauchy * local_f0_[i], local_f0_[i]) - 1.0; + Real I_ss_1 = SimTK::dot(right_cauchy * local_s0_[i], local_s0_[i]) - 1.0; + Real I_fs = SimTK::dot(right_cauchy * local_f0_[i], local_s0_[i]); + Real ln_J = log(det(F)); + Real I_1_1 = right_cauchy.trace() - Real(Vecd(0).size()); + Matd sigmaPK2 = a0_[0] * exp(b0_[0] * I_1_1) * Matd(1.0) + + (lambda0_ * ln_J - a0_[0]) * inverse(right_cauchy) + + 2.0 * a0_[1] * I_ff_1 * exp(b0_[1] * I_ff_1 * I_ff_1) * local_f0f0_[i] + + 2.0 * a0_[2] * I_ss_1 * exp(b0_[2] * I_ss_1 * I_ss_1) * local_s0s0_[i] + + a0_[3] * I_fs * exp(b0_[3] * I_fs * I_fs) * local_f0s0_[i]; + + return sigmaPK2; + } + //=================================================================================================// + void LocallyOrthotropicMuscle::assignElasticSolidParticles(ElasticSolidParticles* elastic_particles) + { + Muscle::assignElasticSolidParticles(elastic_particles); + initializeFiberAndSheet(); + } + //=================================================================================================// + void LocallyOrthotropicMuscle::initializeFiberAndSheet() + { + base_particles_->registerAVariable(local_f0_, "Fiber"); + base_particles_->registerAVariable(local_s0_, "Sheet"); + base_particles_->addAVariableNameToList(reload_local_parameters_, "Fiber"); + base_particles_->addAVariableNameToList(reload_local_parameters_, "Sheet"); + } + //=================================================================================================// + void LocallyOrthotropicMuscle::readFromXmlForLocalParameters(std::string &filefullpath) + { + BaseMaterial::readFromXmlForLocalParameters(filefullpath); + size_t total_real_particles = base_particles_->total_real_particles_; + for(size_t i = 0; i != total_real_particles; i++) + { + local_f0f0_.push_back(SimTK::outer(local_f0_[i], local_f0_[i])); + local_s0s0_.push_back(SimTK::outer(local_s0_[i], local_s0_[i])); + local_f0s0_.push_back(SimTK::outer(local_f0_[i], local_s0_[i])); + } + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/materials/elastic_solid.h b/SPHINXsys/src/shared/materials/elastic_solid.h new file mode 100644 index 0000000000..f806392a93 --- /dev/null +++ b/SPHINXsys/src/shared/materials/elastic_solid.h @@ -0,0 +1,271 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file elastic_solid.h +* @brief These are classes for define properties of elastic solid materials. +* These classes are based on isotropic linear elastic solid. +* Several more complex materials, including neo-hookean, FENE noe-hookean +* and anisotropic muscle, are derived from the basic elastic solid class. +* @author Xiangyu Hu and Chi Zhang +*/ + +#ifndef ELASTIC_SOLID_H +#define ELASTIC_SOLID_H + + +#include "base_material.h" +#include + +namespace SPH { + + //---------------------------------------------------------------------- + // preclaimed classes + //---------------------------------------------------------------------- + class ElasticSolidParticles; + + /** + * @class ElasticSolid + * @brief Abstract class for a generalized elastic solid + */ + class ElasticSolid : public Solid + { + protected: + Real c0_; /*< sound wave speed */ + Real ct0_; /*< tensile wave speed */ + Real cs0_; /*< shear wave speed */ + Real E0_; /*< Youngs or tensile modules */ + Real G0_; /*< shearmodules */ + Real K0_; /*< bulkmodules */ + Real nu_; /*< Poisson ratio */ + ElasticSolidParticles* elastic_particles_; + + virtual void setReferenceSoundSpeed() = 0; + virtual void setTensileWaveSpeed() = 0; + virtual void setShearWaveSpeed() = 0; + virtual void setYoungsModulus() = 0; + virtual void setShearModulus() = 0; + virtual void setBulkModulus() = 0; + virtual void setPoissonRatio() = 0; + void setContactStiffness() { contact_stiffness_ = c0_* c0_; }; + + virtual void assignDerivedMaterialParameters() override; + public: + ElasticSolid() : Solid(), c0_(1.0), ct0_(1.0), cs0_(0.0717), + E0_(1.0), G0_(0.5), K0_(1.0), nu_(0.0), elastic_particles_(nullptr) {}; + virtual ~ElasticSolid() {}; + + virtual void assignElasticSolidParticles(ElasticSolidParticles* elastic_particles); + Real ReferenceSoundSpeed() { return c0_; }; + Real TensileWaveSpeed() { return ct0_; }; + Real ShearWaveSpeed() { return cs0_; }; + Real YoungsModulus() { return E0_; }; + Real ShearModulus() { return G0_; }; + Real BulkModulus() { return K0_; }; + Real PoissonRatio() { return nu_; }; + + /** compute the stress through defoemation, which can be green-lagrangian tensor, left or right cauchy tensor. */ + virtual Matd ConstitutiveRelation(Matd& deformation, size_t particle_index_i) = 0; + //TODO: the NumericalViscosity and NumericalDampingStress to be delete after shell model done. + virtual Real NumericalViscosity(Real smoothing_length); + /** Compute numerical damping stress using right cauchy tensor. */ + virtual Matd NumericalDampingRightCauchy(Matd& deformation, Matd& deformation_rate, Real smoothing_length, size_t particle_index_i); + /** Compute numerical damping stress using left cauchy tensor. */ + virtual Matd NumericalDampingLeftCauchy(Matd& deformation, Matd& deformation_rate, Real smoothing_length, size_t particle_index_i); + /** numerical demaping is computed between particles i and j */ + virtual Real NumericalDamping(Real dE_dt_ij, Real smoothing_length); + + /** Deviatoric Kirchhoff stress related with the deviatoric part of left cauchy-green deformation tensor. + * Note that, dependent of the normalizeation of the later, the returned stress can be normalized or non-normalized. */ + virtual Matd DeviatoricKirchhoff(const Matd& deviatoric_be); + /** Volumetric Kirchhoff stress determinate */ + virtual Real VolumetricKirchhoff(Real J) = 0; + + virtual ElasticSolid* ThisObjectPtr() override {return this;}; + }; + + /** + * @class LinearElasticSolid + * @brief Isotropic linear elastic solid. + * Note that only basic parameters are used to set ElasticSolid parmaters + */ + class LinearElasticSolid : public ElasticSolid + { + public: + LinearElasticSolid() : ElasticSolid(), youngs_modulus_(1.0), poisson_ratio_(0), lambda0_(1.0) + { + material_name_ = "LinearElasticSolid"; + }; + LinearElasticSolid(Real rho_0, Real Youngs_modulus, Real poisson) : ElasticSolid() + { + material_name_ = "LinearElasticSolid"; + rho0_ = rho_0; + youngs_modulus_ = Youngs_modulus; + poisson_ratio_ = poisson; + + assignDerivedMaterialParameters(); + }; + virtual ~LinearElasticSolid() {}; + + virtual Matd ConstitutiveRelation(Matd& deformation, size_t particle_index_i) override; + /** Volumetric Kirchhoff stress determinate */ + virtual Real VolumetricKirchhoff(Real J) override; + protected: + Real youngs_modulus_; /*< Youngs modules as basic inpiut parameter */ + Real poisson_ratio_; /*< Poisson ratio as basic inpiut parameter */ + Real lambda0_; /*< first Lame parameter */ + + virtual void setReferenceSoundSpeed() override; + virtual void setTensileWaveSpeed() override; + virtual void setShearWaveSpeed() override; + virtual void setYoungsModulus() override { E0_ = youngs_modulus_; }; + virtual void setShearModulus() override; + virtual void setBulkModulus() override; + virtual void setPoissonRatio() override { nu_ = poisson_ratio_; }; + virtual void assignDerivedMaterialParameters() override; + private: + Real getBulkModulus(); + Real getShearModulus(); + Real getLambda(); + }; + + /** + * @class NeoHookeanSolid + * @brief Neo-Hookean solid + */ + class NeoHookeanSolid : public LinearElasticSolid + { + public: + NeoHookeanSolid() : LinearElasticSolid() + { + material_name_ = "NeoHookeanSolid"; + }; + NeoHookeanSolid(Real rho_0, Real Youngs_modulus, Real poisson) + : LinearElasticSolid(rho_0, Youngs_modulus, poisson) + { + material_name_ = "NeoHookeanSolid"; + }; + virtual ~NeoHookeanSolid() {}; + + /** second Piola-Kirchhoff stress related with green-lagrangian deformation tensor */ + virtual Matd ConstitutiveRelation(Matd& deformation, size_t particle_index_i) override; + /** Volumetric Kirchhoff stress determinate */ + virtual Real VolumetricKirchhoff(Real J) override; + }; + + /** + * @class FeneNeoHookeanSolid + * @brief Neo-Hookean solid with finite extension + */ + class FeneNeoHookeanSolid : public LinearElasticSolid + { + protected: + Real j1_m_; /**< reference extension as basic paramter */ + public: + FeneNeoHookeanSolid() : LinearElasticSolid(), j1_m_(1.0) { + material_name_ = "FeneNeoHookeanSolid"; + }; + virtual ~FeneNeoHookeanSolid() {}; + virtual Matd ConstitutiveRelation(Matd& deformation, size_t particle_index_i) override; + }; + + /** + * @class Muscle + * @brief Globally orthotropic muscle. + */ + class Muscle : public ElasticSolid + { + public: + Muscle() : ElasticSolid(), + f0_(0), s0_(0), f0f0_(0), s0s0_(0), f0s0_(0), + a0_{ 1.0, 0.0, 0.0, 0.0 }, b0_{ 1.0, 0.0, 0.0, 0.0 }, bulk_modulus_(30.0) + { + material_name_ = "Muscle"; + }; + virtual ~Muscle() {}; + + virtual Matd MuscleFiberDirection(size_t particle_index_i) { return f0f0_; }; + /** compute the stress through Constitutive relation. */ + virtual Matd ConstitutiveRelation(Matd& deformation, size_t particle_index_i) override; + /** Volumetric Kirchhoff stress determinate */ + virtual Real VolumetricKirchhoff(Real J) override; + + virtual Muscle* ThisObjectPtr() override { return this; }; + protected: + Vecd f0_, s0_; /**< Reference fiber and sheet directions as basic parameter. */ + Matd f0f0_, s0s0_, f0s0_; /**< Tensor products of fiber and sheet directions as basic parameter.. */ + Real a0_[4], b0_[4]; /**< constitutive parameters as basic parameter.*/ + Real bulk_modulus_; /**< to achieve weakly compressible condition as basic parameter.*/ + Real lambda0_; /*< first Lame parameter */ + + virtual void setReferenceSoundSpeed() override; + virtual void setTensileWaveSpeed() override; + virtual void setShearWaveSpeed() override; + virtual void setYoungsModulus() override; + virtual void setShearModulus() override; + virtual void setBulkModulus() override { K0_ = bulk_modulus_; }; + virtual void setPoissonRatio() override; + virtual void assignDerivedMaterialParameters() override; + private: + Real getPoissonRatio(); + Real getShearModulus(); + Real getYoungsModulus(); + Real getLambda(); + }; + + /** + * @class LocallyOrthotropicMuscle + * @brief muscle model is a anisotropic material in which + * there are local fiber direction and cross-fiber sheet direction. + * the model here is from + * Holzapfel and Ogden, 2009, Phil. Trans. R. Soc. 367:3445-3475 + * we consider a neo-hookean model for the background isotropic contribution. + */ + class LocallyOrthotropicMuscle : public Muscle + { + protected: + StdLargeVec local_f0f0_, local_s0s0_, local_f0s0_; /**< Sheet direction. */ + virtual void assignDerivedMaterialParameters() override + { + Muscle::assignDerivedMaterialParameters(); + }; + /** initialize the local properties, fiber and sheet direction. */ + void initializeFiberAndSheet(); + public: + StdLargeVec local_f0_; /**< local fiber direction. */ + StdLargeVec local_s0_; /**< local sheet direction. */ + + LocallyOrthotropicMuscle() : Muscle() + { + material_name_ = "LocallyOrthotropicMuscle"; + parameters_name_ = "LocalFiberAndSheet"; + }; + virtual ~LocallyOrthotropicMuscle() {}; + + virtual void assignElasticSolidParticles(ElasticSolidParticles* elastic_particles) override; + virtual Matd MuscleFiberDirection(size_t particle_index_i) override { return local_f0f0_[particle_index_i]; }; + /** Compute the stress through Constitutive relation. */ + virtual Matd ConstitutiveRelation(Matd& deformation, size_t particle_index_i) override; + virtual void readFromXmlForLocalParameters(std::string &filefullpath) override; + }; +} +#endif //ELASTIC_SOLID_H diff --git a/SPHINXsys/src/shared/materials/inelastic_solid.cpp b/SPHINXsys/src/shared/materials/inelastic_solid.cpp new file mode 100644 index 0000000000..a25e2996c1 --- /dev/null +++ b/SPHINXsys/src/shared/materials/inelastic_solid.cpp @@ -0,0 +1,49 @@ +/** + * @file elastic_solid.cpp + * @author Chi Zhang and Xiangyu Hu + */ + +#include "inelastic_solid.h" + +namespace SPH { + //=================================================================================================// + void HardeningPlasticSolid::initializePlasticParameters() + { + base_particles_->registerAVariable(inverse_plastic_strain_, "InversePlasticRightCauchyStrain", Matd(1.0)); + base_particles_->registerAVariable(hardening_parameter_, "HardeningParameter"); + base_particles_->addAVariableToRestart("InversePlasticRightCauchyStrain"); + base_particles_->addAVariableToRestart("HardeningParameter"); + } + //=================================================================================================// + void HardeningPlasticSolid::assignElasticSolidParticles(ElasticSolidParticles* elastic_particles) + { + ElasticSolid::assignElasticSolidParticles(elastic_particles); + initializePlasticParameters(); + } + //=================================================================================================// + Matd HardeningPlasticSolid::PlasticConstitutiveRelation(const Matd& F, size_t index_i, Real dt) + { + Matd be = F * inverse_plastic_strain_[index_i] * (~F); + Matd normalized_be = be * pow(SimTK::det(be), -one_over_dimensions_); + Real normalized_be_isentropic = normalized_be.trace() * one_over_dimensions_; + Matd deviatoric_PK = DeviatoricKirchhoff(normalized_be - normalized_be_isentropic * Matd(1.0)); + Real deviatoric_PK_norm = deviatoric_PK.norm(); + Real trial_function = + deviatoric_PK_norm - sqrt_2_over_3_ * (hardening_modulus_ * hardening_parameter_[index_i] + yield_stress_); + if (trial_function > 0.0) + { + Real renormalized_shear_modulus = normalized_be_isentropic * G0_; + Real relax_increment = 0.5 * trial_function / (renormalized_shear_modulus + hardening_modulus_ / 3.0); + hardening_parameter_[index_i] += sqrt_2_over_3_ * relax_increment; + deviatoric_PK -= 2.0 * renormalized_shear_modulus * relax_increment * deviatoric_PK / deviatoric_PK_norm; + Matd relaxed_be = deviatoric_PK / G0_ + normalized_be_isentropic; + normalized_be = relaxed_be * pow(det(relaxed_be), -one_over_dimensions_); + } + Matd inverse_F = SimTK::inverse(F); + Matd inverse_F_T = ~inverse_F; + inverse_plastic_strain_[index_i] = inverse_F * normalized_be * inverse_F_T; + + return (deviatoric_PK + VolumetricKirchhoff(SimTK::det(F)) * Matd(1.0)) * inverse_F_T; + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/materials/inelastic_solid.h b/SPHINXsys/src/shared/materials/inelastic_solid.h new file mode 100644 index 0000000000..025f605ce3 --- /dev/null +++ b/SPHINXsys/src/shared/materials/inelastic_solid.h @@ -0,0 +1,92 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file inelastic_solid.h +* @brief These are classes for define properties of elastic solid materials. +* These classes are based on isotropic linear elastic solid. +* Several more complex materials, including neo-hookean, FENE noe-hookean +* and anisotropic muscle, are derived from the basic elastic solid class. +* @author Xiangyu Hu and Chi Zhang +*/ +#pragma once + +#include "elastic_solid.h" + +namespace SPH { + + /** + * @class PlasticSolid + * @brief Abstract class for a generalized plastic solid + */ + class PlasticSolid : public NeoHookeanSolid + { + protected: + Real yield_stress_; + + virtual void initializePlasticParameters() = 0; + public: + /** Constructor */ + PlasticSolid() :NeoHookeanSolid() + { + material_name_ = "PlasticSolid"; + }; + virtual ~PlasticSolid() {}; + + Real YieldStress() { return yield_stress_; }; + /** compute the stress through defoemation, and plastic relaxation. */ + virtual Matd PlasticConstitutiveRelation(const Matd& deformation, size_t index_i, Real dt = 0.0) = 0; + + virtual PlasticSolid* ThisObjectPtr() override { return this; }; + }; + + /** + * @class HardeningPlasticSolid + * @brief Class for a generalized plastic solid + */ + class HardeningPlasticSolid : public PlasticSolid + { + protected: + Real hardening_modulus_; + const Real one_over_dimensions_ = 1.0 / (Real)Dimensions; + const Real sqrt_2_over_3_ = sqrt(2.0 / 3.0); + StdLargeVec inverse_plastic_strain_; /**< inverse of plastic right cauchy green strain tensor */ + StdLargeVec hardening_parameter_; /**< hardening parameter */ + + virtual void initializePlasticParameters() override; + public: + /** Constructor */ + HardeningPlasticSolid() :PlasticSolid() + { + material_name_ = "HardeningPlasticSolid"; + }; + virtual ~HardeningPlasticSolid() {}; + + Real HardeningModulus() { return hardening_modulus_; }; + /** assign particles to this material */ + virtual void assignElasticSolidParticles(ElasticSolidParticles* elastic_particles) override; + /** compute the stress through defoemation, and plastic relaxation. */ + virtual Matd PlasticConstitutiveRelation(const Matd& deformation, size_t index_i, Real dt = 0.0) override; + + virtual HardeningPlasticSolid* ThisObjectPtr() override { return this; }; + }; +} diff --git a/SPHINXsys/src/shared/materials/riemann_solver.cpp b/SPHINXsys/src/shared/materials/riemann_solver.cpp new file mode 100644 index 0000000000..f147cd5eda --- /dev/null +++ b/SPHINXsys/src/shared/materials/riemann_solver.cpp @@ -0,0 +1,233 @@ +/** + * @file riemann_solver.cpp + * @author Xiangyu Hu + */ + +#include "riemann_solver.h" + +#include "base_material.h" + +#include "compressible_fluid.h" + +namespace SPH { + //=================================================================================================// + Real NoRiemannSolver:: + getPStar(const FluidState& state_i, const FluidState& state_j, const Vecd& e_ij) + { + return (state_i.p_ * state_j.rho_ + state_j.p_ * state_i.rho_) + / (state_i.rho_ + state_j.rho_); + } + //=================================================================================================// + Vecd NoRiemannSolver:: + getVStar(const FluidState& state_i, const FluidState& state_j, const Vecd& e_ij) + { + return (state_i.vel_ * state_i.rho_ + state_j.vel_ * state_j.rho_) + / (state_i.rho_ + state_j.rho_); + } + //=================================================================================================// + Real AcousticRiemannSolver:: + getPStar(const FluidState& state_i, const FluidState& state_j, const Vecd& e_ij) + { + Real ul = dot(-e_ij, state_i.vel_); + Real ur = dot(-e_ij, state_j.vel_); + + Real rhol_cl = fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_) * state_i.rho_; + Real rhor_cr = fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_) * state_j.rho_; + Real clr = (rhol_cl + rhor_cr) / (state_i.rho_ + state_j.rho_); + + return (rhol_cl * state_j.p_ + rhor_cr * state_i.p_ + rhol_cl * rhor_cr * (ul - ur) + * SMIN(3.0 * SMAX((ul - ur) / clr, 0.0), 1.0)) / (rhol_cl + rhor_cr); + } + //=================================================================================================// + Vecd AcousticRiemannSolver:: + getVStar(const FluidState& state_i, const FluidState& state_j, const Vecd& e_ij) + { + Real ul = dot(-e_ij, state_i.vel_); + Real ur = dot(-e_ij, state_j.vel_); + Real rhol_cl = fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_) * state_i.rho_; + Real rhor_cr = fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_) * state_j.rho_; + Real u_star = (rhol_cl * ul + rhor_cr * ur + state_i.p_ - state_j.p_) / (rhol_cl + rhor_cr); + + return (state_i.vel_ * state_i.rho_ + state_j.vel_ * state_j.rho_) / (state_i.rho_ + state_j.rho_) + - e_ij * (u_star - (ul * state_i.rho_ + ur * state_j.rho_) / (state_i.rho_ + state_j.rho_)); + } + //=================================================================================================// + Real DissipativeRiemannSolver:: + getPStar(const FluidState& state_i, const FluidState& state_j, const Vecd& e_ij) + { + Real ul = dot(-e_ij, state_i.vel_); + Real ur = dot(-e_ij, state_j.vel_); + + Real rhol_cl = fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_) * state_i.rho_; + Real rhor_cr = fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_) * state_j.rho_; + + return (rhol_cl * state_j.p_ + rhor_cr * state_i.p_ + rhol_cl * rhor_cr * (ul - ur)) / (rhol_cl + rhor_cr); + } + //=================================================================================================// + Vecd DissipativeRiemannSolver:: + getVStar(const FluidState& state_i, const FluidState& state_j, const Vecd& e_ij) + { + Real ul = dot(-e_ij, state_i.vel_); + Real ur = dot(-e_ij, state_j.vel_); + Real rhol_cl = fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_) * state_i.rho_; + Real rhor_cr = fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_) * state_j.rho_; + Real u_star = (rhol_cl * ul + rhor_cr * ur + state_i.p_ - state_j.p_) / (rhol_cl + rhor_cr); + + return (state_i.vel_ * state_i.rho_ + state_j.vel_ * state_j.rho_) / (state_i.rho_ + state_j.rho_) + - e_ij * (u_star - (ul * state_i.rho_ + ur * state_j.rho_) / (state_i.rho_ + state_j.rho_)); + } + //=================================================================================================// + Real HLLCRiemannSolver:: + getPStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& e_ij) + { + Real ul = dot(-e_ij, state_i.vel_); + Real ur = dot(-e_ij, state_j.vel_); + Real s_l = ul - compressible_fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_); + Real s_r = ur + compressible_fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_); + Real s_star = (state_j.rho_ * ur * (s_r - ur) + state_i.rho_ * ul * (ul - s_l) + state_i.p_ - state_j.p_) / (state_j.rho_ * (s_r - ur) + state_i.rho_ * (ul - s_l)); + Real p_star = 0.0; + if (0.0 < s_l) { p_star = state_i.p_; } + if (s_l <= 0.0 && s_r >= 0.0) { p_star = state_i.p_ + state_i.rho_*(s_l - ul)*(s_star - ul); } + if (s_r < 0.0) { p_star = state_j.p_; } + + return p_star; + } + //=================================================================================================// + Vecd HLLCRiemannSolver:: + getVStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& e_ij) + { + Real ul = dot(-e_ij, state_i.vel_); + Real ur = dot(-e_ij, state_j.vel_); + Real s_l = ul - compressible_fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_); + Real s_r = ur + compressible_fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_); + Real s_star = (state_j.rho_ * ur * (s_r - ur) + state_i.rho_ * ul * (ul - s_l) + state_i.p_ - state_j.p_) / (state_j.rho_ * (s_r - ur) + state_i.rho_ * (ul - s_l)); + Vecd v_star(0); + if (0.0 < s_l) { v_star = state_i.vel_; } + if (s_l <= 0.0 && 0.0 <= s_star) { v_star = state_i.vel_ - e_ij * (s_star - ul); } + if (s_star <= 0.0 && 0.0 <= s_r) { v_star = state_j.vel_ - e_ij * (s_star - ur); } + if (s_r < 0.0) { v_star = state_j.vel_; } + + return v_star; + } + //=================================================================================================// + Real HLLCRiemannSolver:: + getRhoStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& e_ij) + { + Real ul = dot(-e_ij, state_i.vel_); + Real ur = dot(-e_ij, state_j.vel_); + Real s_l = ul - compressible_fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_); + Real s_r = ur + compressible_fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_); + Real s_star = (state_j.rho_ * ur * (s_r - ur) + state_i.rho_ * ul * (ul - s_l) + state_i.p_ - state_j.p_) / (state_j.rho_ * (s_r - ur) + state_i.rho_ * (ul - s_l)); + Real rho_star = 0.0; + if (0.0 < s_l) { rho_star = state_i.rho_; } + if (s_l <= 0.0 && 0.0 <= s_star) { rho_star = state_i.rho_ * (s_l - ul) / (s_l - s_star); } + if (s_star <= 0.0 && 0.0 <= s_r) { rho_star = state_j.rho_ * (s_r - ur) / (s_r - s_star); } + if (s_r < 0.0) { rho_star = state_j.rho_; } + + return rho_star; + } + //=================================================================================================// + Real HLLCRiemannSolver:: + getEStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& e_ij) + { + Real ul = dot(-e_ij, state_i.vel_); + Real ur = dot(-e_ij, state_j.vel_); + Real s_l = ul - compressible_fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_); + Real s_r = ur + compressible_fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_); + Real s_star = (state_j.rho_ * ur * (s_r - ur) + state_i.rho_ * ul * (ul - s_l) + state_i.p_ - state_j.p_) / (state_j.rho_ * (s_r - ur) + state_i.rho_ * (ul - s_l)); + Real energy_star = 0.0; + if (0.0 < s_l) { energy_star = state_i.E_; } + if (s_l <= 0.0 && 0.0 <= s_star) + { + energy_star = state_i.rho_ * (s_l - ul) / (s_l - s_star) * (state_i.E_ / state_i.rho_ + (s_star - ul) * (s_star + state_i.p_ / state_i.rho_ / (s_l - ul))); + } + if (s_star <= 0.0 && 0.0 <= s_r) + { + energy_star = state_j.rho_ * (s_r - ur) / (s_r - s_star) * (state_j.E_ / state_j.rho_ + (s_star - ur) * (s_star + state_j.p_ / state_j.rho_ / (s_r - ur))); + } + if (s_r < 0.0) { energy_star = state_j.E_; } + + return energy_star; + } + //=================================================================================================// + Real HLLCWithLimiterRiemannSolver:: + getPStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& e_ij) + { + Real ul = dot(-e_ij, state_i.vel_); + Real ur = dot(-e_ij, state_j.vel_); + Real s_l = ul - compressible_fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_); + Real s_r = ur + compressible_fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_); + Real s_star = (state_j.rho_ * ur * (s_r - ur) + state_i.rho_ * ul * (ul - s_l) + state_i.p_ - state_j.p_) / (state_j.rho_ * (s_r - ur) + state_i.rho_ * (ul - s_l)); + Real p_star = 0.0; + if (0.0 < s_l) { p_star = state_i.p_; } + if (s_l <= 0.0 && s_r >= 0.0) + { + Real rho_ave = 2 * state_i.rho_*state_j.rho_ / (state_i.rho_ + state_j.rho_); + Real rho_cl = state_i.rho_*compressible_fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_); + Real rho_cr = state_j.rho_*compressible_fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_); + Real rho_clr = (rho_cl*state_i.rho_ + rho_cr * state_j.rho_) / (state_i.rho_ + state_j.rho_); + p_star = 0.5*(state_i.p_ + state_j.p_) + 0.5*(SMIN(3.0 * SMAX(rho_ave*(ul - ur), 0.0), rho_clr)*(ul - ur) + s_star * (rho_cr - rho_cl)); + } + if (s_r < 0.0) { p_star = state_j.p_; } + + return p_star; + } + //=================================================================================================// + Vecd HLLCWithLimiterRiemannSolver:: + getVStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& e_ij) + { + Real ul = dot(-e_ij, state_i.vel_); + Real ur = dot(-e_ij, state_j.vel_); + Real s_l = ul - compressible_fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_); + Real s_r = ur + compressible_fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_); + Real s_star = (state_j.rho_ * ur * (s_r - ur) + state_i.rho_ * ul * (ul - s_l) + state_i.p_ - state_j.p_) / (state_j.rho_ * (s_r - ur) + state_i.rho_ * (ul - s_l)); + Vecd v_star(0); + if (0.0 < s_l) { v_star = state_i.vel_; } + if (s_l <= 0.0 && 0.0 <= s_star) { v_star = state_i.vel_ - e_ij * (s_star - ul); } + if (s_star <= 0.0 && 0.0 <= s_r) { v_star = state_j.vel_ - e_ij * (s_star - ur); } + if (s_r < 0.0) { v_star = state_j.vel_; } + + return v_star; + } + //=================================================================================================// + Real HLLCWithLimiterRiemannSolver:: + getRhoStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& e_ij) + { + Real ul = dot(-e_ij, state_i.vel_); + Real ur = dot(-e_ij, state_j.vel_); + Real s_l = ul - compressible_fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_); + Real s_r = ur + compressible_fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_); + Real s_star = (state_j.rho_ * ur * (s_r - ur) + state_i.rho_ * ul * (ul - s_l) + state_i.p_ - state_j.p_) / (state_j.rho_ * (s_r - ur) + state_i.rho_ * (ul - s_l)); + Real rho_star = 0.0; + if (0.0 < s_l) { rho_star = state_i.rho_; } + if (s_l <= 0.0 && 0.0 <= s_star) { rho_star = state_i.rho_ * (s_l - ul) / (s_l - s_star); } + if (s_star <= 0.0 && 0.0 <= s_r) { rho_star = state_j.rho_ * (s_r - ur) / (s_r - s_star); } + if (s_r < 0.0) { rho_star = state_j.rho_; } + + return rho_star; + } + //=================================================================================================// + Real HLLCWithLimiterRiemannSolver:: + getEStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& e_ij) + { + Real ul = dot(-e_ij, state_i.vel_); + Real ur = dot(-e_ij, state_j.vel_); + Real s_l = ul - compressible_fluid_i_.getSoundSpeed(state_i.p_, state_i.rho_); + Real s_r = ur + compressible_fluid_j_.getSoundSpeed(state_j.p_, state_j.rho_); + Real s_star = (state_j.rho_ * ur * (s_r - ur) + state_i.rho_ * ul * (ul - s_l) + state_i.p_ - state_j.p_) / (state_j.rho_ * (s_r - ur) + state_i.rho_ * (ul - s_l)); + Real energy_star = 0.0; + if (0.0 < s_l) { energy_star = state_i.E_; } + if (s_l <= 0.0 && 0.0 <= s_star) + { + energy_star = state_i.rho_ * (s_l - ul) / (s_l - s_star) * (state_i.E_ / state_i.rho_ + (s_star - ul) * (s_star + state_i.p_ / state_i.rho_ / (s_l - ul))); + } + if (s_star <= 0.0 && 0.0 <= s_r) + { + energy_star = state_j.rho_ * (s_r - ur) / (s_r - s_star) * (state_j.E_ / state_j.rho_ + (s_star - ur) * (s_star + state_j.p_ / state_j.rho_ / (s_r - ur))); + } + if (s_r < 0.0) { energy_star = state_j.E_; } + + return energy_star; + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/materials/riemann_solver.h b/SPHINXsys/src/shared/materials/riemann_solver.h new file mode 100644 index 0000000000..8541fe7d45 --- /dev/null +++ b/SPHINXsys/src/shared/materials/riemann_solver.h @@ -0,0 +1,108 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file riemann_solvers.h + * @brief This is the collection of Riemann solvers. + * @author Xiangyu Hu + */ + + +#ifndef RIEMANN_SOLVER_H +#define RIEMANN_SOLVER_H + +#include "base_data_package.h" + +namespace SPH +{ + struct FluidState + { + Vecd vel_; + Real rho_, p_; + FluidState(Real& rho, Vecd& vel, Real& p) : + vel_(vel), rho_(rho), p_(p) {}; + }; + + struct CompressibleFluidState + { + Vecd vel_; + Real rho_, p_, E_; + CompressibleFluidState(Real& rho, Vecd& vel, Real& p, Real& E) : + vel_(vel), rho_(rho), p_(p), E_(E) {}; + }; + + class Fluid; + class CompressibleFluid; + + class NoRiemannSolver + { + Fluid& fluid_l_, & fluid_r_; + public: + NoRiemannSolver(Fluid& fluid_i, Fluid& fluid_j) : fluid_l_(fluid_i), fluid_r_(fluid_j) {}; + Real getPStar(const FluidState& state_i, const FluidState& state_j, const Vecd& direction_to_i); + Vecd getVStar(const FluidState& state_i, const FluidState& state_j, const Vecd& direction_to_i); + }; + + class AcousticRiemannSolver + { + Fluid& fluid_i_, & fluid_j_; + public: + AcousticRiemannSolver(Fluid& fluid_i, Fluid& fluid_j) : fluid_i_(fluid_i), fluid_j_(fluid_j) {}; + Real getPStar(const FluidState& state_i, const FluidState& state_j, const Vecd& direction_to_i); + Vecd getVStar(const FluidState& state_i, const FluidState& state_j, const Vecd& direction_to_i); + }; + + class DissipativeRiemannSolver + { + Fluid& fluid_i_, & fluid_j_; + public: + DissipativeRiemannSolver(Fluid& fluid_i, Fluid& fluid_j) : fluid_i_(fluid_i), fluid_j_(fluid_j) {}; + Real getPStar(const FluidState& state_i, const FluidState& state_j, const Vecd& direction_to_i); + Vecd getVStar(const FluidState& state_i, const FluidState& state_j, const Vecd& direction_to_i); + }; + + class HLLCRiemannSolver + { + CompressibleFluid& compressible_fluid_i_, &compressible_fluid_j_; + public: + HLLCRiemannSolver(CompressibleFluid& compressible_fluid_i, CompressibleFluid& compressible_fluid_j) : + compressible_fluid_i_(compressible_fluid_i), compressible_fluid_j_(compressible_fluid_j) {}; + Real getPStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& direction_to_i); + Vecd getVStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& direction_to_i); + Real getRhoStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& direction_to_i); + Real getEStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& direction_to_i); + }; + + class HLLCWithLimiterRiemannSolver + { + CompressibleFluid& compressible_fluid_i_, &compressible_fluid_j_; + public: + HLLCWithLimiterRiemannSolver(CompressibleFluid& compressible_fluid_i, CompressibleFluid& compressible_fluid_j) : + compressible_fluid_i_(compressible_fluid_i), compressible_fluid_j_(compressible_fluid_j) {}; + Real getPStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& direction_to_i); + Vecd getVStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& direction_to_i); + Real getRhoStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& direction_to_i); + Real getEStar(const CompressibleFluidState& state_i, const CompressibleFluidState& state_j, const Vecd& direction_to_i); + }; +} + +#endif //RIEMANN_SOLVER_H diff --git a/SPHINXsys/src/shared/materials/weakly_compressible_fluid.cpp b/SPHINXsys/src/shared/materials/weakly_compressible_fluid.cpp new file mode 100644 index 0000000000..d302c8c1a8 --- /dev/null +++ b/SPHINXsys/src/shared/materials/weakly_compressible_fluid.cpp @@ -0,0 +1,48 @@ +/** + * @file weakly_compressible_fluid.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#include "weakly_compressible_fluid.h" + +namespace SPH { + //===============================================================// + Real WeaklyCompressibleFluid::getPressure(Real rho) + { + return p0_ * (rho / rho0_ - 1.0); + } + //===============================================================// + Real WeaklyCompressibleFluid::DensityFromPressure(Real p) + { + return rho0_ * (p / p0_ + 1.0); + } + //===============================================================// + Real WeaklyCompressibleFluid::getSoundSpeed(Real p, Real rho) + { + return c0_; + } + //===============================================================// + Real SymmetricTaitFluid::getPressure(Real rho) + { + Real rho_ratio = rho / rho0_; + return rho_ratio > 1.0 + ? p0_ * (powerN(rho_ratio, gamma_) - 1.0) / Real(gamma_) + : -p0_ * (powerN(1.0 / rho_ratio, gamma_) - 1.0) / Real(gamma_); + } + //===============================================================// + Real SymmetricTaitFluid::DensityFromPressure(Real p) + { + return p > 0.0 + ? rho0_ * pow(1.0 + Real(gamma_) * p / p0_, 1.0 / Real(gamma_)) + : rho0_ / pow(1.0 - Real(gamma_) * p / p0_, 1.0 / Real(gamma_)); + } + //===============================================================// + Real SymmetricTaitFluid::getSoundSpeed(Real p, Real rho) + { + Real rho_ratio = rho / rho0_; + return rho_ratio > 1.0 + ? sqrt((p0_ + Real(gamma_) * p) / rho) + : sqrt((p0_ - Real(gamma_) * p) / rho); + } + //===============================================================// +} diff --git a/SPHINXsys/src/shared/materials/weakly_compressible_fluid.h b/SPHINXsys/src/shared/materials/weakly_compressible_fluid.h new file mode 100644 index 0000000000..0d6e346951 --- /dev/null +++ b/SPHINXsys/src/shared/materials/weakly_compressible_fluid.h @@ -0,0 +1,158 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file weakly_compressible_fluid.h + * @brief Describe the weakly compressible fluid which is used + * model incompressible fluids. Here, we have included several equation of states. + * Futhermore, A typical non-newtonian fluid model is included. + * @author Xiangyu Hu, Luhui Han and Chi Zhang + */ + + +#ifndef WEAKLY_COMPRESSIBLE_FLUID_H +#define WEAKLY_COMPRESSIBLE_FLUID_H + + + +#include "base_material.h" + +namespace SPH { + + class ViscoelasticFluidParticles; + + /** + * @class WeaklyCompressibleFluid + * @brief Linear equation of state (EOS). + */ + class WeaklyCompressibleFluid : public Fluid + { + protected: + Real p0_; /**< reference pressure */ + + virtual void assignDerivedMaterialParameters() override + { + Fluid::assignDerivedMaterialParameters(); + p0_ = rho0_ * c0_ * c0_; + }; + public: + explicit WeaklyCompressibleFluid() : Fluid(), p0_(1.0) { + material_name_ = "WeaklyCompressibleFluid"; + }; + virtual ~WeaklyCompressibleFluid() {}; + + virtual Real getPressure(Real rho) override; + virtual Real DensityFromPressure(Real p) override; + virtual Real getSoundSpeed(Real p = 0.0, Real rho = 1.0) override; + virtual WeaklyCompressibleFluid* ThisObjectPtr() override {return this;}; + }; + + /** + * @class WeaklyCompressibleFluidFreeSurface + * @brief Equation of state (EOS) with cut-off pressure. + */ + template + class WeaklyCompressibleFluidFreeSurface : public WeaklyCompressibleFluid + { + protected: + WeaklyCompressibleFluidType* fluid_; + Real cutoff_pressure_, cutoff_density_; + + virtual void assignDerivedMaterialParameters() { + WeaklyCompressibleFluid::assignDerivedMaterialParameters(); + }; + public: + WeaklyCompressibleFluidFreeSurface(Real cutoff_pressure) + : WeaklyCompressibleFluid(), + cutoff_pressure_(cutoff_pressure) { + fluid_ = new WeaklyCompressibleFluidType(); + material_name_ = fluid_->material_name_ + "FreeSurface"; + cutoff_density_ = fluid_->DensityFromPressure(cutoff_pressure); + }; + virtual ~WeaklyCompressibleFluidFreeSurface() {}; + + virtual Real getPressure(Real rho) override { + return rho < cutoff_density_ ? cutoff_pressure_ : fluid_->getPressure(rho); + }; + }; + + /** + * @class SymmetricTaitFluid + * @brief Tait EOS for positive and negative pressure symmetrically. + */ + class SymmetricTaitFluid : public WeaklyCompressibleFluid + { + protected: + + int gamma_; /**< determine the stiffness of the fluid */ + + /** assign derived material properties*/ + virtual void assignDerivedMaterialParameters() override + { + WeaklyCompressibleFluid::assignDerivedMaterialParameters(); + }; + + public: + SymmetricTaitFluid() : WeaklyCompressibleFluid(), gamma_(2) + { + material_name_ = "SymmetricTaitFluid"; + }; + virtual ~SymmetricTaitFluid() {}; + + virtual Real getPressure(Real rho) override; + virtual Real DensityFromPressure(Real p) override; + virtual Real getSoundSpeed(Real p = 0.0, Real rho = 1.0) override; + }; + + /** + * @class Oldroyd_B_Fluid + * @brief linear EOS with relaxation time and polymetric viscosity. + */ + class Oldroyd_B_Fluid : public WeaklyCompressibleFluid + { + protected: + Real lambda_; /**< relaxation time */ + Real mu_p_; /**< polymeric viscosity */ + ViscoelasticFluidParticles* viscoelastic_fluid_particles_; + + virtual void assignDerivedMaterialParameters() override + { + WeaklyCompressibleFluid::assignDerivedMaterialParameters(); + }; + public: + explicit Oldroyd_B_Fluid() : WeaklyCompressibleFluid(), + lambda_(1.0), mu_p_(0.0) + { + material_name_ = "Oldroyd_B_Fluid"; + }; + virtual ~Oldroyd_B_Fluid() {}; + + void assignViscoelasticFluidParticles(ViscoelasticFluidParticles* viscoelastic_fluid_particles) + { + viscoelastic_fluid_particles_ = viscoelastic_fluid_particles; + }; + Real getReferenceRelaxationTime() { return lambda_; }; + Real ReferencePolymericViscosity() { return mu_p_; }; + virtual Oldroyd_B_Fluid* ThisObjectPtr() override {return this;}; + }; +} +#endif //WEAKLY_COMPRESSIBLE_FLUID_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/meshes/CMakeLists.txt b/SPHINXsys/src/shared/meshes/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/meshes/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/meshes/base_mesh.cpp b/SPHINXsys/src/shared/meshes/base_mesh.cpp new file mode 100644 index 0000000000..9e30ec6e04 --- /dev/null +++ b/SPHINXsys/src/shared/meshes/base_mesh.cpp @@ -0,0 +1,99 @@ +/** + * @file base_mesh.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#include "base_mesh.h" + +namespace SPH +{ + //=================================================================================================// + BaseMesh::BaseMesh() + : mesh_lower_bound_(0), grid_spacing_(1.0), number_of_grid_points_(0){}; + //=================================================================================================// + BaseMesh::BaseMesh(Vecu number_of_grid_points) + : mesh_lower_bound_(0), grid_spacing_(1.0), number_of_grid_points_(number_of_grid_points){}; + //=================================================================================================// + BaseMesh::BaseMesh(Vecd mesh_lower_bound, Real grid_spacing, Vecu number_of_grid_points) + : mesh_lower_bound_(mesh_lower_bound), grid_spacing_(grid_spacing), + number_of_grid_points_(number_of_grid_points) {} + //=================================================================================================// + BaseMesh::BaseMesh(BoundingBox tentative_bounds, Real grid_spacing, size_t buffer_width) : BaseMesh() + { + grid_spacing_ = grid_spacing; + Vecd mesh_buffer = Vecd(Real(buffer_width) * grid_spacing); + mesh_lower_bound_ = tentative_bounds.first - mesh_buffer; + Vecd tentative_upper_bound = tentative_bounds.second + mesh_buffer; + for (int i = 0; i != Dimensions; ++i) + { + number_of_grid_points_[i] = + 1 + static_cast(ceil((tentative_upper_bound[i] - mesh_lower_bound_[i]) / grid_spacing)); + } + } + //=================================================================================================// + Vecu BaseMesh::CellIndexFromPosition(const Vecd &position) + { + Vecd rltpos = position - mesh_lower_bound_; + Vecu cell_index(0); + for (int n = 0; n < rltpos.size(); n++) + { + cell_index[n] = + clamp((int)floor(rltpos[n] / grid_spacing_), 0, int(number_of_grid_points_[n]) - 2); + } + return cell_index; + } + //=================================================================================================// + Vecd BaseMesh::CellPositionFromIndex(const Vecu &cell_index) + { + Vecd cell_position; + for (int n = 0; n < cell_position.size(); n++) + { + cell_position[n] = mesh_lower_bound_[n] + (Real(cell_index[n]) + 0.5) * grid_spacing_; + } + return cell_position; + } + //=================================================================================================// + Vecd BaseMesh::GridPositionFromIndex(const Vecu &grid_index) + { + Vecd grid_position; + for (int n = 0; n < grid_position.size(); n++) + { + grid_position[n] = mesh_lower_bound_[n] + Real(grid_index[n]) * grid_spacing_; + } + return grid_position; + } + //=================================================================================================// + size_t BaseMesh::MortonCode(const size_t &i) + { + size_t x = i; + x &= 0x3ff; + x = (x | x << 16) & 0x30000ff; + x = (x | x << 8) & 0x300f00f; + x = (x | x << 4) & 0x30c30c3; + x = (x | x << 2) & 0x9249249; + return x; + } + //=================================================================================================// + Mesh::Mesh(BoundingBox tentative_bounds, Real grid_spacing, size_t buffer_width) + : BaseMesh(tentative_bounds, grid_spacing, buffer_width), + buffer_width_(buffer_width), + number_of_cells_(this->NumberOfCellsFromNumberOfGridPoints(this->NumberOfGridPoints())) {} + //=================================================================================================// + Mesh::Mesh(Vecd mesh_lower_bound, Vecu number_of_cells, Real grid_spacing) + : BaseMesh(), buffer_width_(0), number_of_cells_(number_of_cells) + { + mesh_lower_bound_ = mesh_lower_bound; + grid_spacing_ = grid_spacing; + number_of_grid_points_ = NumberOfGridPointsFromNumberOfCells(number_of_cells_); + } + //=================================================================================================// + void Mesh::copyMeshProperties(Mesh *another_mesh) + { + mesh_lower_bound_ = another_mesh->mesh_lower_bound_; + grid_spacing_ = another_mesh->grid_spacing_; + number_of_grid_points_ = another_mesh->number_of_grid_points_; + number_of_cells_ = another_mesh->number_of_cells_; + buffer_width_ = another_mesh->buffer_width_; + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/meshes/base_mesh.h b/SPHINXsys/src/shared/meshes/base_mesh.h new file mode 100644 index 0000000000..e9f6cfdf56 --- /dev/null +++ b/SPHINXsys/src/shared/meshes/base_mesh.h @@ -0,0 +1,193 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file base_mesh.h +* @brief This is the base classes of mesh, which is used to describe ordered and indexed +* data sets. Depending on application, there are different data +* saved on the mesh. The intersection points of mesh lines are called +* grid points, the element enclosed by mesh lines (2D) or faces (3D) called +* cells. The mesh line or face are also called cell faces. Grid points are +* also called cell corners. +* @author Chi ZHang and Xiangyu Hu +*/ + +#ifndef BASE_MESH_H +#define BASE_MESH_H + +#include "base_data_package.h" +#include "sph_data_conainers.h" +#include "my_memory_pool.h" + +#include +#include +#include +#include +using namespace std::placeholders; + +namespace SPH +{ + class Kernel; + class ParticleAdaptation; + + /** Functor for operation on the mesh. */ + typedef std::function MeshFunctor; + /** Iterator on the mesh by looping index. sequential computing. */ + void MeshIterator(const Vecu &index_begin, const Vecu &index_end, MeshFunctor &mesh_functor, Real dt = 0.0); + /** Iterator on the mesh by looping index. parallel computing. */ + void MeshIterator_parallel(const Vecu &index_begin, const Vecu &index_end, MeshFunctor &mesh_functor, Real dt = 0.0); + + /** + * @class BaseMesh + * @brief Base class for all meshes which may be grid or cell based. + * The basic properties of the mesh, such as lower bound, grid spacing + * and number of grid points may be determined by the derived class. + * Note that there is no mesh-based data defined here. + */ + class BaseMesh + { + protected: + Vecd mesh_lower_bound_; /**< mesh lower bound as reference coordinate */ + Real grid_spacing_; /**< grid_spacing */ + Vecu number_of_grid_points_; /**< number of grid points by dimension */ + public: + BaseMesh(); + BaseMesh(Vecu number_of_grid_points); + BaseMesh(Vecd mesh_lower_bound, Real grid_spacing, Vecu number_of_grid_points); + BaseMesh(BoundingBox tentative_bounds, Real grid_spacing, size_t buffer_width); + virtual ~BaseMesh(){}; + + Vecd MeshLowerBound() { return mesh_lower_bound_; }; + Real GridSpacing() { return grid_spacing_; }; + Vecu NumberOfGridPoints() { return number_of_grid_points_; }; + Vecu NumberOfGridPointsFromNumberOfCells(const Vecu &number_of_cells) { return number_of_cells + Vecu(1); }; + Vecu NumberOfCellsFromNumberOfGridPoints(const Vecu &number_of_grid_points) { return number_of_grid_points - Vecu(1); }; + Vecd GridPositionFromCellPosition(const Vecd &cell_position) { return cell_position - Vecd(0.5 * grid_spacing_); }; + + Vecu CellIndexFromPosition(const Vecd &position); + Vecd CellPositionFromIndex(const Vecu &cell_index); + /** Note that, the lower corner grid of a cell has the same index as the cell. */ + Vecd GridPositionFromIndex(const Vecu &grid_index); + Vecu transfer1DtoMeshIndex(const Vecu &number_of_grid_points, size_t i); + size_t transferMeshIndexTo1D(const Vecu &number_of_grid_points, const Vecu &grid_index); + /** converts mesh index into a Morton order. + * Interleave a 10 bit number in 32 bits, fill one bit and leave the other 2 as zeros + * https://stackoverflow.com/questions/18529057/ + * produce-interleaving-bit-patterns-morton-keys-for-32-bit-64-bit-and-128bit + */ + size_t MortonCode(const size_t &i); + /** This function converts mesh index into a Morton order. */ + size_t transferMeshIndexToMortonOrder(const Vecu &grid_index); + }; + + /** + * @class Mesh + * @brief Abstract base class for cell-based mesh + * by introducing number of cells, buffer width and mesh-based data in its derived classes. + */ + class Mesh : public BaseMesh + { + protected: + size_t buffer_width_; /**< buffer width to avoid bound check.*/ + Vecu number_of_cells_; /**< number of cells by dimension */ + + void copyMeshProperties(Mesh *another_mesh); + /** allocate memories for the mesh data matrix*/ + virtual void allocateMeshDataMatrix() = 0; + /** delete memories for mesh data */ + virtual void deleteMeshDataMatrix() = 0; + + public: + Mesh(BoundingBox tentative_bounds, Real grid_spacing, size_t buffer_width); + Mesh(Vecd mesh_lower_bound, Vecu number_of_cells, Real grid_spacing); + virtual ~Mesh(){}; + + Vecu NumberOfCells() { return number_of_cells_; }; + size_t MeshBufferSize() { return buffer_width_; }; + }; + + /** + * @class BaseMeshField + * @brief Abstract base class for the field data saved on a mesh. + */ + class BaseMeshField + { + protected: + std::string name_; + + public: + BaseMeshField(std::string name) : name_(name){}; + virtual ~BaseMeshField(){}; + + std::string Name() { return name_; }; + /** output mesh data for Tecplot visualization */ + virtual void writeMeshFieldToPlt(std::ofstream &output_file) = 0; + }; + + /** + * @class MultilevelMesh + * @brief Multi level Meshes with successively double the resolutions + */ + template + class MultilevelMesh : public MeshFieldType + { + protected: + size_t total_levels_; /**< level 0 is the coarsest */ + StdVec mesh_levels_; + + public: + /**template parameter pack is used with rvalue reference and perfect forwarding to keep + * the type of arguments when called by another function with template parameter pack too. */ + template + MultilevelMesh(BoundingBox tentative_bounds, Real reference_spacing, size_t total_levels, + Real maximum_spacing_ratio, Args &&...args) + : MeshFieldType(std::forward(args)...), total_levels_(total_levels) + { + Real zero_level_spacing = reference_spacing * maximum_spacing_ratio; + for (size_t level = 0; level != total_levels_; ++level) + { + Real spacing_level = zero_level_spacing * powerN(0.5, (int)level); + /** all mesh levels aligned at the lower bound of tentative_bounds */ + MeshLevelType *mesh_level = + new MeshLevelType(tentative_bounds, spacing_level, std::forward(args)...); + mesh_levels_.push_back(mesh_level); + } + }; + + virtual ~MultilevelMesh() + { + for (size_t l = 0; l != total_levels_; ++l) + mesh_levels_[l]->~MeshLevelType(); + }; + + StdVec getMeshLevels() { return mesh_levels_; }; + + void writeMeshFieldToPlt(std::ofstream &output_file) override + { + for (size_t l = 0; l != total_levels_; ++l) + { + mesh_levels_[l]->writeMeshFieldToPlt(output_file); + } + } + }; +} +#endif //BASE_MESH_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/meshes/cell_linked_list.cpp b/SPHINXsys/src/shared/meshes/cell_linked_list.cpp new file mode 100644 index 0000000000..25ba9f1023 --- /dev/null +++ b/SPHINXsys/src/shared/meshes/cell_linked_list.cpp @@ -0,0 +1,151 @@ +/** + * @file cell_linked_list.cpp + * @author Yongchuan Yu, Chi ZHang and Xiangyu Hu + */ + +#include "cell_linked_list.h" +#include "base_kernel.h" +#include "base_body.h" +#include "particle_adaptation.h" +#include "base_particles.h" + +namespace SPH +{ + //=================================================================================================// + BaseCellLinkedList:: + BaseCellLinkedList(SPHBody &sph_body, ParticleAdaptation &particle_adaptation) + : BaseMeshField("CellLinkedList"), + sph_body_(sph_body), kernel_(*particle_adaptation.getKernel()), base_particles_(nullptr) {} + //=================================================================================================// + void BaseCellLinkedList::clearSplitCellLists(SplitCellLists &split_cell_lists) + { + for (size_t i = 0; i < split_cell_lists.size(); i++) + split_cell_lists[i].clear(); + } + //=================================================================================================// + CellLinkedList::CellLinkedList(BoundingBox tentative_bounds, Real grid_spacing, + SPHBody &sph_body, ParticleAdaptation &particle_adaptation) + : BaseCellLinkedList(sph_body, particle_adaptation), Mesh(tentative_bounds, grid_spacing, 2) + { + allocateMeshDataMatrix(); + } + //=================================================================================================// + void CellLinkedList::UpdateCellLists() + { + clearCellLists(); + StdLargeVec &pos_n = base_particles_->pos_n_; + size_t total_real_particles = base_particles_->total_real_particles_; + parallel_for( + blocked_range(0, total_real_particles), + [&](const blocked_range &r) + { + for (size_t i = r.begin(); i != r.end(); ++i) + { + insertACellLinkedParticleIndex(i, pos_n[i]); + } + }, + ap); + UpdateCellListData(); + updateSplitCellLists(sph_body_.split_cell_lists_); + } + //=================================================================================================// + void CellLinkedList::assignBaseParticles(BaseParticles *base_particles) + { + base_particles_ = base_particles; + }; + //=================================================================================================// + void CellLinkedList::computingSequence(StdLargeVec &sequence) + { + StdLargeVec &positions = base_particles_->pos_n_; + size_t total_real_particles = base_particles_->total_real_particles_; + parallel_for( + blocked_range(0, total_real_particles), + [&](const blocked_range &r) + { + for (size_t i = r.begin(); i != r.end(); ++i) + { + sequence[i] = transferMeshIndexToMortonOrder(CellIndexFromPosition(positions[i])); + } + }, + ap); + } + //=================================================================================================// + MultilevelCellLinkedList:: + MultilevelCellLinkedList(BoundingBox tentative_bounds, Real reference_grid_spacing, + size_t total_levels, Real maximum_spacing_ratio, + SPHBody &sph_body, ParticleAdaptation &particle_adaptation) + : MultilevelMesh(tentative_bounds, + reference_grid_spacing, total_levels, + maximum_spacing_ratio, sph_body, + particle_adaptation), + h_ratio_(dynamic_cast(particle_adaptation).h_ratio_) {} + //=================================================================================================// + size_t MultilevelCellLinkedList::getMeshLevel(Real particle_cutoff_radius) + { + for (size_t level = total_levels_; level != 0; --level) + if (particle_cutoff_radius - mesh_levels_[level - 1]->GridSpacing() < Eps) + return level - 1; //jump out the loop! + + std::cout << "\n Error: CellLinkedList level searching out of bound!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + return 999; //means an error in level searching + }; + //=================================================================================================// + void MultilevelCellLinkedList:: + insertACellLinkedParticleIndex(size_t particle_index, const Vecd &particle_position) + { + size_t level = getMeshLevel(kernel_.CutOffRadius(h_ratio_[particle_index])); + mesh_levels_[level]->insertACellLinkedParticleIndex(particle_index, particle_position); + } + //=================================================================================================// + void MultilevelCellLinkedList:: + InsertACellLinkedListDataEntry(size_t particle_index, const Vecd &particle_position) + { + size_t level = getMeshLevel(kernel_.CutOffRadius(h_ratio_[particle_index])); + mesh_levels_[level]->InsertACellLinkedListDataEntry(particle_index, particle_position); + } + //=================================================================================================// + void MultilevelCellLinkedList::assignBaseParticles(BaseParticles *base_particles) + { + base_particles_ = base_particles; + for (size_t l = 0; l != total_levels_; ++l) + { + mesh_levels_[l]->assignBaseParticles(base_particles); + } + }; + //=================================================================================================// + void MultilevelCellLinkedList::UpdateCellLists() + { + for (size_t level = 0; level != total_levels_; ++level) + mesh_levels_[level]->clearCellLists(); + + StdLargeVec &pos_n = base_particles_->pos_n_; + size_t total_real_particles = base_particles_->total_real_particles_; + //rebuild the corresponding particle list. + parallel_for( + blocked_range(0, total_real_particles), + [&](const blocked_range &r) + { + for (size_t i = r.begin(); i != r.end(); ++i) + { + insertACellLinkedParticleIndex(i, pos_n[i]); + } + }, + ap); + + for (size_t level = 0; level != total_levels_; ++level) + mesh_levels_[level]->UpdateCellListData(); + updateSplitCellLists(sph_body_.split_cell_lists_); + } + //=================================================================================================// + void MultilevelCellLinkedList:: + tagBodyPartByCell(CellLists &cell_lists, std::function &check_included) + { + for (size_t l = 0; l != total_levels_; ++l) + { + mesh_levels_[l]->tagBodyPartByCell(cell_lists, check_included); + } + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/meshes/cell_linked_list.h b/SPHINXsys/src/shared/meshes/cell_linked_list.h new file mode 100644 index 0000000000..4f693549e6 --- /dev/null +++ b/SPHINXsys/src/shared/meshes/cell_linked_list.h @@ -0,0 +1,187 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ + +/** +* @file cell_linked_list.h +* @brief Here gives the classes for managing cell linked lists. This is the basic class +* for building the particle configurations. +* @details The cell linked list saves for each body a list of particles +* located within the cell. +* @author Yongchuan Yu, Chi ZHang and Xiangyu Hu +*/ + +#ifndef MESH_CELL_LINKED_LIST_H +#define MESH_CELL_LINKED_LIST_H + +#include "base_mesh.h" +#include "neighbor_relation.h" + +namespace SPH +{ + + class SPHSystem; + class SPHBody; + class BaseParticles; + class Kernel; + + /** + * @class CellList + * @brief The linked list for one cell + */ + class CellList + { + public: + /** using concurrent vectors due to writting conflicts when building the list */ + ConcurrentIndexVector concurrent_particle_indexes_; + /** non-concurrent cell linked list rewritten for building neighbor list */ + ListDataVector cell_list_data_; + /** the index vector for real particles. */ + IndexVector real_particle_indexes_; + + CellList(); + ~CellList(){}; + }; + + /** + * @class BaseCellLinkedList + * @brief Abstract class for mesh cell linked list. + */ + class BaseCellLinkedList : public BaseMeshField + { + protected: + SPHBody &sph_body_; + Kernel &kernel_; + BaseParticles *base_particles_; + + /** clear split cell lists in this mesh*/ + virtual void clearSplitCellLists(SplitCellLists &split_cell_lists); + /** update split particle list in this mesh */ + virtual void updateSplitCellLists(SplitCellLists &split_cell_lists) = 0; + + public: + /** The buffer size 2 used to expand computational domian for particle searching. */ + BaseCellLinkedList(SPHBody &sph_body, ParticleAdaptation &particle_adaptation); + virtual ~BaseCellLinkedList(){}; + + /** Assign base particles to the mesh cell linked list, + * and is important because particles are not defined in the constructor. */ + virtual void assignBaseParticles(BaseParticles *base_particles) = 0; + + /** update the cell lists */ + virtual void UpdateCellLists() = 0; + /** Insert a cell-linked_list entry to the concurrent index list. */ + virtual void insertACellLinkedParticleIndex(size_t particle_index, const Vecd &particle_position) = 0; + /** Insert a cell-linked_list entry of the index and particle position pair. */ + virtual void InsertACellLinkedListDataEntry(size_t particle_index, const Vecd &particle_position) = 0; + /** find the nearest list data entry */ + virtual ListData findNearestListDataEntry(const Vecd &position) = 0; + /** computing the sequence which indicate the order of sorted particle data */ + virtual void computingSequence(StdLargeVec &sequence) = 0; + /** Tag body part by cell, call by body part */ + virtual void tagBodyPartByCell(CellLists &cell_lists, std::function &check_included) = 0; + /** Tag domain bounding cells in an axis direction, called by domain bounding classes */ + virtual void tagBodyDomainBoundingCells(StdVec &cell_lists, BoundingBox &body_domain_bounds, int axis) = 0; + /** Tag mirror bounding cells, called by mirror boundary condition */ + virtual void tagMirrorBoundingCells(CellLists &cell_lists, BoundingBox &body_domain_bounds, int axis, bool positive) = 0; + }; + + /** + * @class CellLinkedList + * @brief Defining a mesh cell linked list for a body. + * The meshes for all bodies share the same global coordinates. + */ + class CellLinkedList : public BaseCellLinkedList, public Mesh + { + protected: + /** The array for of mesh cells, i.e. mesh data. + * Within each cell, a list is saved with the indexes of particles.*/ + MeshDataMatrix cell_linked_lists_; + + virtual void updateSplitCellLists(SplitCellLists &split_cell_lists) override; + + public: + CellLinkedList(BoundingBox tentative_bounds, Real grid_spacing, + SPHBody &sph_body, ParticleAdaptation &particle_adaptation); + virtual ~CellLinkedList() { deleteMeshDataMatrix(); }; + + virtual void allocateMeshDataMatrix() override; + virtual void deleteMeshDataMatrix() override; + virtual void assignBaseParticles(BaseParticles *base_particles) override; + + void clearCellLists(); + void UpdateCellListData(); + virtual void UpdateCellLists() override; + void insertACellLinkedParticleIndex(size_t particle_index, const Vecd &particle_position) override; + void InsertACellLinkedListDataEntry(size_t particle_index, const Vecd &particle_position) override; + virtual ListData findNearestListDataEntry(const Vecd &position) override; + virtual void computingSequence(StdLargeVec &sequence) override; + virtual void tagBodyPartByCell(CellLists &cell_lists, std::function &check_included) override; + virtual void tagBodyDomainBoundingCells(StdVec &cell_lists, BoundingBox &body_domain_bounds, int axis) override; + virtual void tagMirrorBoundingCells(CellLists &cell_lists, BoundingBox &body_domain_bounds, int axis, bool positive) override; + virtual void writeMeshFieldToPlt(std::ofstream &output_file) override; + + /** generalized particle search algorithm */ + template + void searchNeighborsByParticles(size_t total_real_particles, BaseParticles &source_particles, + ParticleConfiguration &particle_configuration, GetParticleIndex &get_particle_index, + GetSearchDepth &get_search_depth, GetNeighborRelation &get_neighbor_relation); + + /** generalized particle search algorithm for searching body part */ + template + void searchNeighborPartsByParticles(size_t total_real_particles, BaseParticles &source_particles, + ParticleConfiguration &particle_configuration, GetParticleIndex &get_particle_index, + GetSearchDepth &get_search_depth, GetNeighborRelation &get_neighbor_relation, + PartParticleCheck &part_check); + }; + + /** + * @class MultilevelCellLinkedList + * @brief Defining a multilevel mesh cell linked list for a body + * for multiresolution particle configuration. + */ + class MultilevelCellLinkedList : public MultilevelMesh + { + protected: + StdLargeVec &h_ratio_; + virtual void updateSplitCellLists(SplitCellLists &split_cell_lists) override{}; + /** determine mesh level from particle cutoff radius */ + inline size_t getMeshLevel(Real particle_cutoff_radius); + + public: + MultilevelCellLinkedList(BoundingBox tentative_bounds, Real reference_grid_spacing, + size_t total_levels, Real maximum_spacing_ratio, + SPHBody &sph_body, ParticleAdaptation &particle_adaptation); + virtual ~MultilevelCellLinkedList(){}; + + virtual void assignBaseParticles(BaseParticles *base_particles) override; + virtual void UpdateCellLists() override; + void insertACellLinkedParticleIndex(size_t particle_index, const Vecd &particle_position) override; + void InsertACellLinkedListDataEntry(size_t particle_index, const Vecd &particle_position) override; + virtual ListData findNearestListDataEntry(const Vecd &position) override { return ListData(0, Vecd(0)); }; + virtual void computingSequence(StdLargeVec &sequence) override{}; + virtual void tagBodyPartByCell(CellLists &cell_lists, std::function &check_included) override; + virtual void tagBodyDomainBoundingCells(StdVec &cell_lists, BoundingBox &body_domain_bounds, int axis) override{}; + virtual void tagMirrorBoundingCells(CellLists &cell_lists, BoundingBox &body_domain_bounds, int axis, bool positive) override{}; + }; +} +#endif //MESH_CELL_LINKED_LIST_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/meshes/mesh_with_data_packages.h b/SPHINXsys/src/shared/meshes/mesh_with_data_packages.h new file mode 100644 index 0000000000..c656a908c0 --- /dev/null +++ b/SPHINXsys/src/shared/meshes/mesh_with_data_packages.h @@ -0,0 +1,255 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file base_mesh.h +* @brief This is the base classes of mesh, which describe ordered and indexed +* data sets. Depending on application, there are different data +* saved on the mesh. The intersection points of mesh lines are called +* grid points, the element enclosed by mesh lines (2D) or faces (3D) called +* cells. The mesh line or face are also called cell faces. Grid points are +* also called cell corners. +* @author Chi ZHang and Xiangyu Hu +*/ + +#ifndef MESH_WITH_DATA_PACKAGES_H +#define MESH_WITH_DATA_PACKAGES_H + +#include "base_mesh.h" +#include "my_memory_pool.h" + +#include +#include +#include +#include +using namespace std::placeholders; + +namespace SPH +{ + class Kernel; + + /** Functor for operation on the mesh data package. */ + template + using PackageFunctor = std::function; + /** Iterator on a collection of mesh data packages. sequential computing. */ + template + void PackageIterator(ConcurrentVector &data_pkgs, + PackageFunctor &pkg_functor, Real dt = 0.0) + { + for (size_t i = 0; i != data_pkgs.size(); ++i) + pkg_functor(data_pkgs[i], dt); + }; + /** Iterator on a collection of mesh data packages. parallel computing. */ + template + void PackageIterator_parallel(ConcurrentVector &data_pkgs, + PackageFunctor &pkg_functor, Real dt = 0.0) + { + parallel_for( + blocked_range(0, data_pkgs.size()), + [&](const blocked_range &r) + { + for (size_t i = r.begin(); i != r.end(); ++i) + { + pkg_functor(data_pkgs[i], dt); + } + }, + ap); + }; + /** Package iterator for reducing. sequential computing. */ + template + ReturnType ReducePackageIterator(ConcurrentVector &data_pkgs, ReturnType temp, + PackageFunctor &reduce_pkg_functor, + ReduceOperation &reduce_operation, Real dt = 0.0) + { + for (size_t i = 0; i < data_pkgs.size(); ++i) + { + temp = reduce_operation(temp, reduce_pkg_functor(data_pkgs[i], dt)); + } + return temp; + }; + /** Package iterator for reducing. parallel computing. */ + template + ReturnType ReducePackageIterator_parallel(ConcurrentVector &data_pkgs, ReturnType temp, + PackageFunctor &reduce_pkg_functor, + ReduceOperation &reduce_operation, Real dt = 0.0) + { + return parallel_reduce( + blocked_range(0, data_pkgs.size()), + temp, [&](const blocked_range &r, ReturnType temp0) -> ReturnType + { + for (size_t i = r.begin(); i != r.end(); ++i) + { + temp0 = reduce_operation(temp, reduce_pkg_functor(data_pkgs[i], dt)); + } + return temp0; + }, + [&](ReturnType x, ReturnType y) -> ReturnType + { + return reduce_operation(x, y); + }); + }; + + /** + * @class BaseDataPackage + * @brief Abstract base class for a data package + * whose data are defined on the grids of a small mesh patch. + * note tha ADDRS_SIZE = PKG_SIZE + 2 * pkg_addrs_buffer_; + * Also note that, while the mesh lower bound locates the first data address, + * the data lower bound locates the first data. + */ + template + class BaseDataPackage : public BaseMesh + { + public: + Vecd data_lower_bound_; /**< lower bound coordinate for the data as reference */ + Vecu pkg_index_; /**< index of the inner packages in the mesh, 0 for far-field packages. */ + bool is_inner_pkg_; /**< If true, its data saved in memory pool. */ + /** define package data type */ + template + using PackageData = PackageDataMatrix; + /** define package data address type */ + template + using PackageDataAddress = PackageDataMatrix; + /** define matrix data for temporary usage*/ + template + using PackageTemporaryData = PackageDataMatrix; + + BaseDataPackage() : BaseMesh(Vecu(ADDRS_SIZE)), + data_lower_bound_(0), pkg_index_(0), is_inner_pkg_(false){}; + virtual ~BaseDataPackage(){}; + + constexpr int PackageSize() { return PKG_SIZE; }; + constexpr int AddressSize() { return ADDRS_SIZE; }; + constexpr int AddressBufferWidth() { return (ADDRS_SIZE - PKG_SIZE) / 2; }; + constexpr int OperationUpperBound() { return PKG_SIZE + AddressBufferWidth(); }; + /** initialize package mesh geometric information. */ + void initializePackageGeometry(Vecd &pkg_lower_bound, Real data_spacing) + { + mesh_lower_bound_ = pkg_lower_bound - Vecd(data_spacing) * ((Real)AddressBufferWidth() - 0.5); + grid_spacing_ = data_spacing; + data_lower_bound_ = pkg_lower_bound + Vecd(data_spacing) * 0.5; + }; + /** This function probes by applying Bi and tri-linear interpolation within the package. */ + template + DataType probeDataPackage(PackageDataAddress &pkg_data_addrs, const Vecd &position); + /** This function compute gradient transform within data package */ + template + void computeGradient(PackageDataAddress &in_pkg_data_addrs, + PackageDataAddress out_pkg_data_addrs, Real dt = 0.0); + /** This function compute normalized gradient transform within data package */ + template + void computeNormalizedGradient(PackageDataAddress &in_pkg_data_addrs, + PackageDataAddress out_pkg_data_addrs, Real dt = 0.0); + + protected: + /** initialize package data address within a derived class constructor */ + template + void initializePackageDataAddress(PackageData &pkg_data, + PackageDataAddress &pkg_data_addrs); + /** assign address for a package data when the package is an inner one */ + template + void assignPackageDataAddress(PackageDataAddress &pkg_data_addrs, Vecu &addrs_index, + PackageData &pkg_data, Vecu &data_index); + /** obtain averaged value at a corner of a data cell */ + template + DataType CornerAverage(PackageDataAddress &pkg_data_addrs, Veci addrs_index, Veci corner_direction); + }; + + /** + * @class MeshWithDataPackages + * @brief Abstract class for mesh with data packages + * @details The idea is to save sparse data on a cell-based mesh. + * We say sparse data, it means that only in some inner mesh cells there are no trivial data. + * A typical example is a level set field which only has meaningful values near the interface, + * while the latter is in the inner region of a mesh. + * In this class, only some inner mesh cells are filled with data packages. + * Each data package is again a mesh, but grid based, where two sets of data are saved on its grid points. + * One is the field data of matrices with PKG_SIZE, the other is corresponding address data of matrices with ADDRS_SIZE. + * For two neighboring data packages, they share the data in the buffer which is in the overlap region. + * The filling of field data is achieved first by the data matrices by the function initializeDataInACell + * and then the address matrix by the function initializeAddressesInACell. + * All these data packages are indexed by a concurrent vector inner_data_pkgs_. + * Note that a data package should be not near the mesh bound, otherwise one will encouter the error "out of range". + */ + template + class MeshWithDataPackages : public MeshFieldType, public Mesh + { + public: + MyMemoryPool data_pkg_pool_; /**< memory pool for all packages in the mesh. */ + MeshDataMatrix data_pkg_addrs_; /**< Address of data packages. */ + ConcurrentVector inner_data_pkgs_; /**< Inner data packages which is able to carry out spatial operations. */ + + virtual void allocateMeshDataMatrix() override; /**< allocate memories for addresses of data packages. */ + virtual void deleteMeshDataMatrix() override; /**< delete memories for addresses of data packages. */ + + template + explicit MeshWithDataPackages(BoundingBox tentative_bounds, Real data_spacing, size_t buffer_size, Args &&...args) + : MeshFieldType(std::forward(args)...), + Mesh(tentative_bounds, DataPackageType().PackageSize() * data_spacing, buffer_size), + data_spacing_(data_spacing), + pkg_size_((int)DataPackageType().PackageSize()), + pkg_addrs_buffer_((int)DataPackageType().AddressBufferWidth()), + pkg_operations_(pkg_size_ + pkg_addrs_buffer_), + pkg_addrs_size_(pkg_size_ + 2 * pkg_addrs_buffer_), + global_mesh_(this->mesh_lower_bound_ + Vecd(data_spacing) * 0.5, data_spacing, this->number_of_cells_ * pkg_size_) + { + allocateMeshDataMatrix(); + }; + virtual ~MeshWithDataPackages() { deleteMeshDataMatrix(); }; + + /** This function probe a mesh value */ + template + DataType probeMesh(const Vecd &position); + + protected: + Real data_spacing_; /**< spacing of data in the data packages*/ + int pkg_size_; /**< the size of the data package matrix*/ + int pkg_addrs_buffer_; /**< the size of address buffer, a value less than the package size. */ + int pkg_operations_; /**< the size of operation loops. */ + int pkg_addrs_size_; /**< the size of address matrix in the data packages. */ + StdVec singular_data_pkgs_addrs; /**< singular data packages. prodvied for far field condition. */ + std::mutex mutex_my_pool; /**< mutex exclusion for memory pool */ + BaseMesh global_mesh_; /**< the mesh for the locations of all possible data points. */ + + virtual void initializeDataInACell(const Vecu &cell_index, Real dt) = 0; + virtual void initializeAddressesInACell(const Vecu &cell_index, Real dt) = 0; + /** This function tag if a data package is inner package. */ + virtual void tagACellIsInnerPackage(const Vecu &cell_index, Real dt) = 0; + /** This function initialize the data packages with external information */ + virtual void initializeDataPackages() = 0; + + /** This function find the value of data from its index from global mesh. */ + template + DataType DataValueFromGlobalIndex(Vecu global_grid_index); + void initializePackageAddressesInACell(Vecu cell_index); + /** find related cell index and data index for a data package address matrix */ + std::pair CellShiftAndDataIndex(int data_addrs_index_component) + { + std::pair shift_and_index; + int signed_date_index = data_addrs_index_component - pkg_addrs_buffer_; + shift_and_index.first = (signed_date_index + pkg_size_) / pkg_size_ - 1; + shift_and_index.second = signed_date_index - shift_and_index.first * pkg_size_; + return shift_and_index; + } + }; +} +#endif //MESH_WITH_DATA_PACKAGES_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/meshes/my_memory_pool.h b/SPHINXsys/src/shared/meshes/my_memory_pool.h new file mode 100644 index 0000000000..e6d036ba28 --- /dev/null +++ b/SPHINXsys/src/shared/meshes/my_memory_pool.h @@ -0,0 +1,62 @@ +#ifndef MY_MEMORY_POOL_H +#define MY_MEMORY_POOL_H + +#define TBB_PREVIEW_MEMORY_POOL 1 + +#include "tbb/memory_pool.h" +#include "tbb/enumerable_thread_specific.h" + +#include + +using namespace tbb; +//------------------------------------------------------------------------------------------------- +//my memory pool +//------------------------------------------------------------------------------------------------- +template +class MyMemoryPool { + T sample; + tbb::memory_pool< std::allocator > my_pool; //memory pool + typedef tbb::memory_pool_allocator pool_allocator_t; //memory allocator + std::list data_list; //list of all nodes allocated + std::list free_list; //list of all free nodes + +public: + + //constructor + MyMemoryPool() : data_list((pool_allocator_t(my_pool))) {}; + //deconstructor + ~MyMemoryPool() { + //my_pool.recycle(); + }; + //prepare an avaliable node + T* malloc() + { + if (free_list.empty()) { + data_list.push_back(sample); + return (&data_list.back()); + } + else + { + T* result = free_list.front(); + free_list.pop_front(); + return result; + } + }; + //relinquish an unused node + void free(T* ptr) + { + free_list.push_back(ptr); + }; + //return the total number of nodes allocated + int capicity() + { + return data_list.size(); + }; + //return the number of current available nodes + int available_node() + { + return free_list.size(); + }; +}; + +#endif //MY_MEMORY_POOL_H diff --git a/SPHINXsys/src/shared/particle_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/active_muscle_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/active_muscle_dynamics.cpp new file mode 100644 index 0000000000..07e14a8020 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/active_muscle_dynamics.cpp @@ -0,0 +1,57 @@ +/** + * @file active_muscle_dynamics.cpp + * @brief In is file, we define functions decleared in active muscle dynamics.h + * @author Chi Zhang and Xiangyu Hu + */ +#include "active_muscle_dynamics.h" + +using namespace SimTK; + +namespace SPH +{ + namespace active_muscle_dynamics + { + //=================================================================================================// + MuscleActivation:: + MuscleActivation(SolidBody* body) : + ParticleDynamicsSimple(body), ActiveMuscleDataDelegateSimple(body), + pos_0_(particles_->pos_0_), active_contraction_stress_(particles_->active_contraction_stress_) {}; + //=================================================================================================// + SpringConstrainMuscleRegion:: + SpringConstrainMuscleRegion(SolidBody* body, BodyPartByParticle* body_part) : + PartSimpleDynamicsByParticle(body, body_part), + ActiveMuscleDataDelegateSimple(body), mass_(particles_->mass_), + pos_n_(particles_->pos_n_), pos_0_(particles_->pos_0_), + vel_n_(particles_->vel_n_) {} + //=================================================================================================// + Vecd SpringConstrainMuscleRegion::getAcceleration(Vecd &disp, Real mass) + { + Vecd spring_force(0); + for(int i = 0; i < disp.size(); i++) + { + spring_force[i] = -stiffness_[i] * disp[i] / mass; + } + return spring_force; + } + //=================================================================================================// + void SpringConstrainMuscleRegion::Update(size_t index_i, Real dt) + { + Vecd disp_from_0 = pos_n_[index_i] - pos_0_[index_i]; + vel_n_[index_i] += dt * getAcceleration(disp_from_0, mass_[index_i]); + pos_n_[index_i] += dt * dt * getAcceleration(disp_from_0, mass_[index_i]); + } + //=================================================================================================// + ImposingStress:: + ImposingStress(SolidBody* body, SolidBodyPartForSimbody* body_part) : + PartSimpleDynamicsByParticle(body, body_part), + ActiveMuscleDataDelegateSimple(body), + pos_0_(particles_->pos_0_), active_stress_(particles_->active_stress_) {} + //=================================================================================================// + void ImposingStress + ::Update(size_t index_i, Real dt) + { + active_stress_[index_i] = getStress(pos_0_[index_i]); + } + //=================================================================================================// + } +} diff --git a/SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/active_muscle_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/active_muscle_dynamics.h new file mode 100644 index 0000000000..057122c01c --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/active_muscle_dynamics/active_muscle_dynamics.h @@ -0,0 +1,106 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file active_muscle_dynamics.h + * @brief In is file, we declear muscle dynamics which is driven by an external injection of energy. + * @author Chi Zhang and Xiangyu Hu + * @version 0.3.1 + * Here, we need identify the physical differences between electrophysiology and active muscle. + * The former is on the diffusion and electro-chemical reaction happens in tissue. + * The latter is for muscle dynamics which is driven by an external injection of energy. + * As the naming of class, function and variables in this code need be based on physics. + * We will identify physical differences by properly choosing names. + * Xiangyu Hu + */ + +#ifndef ACTIVE_MUSCLE_DYNAMICS_H +#define ACTIVE_MUSCLE_DYNAMICS_H + + + +#include "all_particle_dynamics.h" +#include "elastic_solid.h" +#include "base_kernel.h" +#include "solid_body.h" +#include "solid_particles.h" + +namespace SPH +{ + namespace active_muscle_dynamics + { + typedef DataDelegateSimple ActiveMuscleDataDelegateSimple; + + /** + * @class MuscleActivation + * @brief impose cases specific muscle activation + * This is a abstract class to be override for case specific activation + */ + class MuscleActivation : + public ParticleDynamicsSimple, public ActiveMuscleDataDelegateSimple + { + public: + MuscleActivation(SolidBody *body); + virtual ~MuscleActivation() {}; + protected: + StdLargeVec& pos_0_; + StdLargeVec& active_contraction_stress_; + }; + + /**@class SpringConstrainMuscleRegion + * @brief Constrain a solid body part with a spring force + * towards each constrained particles' original position. + */ + class SpringConstrainMuscleRegion : + public PartSimpleDynamicsByParticle, public ActiveMuscleDataDelegateSimple + { + public: + SpringConstrainMuscleRegion(SolidBody *body, BodyPartByParticle*body_part); + virtual ~SpringConstrainMuscleRegion() {}; + void setUpSpringStiffness(Vecd stiffness){stiffness_ = stiffness;} + protected: + StdLargeVec& mass_; + StdLargeVec& pos_n_, & pos_0_, & vel_n_; + Vecd stiffness_; + virtual Vecd getAcceleration(Vecd& disp, Real mass); + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /**@class ImposingStress + * @brief impose activation stress on a solid body part + */ + class ImposingStress : + public PartSimpleDynamicsByParticle, public ActiveMuscleDataDelegateSimple + { + public: + ImposingStress(SolidBody *body, SolidBodyPartForSimbody *body_part); + virtual ~ImposingStress() {}; + protected: + StdLargeVec& pos_0_; + StdLargeVec& active_stress_; + + virtual Matd getStress(Vecd& pos) = 0; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + } +} +#endif //ACTIVE_MUSCLE_DYNAMICS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/all_particle_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/all_particle_dynamics.h new file mode 100644 index 0000000000..f3564a5dc2 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/all_particle_dynamics.h @@ -0,0 +1,9 @@ + +#ifndef ALL_PARTICLE_DYNAMICS_H +#define ALL_PARTICLE_DYNAMICS_H + + + +#include "particle_dynamics_algorithms.h" +#include "particle_dynamics_bodypart.h" +#endif //ALL_PARTICLE_DYNAMICS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/all_physical_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/all_physical_dynamics.h new file mode 100644 index 0000000000..11b8fd9bbb --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/all_physical_dynamics.h @@ -0,0 +1,25 @@ + +#ifndef ALL_PHYSICAL_DYNAMICS_H +#define ALL_PHYSICAL_DYNAMICS_H + + + +/** @file +This is the header file that user code should include to pick up all +particle dynamics capabilities. **/ + +#include "external_force.h" +#include "general_dynamics.h" +#include "all_fluid_dynamics.h" +#include "all_solid_dynamics.h" +#include "observer_dynamics.h" +#include "relax_dynamics.h" +#include "electro_physiology.h" +#include "active_muscle_dynamics.h" +#include "particle_dynamics_diffusion_reaction.h" +#include "particle_dynamics_diffusion_reaction.hpp" +#include "particle_dynamics_dissipation.h" +#include "particle_dynamics_dissipation.hpp" + + +#endif //ALL_PHYSICAL_DYNAMICS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.cpp new file mode 100644 index 0000000000..0e7530ffc4 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.cpp @@ -0,0 +1,99 @@ +/** + * @file base_particle_dynamics.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ +#include "base_particle_dynamics.h" +#include "base_particle_dynamics.hpp" +//=============================================================================================// +namespace SPH +{ + Real GlobalStaticVariables::physical_time_ = 0.0; + //=============================================================================================// + void ParticleIterator(size_t total_real_particles, ParticleFunctor &particle_functor, Real dt) + { + for (size_t i = 0; i < total_real_particles; ++i) + particle_functor(i, dt); + } + //=============================================================================================// + void ParticleIterator_parallel(size_t total_real_particles, ParticleFunctor &particle_functor, Real dt) + { + parallel_for(blocked_range(0, total_real_particles), + [&](const blocked_range& r) { + for (size_t i = r.begin(); i < r.end(); ++i) { + particle_functor(i, dt); + } + }, ap); + } + //=================================================================================================// + void ParticleIteratorSplittingSweep(SplitCellLists& split_cell_lists, + ParticleFunctor& particle_functor, Real dt) + { + Real dt2 = dt * 0.5; + //forward sweeping + for (size_t k = 0; k != split_cell_lists.size(); ++k) { + ConcurrentCellLists& cell_lists = split_cell_lists[k]; + for (size_t l = 0; l != cell_lists.size(); ++l) + { + IndexVector& particle_indexes + = cell_lists[l]->real_particle_indexes_; + for (size_t i = 0; i != particle_indexes.size(); ++i) + { + particle_functor(particle_indexes[i], dt2); + } + } + } + + //backward sweeping + for (size_t k = split_cell_lists.size(); k != 0; --k) { + ConcurrentCellLists& cell_lists = split_cell_lists[k - 1]; + for (size_t l = 0; l != cell_lists.size(); ++l) + { + IndexVector& particle_indexes + = cell_lists[l]->real_particle_indexes_; + for (size_t i = particle_indexes.size(); i != 0; --i) + { + particle_functor(particle_indexes[i - 1], dt2); + } + } + } + } + //=================================================================================================// + void ParticleIteratorSplittingSweep_parallel(SplitCellLists& split_cell_lists, + ParticleFunctor &particle_functor, Real dt) + { + Real dt2 = dt * 0.5; + //forward sweeping + for (size_t k = 0; k != split_cell_lists.size(); ++k) { + ConcurrentCellLists& cell_lists = split_cell_lists[k]; + parallel_for(blocked_range(0, cell_lists.size()), + [&](const blocked_range& r) { + for (size_t l = r.begin(); l < r.end(); ++l) { + IndexVector& particle_indexes + = cell_lists[l]->real_particle_indexes_; + for (size_t i = 0; i < particle_indexes.size(); ++i) + { + particle_functor(particle_indexes[i], dt2); + } + } + }, ap); + } + + //backward sweeping + for (size_t k = split_cell_lists.size(); k != 0; --k) { + ConcurrentCellLists& cell_lists = split_cell_lists[k - 1]; + parallel_for(blocked_range(0, cell_lists.size()), + [&](const blocked_range& r) { + for (size_t l = r.begin(); l < r.end(); ++l) { + IndexVector& particle_indexes + = cell_lists[l]->real_particle_indexes_; + for (size_t i = particle_indexes.size(); i != 0; --i) + { + particle_functor(particle_indexes[i - 1], dt2); + } + } + }, ap); + } + } + //=============================================================================================// +} +//=============================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.h new file mode 100644 index 0000000000..4cfdd96a22 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.h @@ -0,0 +1,272 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file base_particle_dynamics.h +* @brief This is for the base classes of particle dynamics, which describe the +* interaction between particles. These interactions are used to define +* differential operators for surface forces or fluxes in continuum mechanics +* @author Xiangyu Hu, Luhui Han and Chi Zhang +*/ + +#ifndef BASE_PARTICLE_DYNAMICS_H +#define BASE_PARTICLE_DYNAMICS_H + + +#include "base_data_package.h" +#include "sph_data_conainers.h" +#include "all_particles.h" +#include "all_materials.h" +#include "neighbor_relation.h" +#include "all_bodies.h" +#include "cell_linked_list.h" +#include "external_force.h" +#include "body_relation.h" +#include + +using namespace std::placeholders; + +namespace SPH +{ + /** Functor for operation on particles. */ + typedef std::function ParticleFunctor; + /** Functors for reducing operation on particles. */ + template + using ReduceFunctor = std::function; + + /** Iterators for particle functors. sequential computing. */ + void ParticleIterator(size_t total_real_particles, ParticleFunctor &particle_functor, Real dt = 0.0); + /** Iterators for particle functors. parallel computing. */ + void ParticleIterator_parallel(size_t total_real_particles, ParticleFunctor &particle_functor, Real dt = 0.0); + + /** Iterators for reduce functors. sequential computing. */ + template + ReturnType ReduceIterator(size_t total_real_particles, ReturnType temp, + ReduceFunctor &reduce_functor, ReduceOperation &reduce_operation, Real dt = 0.0); + /** Iterators for reduce functors. parallel computing. */ + template + ReturnType ReduceIterator_parallel(size_t total_real_particles, ReturnType temp, + ReduceFunctor &reduce_functor, ReduceOperation &reduce_operation, Real dt = 0.0); + + /** Iterators for particle functors with splitting. sequential computing. */ + void ParticleIteratorSplittingSweep(SplitCellLists& split_cell_lists, + ParticleFunctor& particle_functor, Real dt = 0.0); + /** Iterators for particle functors with splitting. parallel computing. */ + void ParticleIteratorSplittingSweep_parallel(SplitCellLists& split_cell_lists, + ParticleFunctor& particle_functor, Real dt = 0.0); + + + /** A Functor for Summation */ + template + struct ReduceSum { ReturnType operator () (const ReturnType& x, const ReturnType& y) const { return x + y; }; }; + /** A Functor for Maximum */ + struct ReduceMax { Real operator () (Real x, Real y) const { return SMAX(x, y); }; }; + /** A Functor for Minimum */ + struct ReduceMin { Real operator () (Real x, Real y) const { return SMIN(x, y); }; }; + /** A Functor for OR operator */ + struct ReduceOR { bool operator () (bool x, bool y) const { return x || y; }; }; + /** A Functor for lower bound */ + struct ReduceLowerBound { + Vecd operator () (const Vecd& x, const Vecd& y) const { + Vecd lower_bound; + for (int i = 0; i < lower_bound.size(); ++i) lower_bound[i] = SMIN(x[i], y[i]); + return lower_bound; + }; + }; + /** A Functor for upper bound */ + struct ReduceUpperBound { + Vecd operator () (const Vecd& x, const Vecd& y) const { + Vecd upper_bound; + for (int i = 0; i < upper_bound.size(); ++i) upper_bound[i] = SMAX(x[i], y[i]); + return upper_bound; + }; + }; + + /** + * @class GlobalStaticVariables + * @brief A place to put all global variables + */ + class GlobalStaticVariables + { + public: + explicit GlobalStaticVariables() {}; + virtual ~GlobalStaticVariables() {}; + + /** the physical time is global value for all dynamics */ + static Real physical_time_; + }; + + /** + * @class ParticleDynamics + * @brief The base class for all particle dynamics + * This class contains the only two interface functions available + * for particle dynamics. An specific implementation should be realized. + */ + template + class ParticleDynamics : public GlobalStaticVariables + { + public: + explicit ParticleDynamics(SPHBody* sph_body) + : GlobalStaticVariables(), sph_body_(sph_body), + particle_adaptation_(sph_body->particle_adaptation_), + base_particles_(sph_body->base_particles_) {}; + virtual ~ParticleDynamics() {}; + + SPHBody* getSPHBody() { return sph_body_; }; + /** The only two functions can be called from outside + * One is for sequential execution, the other is for parallel. */ + virtual ReturnType exec(Real dt = 0.0) = 0; + virtual ReturnType parallel_exec(Real dt = 0.0) = 0; + protected: + SPHBody* sph_body_; + ParticleAdaptation* particle_adaptation_; + BaseParticles* base_particles_; + + void setBodyUpdated() { sph_body_->setNewlyUpdated(); }; + /** the function for set global parameters for the particle dynamics */ + virtual void setupDynamics(Real dt = 0.0) {}; + }; + + /** + * @class DataDelegateBase + * @brief empty base class mixin template. + */ + class DataDelegateEmptyBase + { + public: + explicit DataDelegateEmptyBase(SPHBody* sph_body) {}; + virtual ~DataDelegateEmptyBase() {}; + }; + + /** + * @class DataDelegateSimple + * @brief prepare data for simple particle dynamics. + */ + template + class DataDelegateSimple + { + public: + explicit DataDelegateSimple(SPHBody* body) : + body_(dynamic_cast(body)), + particles_(dynamic_cast(body->base_particles_)), + material_(dynamic_cast(body->base_particles_->base_material_)), + sorted_id_(body_->base_particles_->sorted_id_), + unsorted_id_(body_->base_particles_->unsorted_id_) {}; + virtual ~DataDelegateSimple() {}; + protected: + BodyType* body_; + ParticlesType* particles_; + MaterialType* material_; + StdLargeVec& sorted_id_; + StdLargeVec& unsorted_id_; + }; + + /** + * @class DataDelegateInner + * @brief prepare data for inner particle dynamics + */ + template > + class DataDelegateInner : public BaseDataDelegateType + { + public: + explicit DataDelegateInner(BaseBodyRelationInner* body_inner_relation) : + BaseDataDelegateType(body_inner_relation->sph_body_), + inner_configuration_(body_inner_relation->inner_configuration_) {}; + virtual ~DataDelegateInner() {}; + protected: + /** inner configuration of the designated body */ + ParticleConfiguration& inner_configuration_; + }; + + /** + * @class DataDelegateContact + * @brief prepare data for contact particle dynamics + */ + template > + class DataDelegateContact : public BaseDataDelegateType + { + public: + explicit DataDelegateContact(BaseBodyRelationContact* body_contact_relation); + virtual ~DataDelegateContact() {}; + protected: + StdVec contact_bodies_; + StdVec contact_particles_; + StdVec contact_material_; + /** Configurations for particle interaction between bodies. */ + StdVec contact_configuration_; + }; + + /** + * @class DataDelegateComplex + * @brief prepare data for complex particle dynamics + */ + template + class DataDelegateComplex : + public DataDelegateInner, + public DataDelegateContact + { + public: + explicit DataDelegateComplex(ComplexBodyRelation* body_complex_relation) : + DataDelegateInner(body_complex_relation->inner_relation_), + DataDelegateContact(body_complex_relation->contact_relation_) {}; + virtual ~DataDelegateComplex() {}; + }; + + /** + * @class ParticleDynamicsComplex + * @brief particle dynamics by considering contribution from extra contact bodies + */ + template + class ParticleDynamicsComplex : public ParticleDynamicsInnerType, public ContactDataType + { + public: + ParticleDynamicsComplex(BaseBodyRelationInner* inner_relation, + BaseBodyRelationContact* contact_relation) : + ParticleDynamicsInnerType(inner_relation), ContactDataType(contact_relation) {}; + + ParticleDynamicsComplex(ComplexBodyRelation* complex_relation, + BaseBodyRelationContact* extra_contact_relation); + + virtual ~ParticleDynamicsComplex() {}; + protected: + virtual void prepareContactData() = 0; + }; +} +#endif //BASE_PARTICLE_DYNAMICS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.hpp b/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.hpp new file mode 100644 index 0000000000..7bfb8bc779 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.hpp @@ -0,0 +1,95 @@ +/** +* @file base_particle_dynamics.hpp +* @brief This is the implementation of the template class for 3D build +* @author Chi ZHang and Xiangyu Hu +*/ + +#ifndef BASE_PARTICLE_DYNAMICS_HPP +#define BASE_PARTICLE_DYNAMICS_HPP + + + +#include "base_particle_dynamics.h" +//=================================================================================================// +namespace SPH { + //=================================================================================================// + template + DataDelegateContact + ::DataDelegateContact(BaseBodyRelationContact* body_contact_relation) : + BaseDataDelegateType(body_contact_relation->sph_body_) + { + RealBodyVector contact_sph_bodies = body_contact_relation->contact_bodies_; + for (size_t i = 0; i != contact_sph_bodies.size(); ++i) { + contact_bodies_.push_back(dynamic_cast(contact_sph_bodies[i])); + contact_particles_.push_back(dynamic_cast(contact_sph_bodies[i]->base_particles_)); + contact_material_.push_back(dynamic_cast(contact_sph_bodies[i]->base_particles_->base_material_)); + contact_configuration_.push_back(&body_contact_relation->contact_configuration_[i]); + } + } + //=================================================================================================// + template + ReturnType ReduceIterator(size_t total_real_particles, ReturnType temp, + ReduceFunctor& reduce_functor, ReduceOperation& reduce_operation, Real dt) + { + for (size_t i = 0; i < total_real_particles; ++i) + { + temp = reduce_operation(temp, reduce_functor(i, dt)); + } + return temp; + } + //=================================================================================================// + template + ReturnType ReduceIterator_parallel(size_t total_real_particles, ReturnType temp, + ReduceFunctor& reduce_functor, ReduceOperation& reduce_operation, Real dt) + { + return parallel_reduce(blocked_range(0, total_real_particles), + temp, [&](const blocked_range& r, ReturnType temp0)->ReturnType { + for (size_t i = r.begin(); i != r.end(); ++i) { + temp0 = reduce_operation(temp0, reduce_functor(i, dt)); + } + return temp0; + }, + [&](ReturnType x, ReturnType y)->ReturnType { + return reduce_operation(x, y); + } + ); + } + //=================================================================================================// + template + ParticleDynamicsComplex:: + ParticleDynamicsComplex(ComplexBodyRelation* complex_relation, + BaseBodyRelationContact* extra_contact_relation) : + ParticleDynamicsInnerType(complex_relation->inner_relation_), + ContactDataType(complex_relation->contact_relation_) + { + if (complex_relation->sph_body_ != extra_contact_relation->sph_body_) + { + std::cout << "\n Error: the two body_realtions do not have the same source body!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + + for (auto& extra_body : extra_contact_relation->contact_bodies_) + { + // here we first obtain the pointer to the most derived class and then implicitly downcast it to + // the types defined in the base complex dynamics + this->contact_bodies_.push_back(extra_body->ThisObjectPtr()); + this->contact_particles_.push_back(extra_body->base_particles_->ThisObjectPtr()); + this->contact_material_.push_back(extra_body->base_particles_->base_material_->ThisObjectPtr()); + } + + for (size_t i = 0; i != extra_contact_relation->contact_bodies_.size(); ++i) + { + this->contact_configuration_.push_back(&extra_contact_relation->contact_configuration_[i]); + } + } + //=================================================================================================// +} +//=================================================================================================// +#endif //BASE_PARTICLE_DYNAMICS_HPP \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/particle_dynamics_diffusion_reaction.h b/SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/particle_dynamics_diffusion_reaction.h new file mode 100644 index 0000000000..fe89730c06 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/particle_dynamics_diffusion_reaction.h @@ -0,0 +1,297 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file particle_dynamics_diffusion_reaction.h +* @brief This is the particle dynamics aplliable for all type bodies +* @author Xiaojing Tang, Chi ZHang and Xiangyu Hu +*/ + + +#ifndef PARTICLE_DYNAMICS_DIFFUSION_REACTION_H +#define PARTICLE_DYNAMICS_DIFFUSION_REACTION_H + + + +#include "all_particle_dynamics.h" +#include "diffusion_reaction_particles.h" +#include "diffusion_reaction.h" + + +namespace SPH +{ + template + using DiffusionReactionSimpleData = DataDelegateSimple, + DiffusionReactionMaterial>; + + template + using DiffusionReactionInnerData = DataDelegateInner, + DiffusionReactionMaterial>; + + template + using DiffusionReactionContactData = DataDelegateContact, + DiffusionReactionMaterial, + ContactBodyType, DiffusionReactionParticles, + ContactBaseMaterialType, DataDelegateEmptyBase>; + + /** + * @class DiffusionReactionInitialCondition + * @brief pure abstract class for initial conditions + */ + template + class DiffusionReactionInitialCondition : + public ParticleDynamicsSimple, + public DiffusionReactionSimpleData + { + public: + DiffusionReactionInitialCondition(BodyType* diffusion_body); + virtual ~DiffusionReactionInitialCondition() {}; + protected: + StdLargeVec& pos_n_; + StdVec>& species_n_; + }; + + /** + * @class GetDiffusionTimeStepSize + * @brief Computing the time step size based on diffusion coefficient and particle smoothing length + */ + template + class GetDiffusionTimeStepSize : + public ParticleDynamics, + public DiffusionReactionSimpleData + { + public: + explicit GetDiffusionTimeStepSize(BodyType* body); + virtual ~GetDiffusionTimeStepSize() {}; + + virtual Real exec(Real dt = 0.0) override { return diff_time_step_; }; + virtual Real parallel_exec(Real dt = 0.0) override { return exec(dt); }; + protected: + Real diff_time_step_; + }; + + /** + * @class RelaxationOfAllDiffussionSpeciesInner + * @brief Compute the diffusion relaxation process of all species + */ + template + class RelaxationOfAllDiffussionSpeciesInner : + public InteractionDynamicsWithUpdate, + public DiffusionReactionInnerData + { + /** all diffusion species and diffusion relation. */ + StdVec species_diffusion_; + StdVec>& species_n_; + StdVec>& diffusion_dt_; + StdLargeVec& Vol_; + protected: + void initializeDiffusionChangeRate(size_t particle_i); + void getDiffusionChangeRate(size_t particle_i, size_t particle_j, Vecd& e_ij, Real surface_area_ij); + virtual void updateSpeciesDiffusion(size_t particle_i, Real dt); + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + public: + RelaxationOfAllDiffussionSpeciesInner(BaseBodyRelationInner* body_inner_relation); + virtual ~RelaxationOfAllDiffussionSpeciesInner() {}; + }; + + /** + * @class RelaxationOfAllDiffussionSpeciesComplex + * Complex diffusion relaxation between two different bodies + */ + template + class RelaxationOfAllDiffussionSpeciesComplex : + public RelaxationOfAllDiffussionSpeciesInner, + public DiffusionReactionContactData + { + StdVec species_diffusion_; + StdVec>& species_n_; + StdVec>& diffusion_dt_; + StdVec*> contact_Vol_; + StdVec>*> contact_species_n_; + protected: + void getDiffusionChangeRateContact(size_t particle_i, size_t particle_j, Vecd& e_ij, + Real surface_area_ij, StdVec>& species_n_k); + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + public: + RelaxationOfAllDiffussionSpeciesComplex(ComplexBodyRelation* body_complex_relation); + virtual ~RelaxationOfAllDiffussionSpeciesComplex() {}; + }; + + /** + * @class RungeKuttaInitialization + * @brief initialization of a runge-kutta integration scheme + */ + template + class RungeKuttaInitialization : + public ParticleDynamicsSimple, + public DiffusionReactionSimpleData + { + StdVec species_diffusion_; + StdVec>& species_n_, & species_s_; + + void initializeIntermediateValue(size_t particle_i); + virtual void Update(size_t index_i, Real dt = 0.0) override; + public: + RungeKuttaInitialization(SPHBody* body, StdVec>& species_s); + virtual ~RungeKuttaInitialization() {}; + }; + + /** + * @class RungeKutta2Stages2ndStage + * @brief the second stage of the second runge-kutta scheme + */ + template + class RungeKutta2Stages2ndStage : public RungeKutta2Stages1stStageType + { + StdVec species_diffusion_; + StdVec>& species_n_; + StdVec>& diffusion_dt_; + protected: + StdVec>& species_s_; + virtual void updateSpeciesDiffusion(size_t particle_i, Real dt) override; + public: + RungeKutta2Stages2ndStage(BodyRelationType* body_relation, StdVec>& species_s); + virtual ~RungeKutta2Stages2ndStage() {}; + }; + + /** + * @class RelaxationOfAllDiffusionSpeciesRK2 + * @brief Compute the diffusion relaxation process of all species + * with second order Runge-Kutta time stepping + */ + template + class RelaxationOfAllDiffusionSpeciesRK2 : public ParticleDynamics, + public DiffusionReactionSimpleData + { + protected: + StdVec species_diffusion_; + /** Intermediate Value */ + StdVec> species_s_; + + RungeKuttaInitialization runge_kutta_initialization_; + RungeKutta2Stages1stStageType runge_kutta_1st_stage_; + RungeKutta2Stages2ndStage runge_kutta_2nd_stage_; + public: + RelaxationOfAllDiffusionSpeciesRK2(BodyRelationType* body_relation); + virtual ~RelaxationOfAllDiffusionSpeciesRK2() {}; + + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override; + }; + + + struct UpdateAReactionSpecies + { + Real operator () (Real input, Real production_rate, Real loss_rate, Real dt) const + { + return input * exp(-loss_rate * dt) + production_rate * (1.0 - exp(-loss_rate * dt)) / (loss_rate + TinyReal); + }; + }; + + /** + * @class RelaxationOfAllReactionsForward + * @brief Compute the reaction process of all species by forward splitting + */ + template + class RelaxationOfAllReactionsForward : + public ParticleDynamicsSimple, + public DiffusionReactionSimpleData + { + BaseReactionModel* species_reaction_; + StdVec>& species_n_; + UpdateAReactionSpecies updateAReactionSpecies; + protected: + virtual void Update(size_t index_i, Real dt = 0.0) override; + public: + RelaxationOfAllReactionsForward(BodyType* body); + virtual ~RelaxationOfAllReactionsForward() {}; + }; + + /** + * @class RelaxationOfAllReactionsBackward + * @brief Compute the reaction process of all species by backward splitting + */ + template + class RelaxationOfAllReactionsBackward : + public ParticleDynamicsSimple, + public DiffusionReactionSimpleData + { + BaseReactionModel* species_reaction_; + StdVec>& species_n_; + UpdateAReactionSpecies updateAReactionSpecies; + protected: + virtual void Update(size_t index_i, Real dt = 0.0) override; + public: + RelaxationOfAllReactionsBackward(BodyType* body); + virtual ~RelaxationOfAllReactionsBackward() {}; + }; + + /** + * @class ConstrainDiffusionBodyRegion + * @brief set boundary condition for diffusion problem + */ + template + class ConstrainDiffusionBodyRegion : + public PartSimpleDynamicsByParticle, + public DiffusionReactionSimpleData + { + public: + ConstrainDiffusionBodyRegion(BodyType* body, BodyPartByParticleType* body_part) : + PartSimpleDynamicsByParticle(body, body_part), + DiffusionReactionSimpleData(body), + pos_n_(this->particles_->pos_n_), species_n_(this->particles_->species_n_) {}; + virtual ~ConstrainDiffusionBodyRegion() {}; + protected: + StdLargeVec& pos_n_; + StdVec>& species_n_; + }; + + /** + * @class DiffusionBasedMapping + * @brief Mapping inside of body according to diffusion. + * This is a abstract class to be override for case specific implementation + */ + template + class DiffusionBasedMapping : + public ParticleDynamicsSimple, + public DiffusionReactionSimpleData + { + public: + DiffusionBasedMapping(BodyType* body) : + ParticleDynamicsSimple(body), + DiffusionReactionSimpleData(body), + pos_n_(this->particles_->pos_n_), species_n_(this->particles_->species_n_) {}; + virtual ~DiffusionBasedMapping() {}; + protected: + StdLargeVec& pos_n_; + StdVec>& species_n_; + }; +} +#endif //PARTICLE_DYNAMICS_DIFFUSION_REACTION_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/particle_dynamics_diffusion_reaction.hpp b/SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/particle_dynamics_diffusion_reaction.hpp new file mode 100644 index 0000000000..7247ccfdd7 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/diffusion_reaction_dynamics/particle_dynamics_diffusion_reaction.hpp @@ -0,0 +1,314 @@ +/** +* @file particle_dynamics_diffusion_reaction.hpp +* @author Xiaojing Tang, Chi ZHang and Xiangyu Hu +*/ + + +#ifndef PARTICLE_DYNAMICS_DIFFUSION_REACTION_HPP +#define PARTICLE_DYNAMICS_DIFFUSION_REACTION_HPP + + + +#include "particle_dynamics_diffusion_reaction.h" + +namespace SPH +{ + //=================================================================================================// + template + DiffusionReactionInitialCondition:: + DiffusionReactionInitialCondition(BodyType* diffusion_body) : + ParticleDynamicsSimple(diffusion_body), + DiffusionReactionSimpleData(diffusion_body), + pos_n_(this->particles_->pos_n_), species_n_(this->particles_->species_n_) {} + //=================================================================================================// + template + GetDiffusionTimeStepSize:: + GetDiffusionTimeStepSize(BodyType* body) : + ParticleDynamics(body), + DiffusionReactionSimpleData(body) + { + Real smoothing_length = body->particle_adaptation_->ReferenceSmoothingLength(); + diff_time_step_ = this->material_->getDiffusionTimeStepSize(smoothing_length); + } + //=================================================================================================// + template + RelaxationOfAllDiffussionSpeciesInner:: + RelaxationOfAllDiffussionSpeciesInner(BaseBodyRelationInner* body_inner_relation) : + InteractionDynamicsWithUpdate(body_inner_relation->sph_body_), + DiffusionReactionInnerData(body_inner_relation), + species_n_(this->particles_->species_n_), + diffusion_dt_(this->particles_->diffusion_dt_), Vol_(this->particles_->Vol_) + { + species_diffusion_ = this->material_->SpeciesDiffusion(); + } + //=================================================================================================// + template + void RelaxationOfAllDiffussionSpeciesInner:: + initializeDiffusionChangeRate(size_t particle_i) + { + for (size_t m = 0; m < species_diffusion_.size(); ++m) + { + diffusion_dt_[m][particle_i] = 0; + } + } + //=================================================================================================// + template + void RelaxationOfAllDiffussionSpeciesInner:: + getDiffusionChangeRate(size_t particle_i, size_t particle_j, Vecd& e_ij, Real surface_area_ij) + { + for (size_t m = 0; m < species_diffusion_.size(); ++m) + { + Real diff_coff_ij = species_diffusion_[m]->getInterParticleDiffusionCoff(particle_i, particle_j, e_ij); + size_t l = species_diffusion_[m]->gradient_species_index_; + Real phi_ij = species_n_[l][particle_i] - species_n_[l][particle_j]; + diffusion_dt_[m][particle_i] += diff_coff_ij * phi_ij * surface_area_ij; + } + } + //=================================================================================================// + template + void RelaxationOfAllDiffussionSpeciesInner:: + updateSpeciesDiffusion(size_t particle_i, Real dt) + { + for (size_t m = 0; m < species_diffusion_.size(); ++m) + { + size_t k = species_diffusion_[m]->diffusion_species_index_; + species_n_[k][particle_i] += dt * diffusion_dt_[m][particle_i]; + } + } + //=================================================================================================// + template + void RelaxationOfAllDiffussionSpeciesInner:: + Interaction(size_t index_i, Real dt) + { + DiffusionReactionParticles* particles = this->particles_; + Neighborhood& inner_neighborhood = this->inner_configuration_[index_i]; + + initializeDiffusionChangeRate(index_i); + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + Real dW_ij_ = inner_neighborhood.dW_ij_[n]; + Real r_ij_ = inner_neighborhood.r_ij_[n]; + Vecd& e_ij = inner_neighborhood.e_ij_[n]; + + const Vecd& gradi_ij = particles->getKernelGradient(index_i, index_j, dW_ij_, e_ij); + Real area_ij = 2.0 * Vol_[index_j] * dot(gradi_ij, e_ij) / r_ij_; + getDiffusionChangeRate(index_i, index_j, e_ij, area_ij); + } + } + //=================================================================================================// + template + void RelaxationOfAllDiffussionSpeciesInner:: + Update(size_t index_i, Real dt) + { + updateSpeciesDiffusion(index_i, dt); + } + //=================================================================================================// + template + RelaxationOfAllDiffussionSpeciesComplex:: + RelaxationOfAllDiffussionSpeciesComplex(ComplexBodyRelation* body_complex_relation) : + RelaxationOfAllDiffussionSpeciesInner(body_complex_relation->inner_relation_), + DiffusionReactionContactData(body_complex_relation->contact_relation_), + species_n_(this->particles_->species_n_), diffusion_dt_(this->particles_->diffusion_dt_) + { + species_diffusion_ = this->material_->SpeciesDiffusion(); + + for (size_t k = 0; k != this->contact_particles_.size(); ++k) + { + contact_Vol_.push_back(&(this->contact_particles_[k]->Vol_)); + contact_species_n_.push_back(&(this->contact_particles_[k]->species_n_)); + } + } + //=================================================================================================// + template + void RelaxationOfAllDiffussionSpeciesComplex:: + getDiffusionChangeRateContact(size_t particle_i, size_t particle_j, Vecd& e_ij, + Real surface_area_ij, StdVec>& species_n_k) + { + for (size_t m = 0; m < species_diffusion_.size(); ++m) + { + Real diff_coff_ij = species_diffusion_[m]->getInterParticleDiffusionCoff(particle_i, particle_j, e_ij); + size_t l = species_diffusion_[m]->gradient_species_index_; + Real phi_ij = species_n_[l][particle_i] - species_n_k[l][particle_j]; + diffusion_dt_[m][particle_i] += diff_coff_ij * phi_ij * surface_area_ij; + } + } + //=================================================================================================// + template + void RelaxationOfAllDiffussionSpeciesComplex:: + Interaction(size_t index_i, Real dt) + { + RelaxationOfAllDiffussionSpeciesInner::Interaction(index_i, dt); + DiffusionReactionParticles* particles = this->particles_; + + for (size_t k = 0; k < this->contact_configuration_.size(); ++k) + { + StdLargeVec& Vol_k = *(contact_Vol_[k]); + StdVec>& species_n_k = *(contact_species_n_[k]); + + Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + Real r_ij_ = contact_neighborhood.r_ij_[n]; + Real dW_ij_ = contact_neighborhood.dW_ij_[n]; + Vecd& e_ij = contact_neighborhood.e_ij_[n]; + + const Vecd& gradi_ij = particles->getKernelGradient(index_i, index_j, dW_ij_, e_ij); + Real area_ij = 2.0 * Vol_k[index_j] * dot(gradi_ij, e_ij) / r_ij_; + getDiffusionChangeRateContact(index_i, index_j, e_ij, area_ij, species_n_k); + } + } + } + //=================================================================================================// + template + RungeKuttaInitialization:: + RungeKuttaInitialization(SPHBody* body, StdVec>& species_s) : + ParticleDynamicsSimple(body), + DiffusionReactionSimpleData(body), + species_n_(this->particles_->species_n_), species_s_(species_s) + { + species_diffusion_ = this->material_->SpeciesDiffusion(); + } + //=================================================================================================// + template + void RungeKuttaInitialization:: + initializeIntermediateValue(size_t particle_i) + { + for (size_t m = 0; m < species_diffusion_.size(); ++m) + { + size_t k = species_diffusion_[m]->diffusion_species_index_; + species_s_[m][particle_i] = species_n_[k][particle_i]; + } + } + //=================================================================================================// + template + void RungeKuttaInitialization:: + Update(size_t index_i, Real dt) + { + initializeIntermediateValue(index_i); + } + //=================================================================================================// + template + RungeKutta2Stages2ndStage:: + RungeKutta2Stages2ndStage(BodyRelationType* body_relation, StdVec>& species_s) : + RungeKutta2Stages1stStageType(body_relation), + species_n_(this->particles_->species_n_), diffusion_dt_(this->particles_->diffusion_dt_), + species_s_(species_s) + { + species_diffusion_ = this->material_->SpeciesDiffusion(); + } + //=================================================================================================// + template + void RungeKutta2Stages2ndStage:: + updateSpeciesDiffusion(size_t particle_i, Real dt) + { + for (size_t m = 0; m < this->species_diffusion_.size(); ++m) + { + size_t k = species_diffusion_[m]->diffusion_species_index_; + species_n_[k][particle_i] = 0.5 * species_s_[m][particle_i] + + 0.5 * (species_n_[k][particle_i] + dt * diffusion_dt_[m][particle_i]); + } + } + //=================================================================================================// + template + RelaxationOfAllDiffusionSpeciesRK2:: + RelaxationOfAllDiffusionSpeciesRK2(BodyRelationType* body_relation) : + ParticleDynamics(body_relation->sph_body_), + DiffusionReactionSimpleData(body_relation->sph_body_), + runge_kutta_initialization_(body_relation->sph_body_, species_s_), + runge_kutta_1st_stage_(body_relation), + runge_kutta_2nd_stage_(body_relation, species_s_) + { + StdVec species_diffusion_ = this->material_->SpeciesDiffusion(); + + size_t number_of_diffusion_species = species_diffusion_.size(); + species_s_.resize(number_of_diffusion_species); + for (size_t m = 0; m < number_of_diffusion_species; ++m) + { + //the size should be the same as that in the base particles + species_s_[m].resize(this->particles_->real_particles_bound_); + //register data in base particles + std::get(this->particles_->all_particle_data_).push_back(&species_s_[m]); + } + } + //=================================================================================================// + template + void RelaxationOfAllDiffusionSpeciesRK2::exec(Real dt) + { + runge_kutta_initialization_.exec(); + runge_kutta_1st_stage_.exec(dt); + runge_kutta_2nd_stage_.exec(dt); + } + //=================================================================================================// + template + void RelaxationOfAllDiffusionSpeciesRK2::parallel_exec(Real dt) + { + runge_kutta_initialization_.parallel_exec(); + runge_kutta_1st_stage_.parallel_exec(dt); + runge_kutta_2nd_stage_.parallel_exec(dt); + } + //=================================================================================================// + template + RelaxationOfAllReactionsForward:: + RelaxationOfAllReactionsForward(BodyType* body) : + ParticleDynamicsSimple(body), + DiffusionReactionSimpleData(body), + species_n_(this->particles_->species_n_) + { + species_reaction_ = this->material_->SpeciesReaction(); + } + //=================================================================================================// + template + void RelaxationOfAllReactionsForward:: + Update(size_t index_i, Real dt) + { + IndexVector& reactive_species = species_reaction_->reactive_species_; + + for (size_t m = 0; m != reactive_species.size(); ++m) { + size_t k = reactive_species[m]; + Real production_rate = species_reaction_->get_production_rates_[k](species_n_, index_i); + Real loss_rate = species_reaction_->get_loss_rates_[k](species_n_, index_i); + species_n_[k][index_i] = updateAReactionSpecies(species_n_[k][index_i], production_rate, loss_rate, dt); + } + } + //=================================================================================================// + template + RelaxationOfAllReactionsBackward:: + RelaxationOfAllReactionsBackward(BodyType* body) : + ParticleDynamicsSimple(body), + DiffusionReactionSimpleData(body), + species_n_(this->particles_->species_n_) + { + species_reaction_ = this->material_->SpeciesReaction(); + } + //=================================================================================================// + template + void RelaxationOfAllReactionsBackward:: + Update(size_t index_i, Real dt) + { + IndexVector& reactive_species = species_reaction_->reactive_species_; + + for (size_t m = reactive_species.size(); m != 0; --m) { + size_t k = reactive_species[m - 1]; + Real production_rate = species_reaction_->get_production_rates_[k](species_n_, index_i); + Real loss_rate = species_reaction_->get_loss_rates_[k](species_n_, index_i); + species_n_[k][index_i] = updateAReactionSpecies(species_n_[k][index_i], production_rate, loss_rate, dt); + } + } + //=================================================================================================// +} +#endif //PARTICLE_DYNAMICS_DIFFUSION_REACTION_HPP \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/particle_dynamics_dissipation.h b/SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/particle_dynamics_dissipation.h new file mode 100644 index 0000000000..9fdc4c47fa --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/particle_dynamics_dissipation.h @@ -0,0 +1,201 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file particle_dynamics_dissipation.h +* @brief This is the particle dynamics aplliable for all type bodies +* @author Chi ZHang and Xiangyu Hu +*/ + + +#ifndef PARTICLE_DYNAMICS_DISSIPATION_H +#define PARTICLE_DYNAMICS_DISSIPATION_H + + + +#include "all_particle_dynamics.h" + +namespace SPH +{ + typedef DataDelegateInner DissipationDataInner; + typedef DataDelegateContact DissipationDataContact; + typedef DataDelegateContact DissipationDataWithWall; + + template + struct ErrorAndParameters + { + VariableType error_; + Real a_, c_; + ErrorAndParameters(Real zero = 0.0) : + error_(zero), a_(zero), c_(zero) {}; + }; + + /** + * @class DampingBySplittingAlgorithm + * @brief A quantity damping by splitting scheme + * this method modifies the quantity directly. + * Note that, if periodic boundary condition is applied, + * the parallelized version of the method requires the one using ghost particles + * because the splitting partition only works in this case. + */ + template + class DampingBySplittingInner : + public InteractionDynamicsSplitting, public DissipationDataInner + { + protected: + public: + DampingBySplittingInner(BaseBodyRelationInner* body_inner_relation, + std::string variable_name, Real eta); + virtual ~DampingBySplittingInner() {}; + void resetDampingCoefficient(Real reset_ratio) { eta_ *= reset_ratio; }; + protected: + Real eta_; /**< damping coefficient */ + StdLargeVec& Vol_, & mass_; + StdLargeVec& variable_; + + virtual ErrorAndParameters computeErrorAndParameters(size_t index_i, Real dt = 0.0); + virtual void updateStates(size_t index_i, Real dt, const ErrorAndParameters& error_and_parameters); + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + template + class DampingBySplittingComplex : + public DampingBySplittingInner, public DissipationDataContact + { + public: + DampingBySplittingComplex(ComplexBodyRelation* body_complex_relation, + std::string variable_name, Real eta); + virtual ~DampingBySplittingComplex() {}; + protected: + virtual ErrorAndParameters computeErrorAndParameters(size_t index_i, Real dt = 0.0) override; + virtual void updateStates(size_t index_i, Real dt, const ErrorAndParameters& error_and_parameters) override; + private: + StdVec*> contact_Vol_, contact_mass_; + StdVec*> contact_variable_; + }; + + template + class BaseDampingBySplittingType> + class DampingBySplittingWithWall : + public BaseDampingBySplittingType, public DissipationDataWithWall + { + public: + DampingBySplittingWithWall(ComplexBodyRelation* body_wall_relation, + std::string variable_name, Real eta); + virtual ~DampingBySplittingWithWall() {}; + protected: + virtual ErrorAndParameters computeErrorAndParameters(size_t index_i, Real dt = 0.0) override; + private: + StdVec*> wall_Vol_; + StdVec*> wall_variable_; + }; + + /** + * @class DampingPairwiseInner + * @brief A quantity damping by a pairwise splitting scheme + * this method modifies the quantity directly + * Note that, if periodic boundary condition is applied, + * the parallelized version of the method requires the one using ghost particles + * because the splitting partition only works in this case. + */ + template + class DampingPairwiseInner : + public InteractionDynamicsSplitting, public DissipationDataInner + { + public: + DampingPairwiseInner(BaseBodyRelationInner* body_inner_relation, + std::string variable_name, Real eta); + virtual ~DampingPairwiseInner() {}; + void resetDampingCoefficient(Real reset_ratio) { eta_ *= reset_ratio; }; + protected: + StdLargeVec& Vol_, & mass_; + StdLargeVec& variable_; + Real eta_; /**< damping coefficient */ + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + template + class DampingPairwiseComplex : + public DampingPairwiseInner, public DissipationDataContact + { + public: + DampingPairwiseComplex(ComplexBodyRelation* body_complex_relation, + std::string variable_name, Real eta); + virtual ~DampingPairwiseComplex() {}; + protected: + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + private: + StdVec*> contact_Vol_, contact_mass_; + StdVec*> contact_variable_; + }; + + /** + * @class DampingPairwiseWithWall + * @brief Damping with wall by which the wall velocity is not updated + * and the mass of wall particle is not considered. + */ + template + class BaseDampingPairwiseType> + class DampingPairwiseWithWall : + public BaseDampingPairwiseType, + public DissipationDataWithWall + { + public: + DampingPairwiseWithWall(ComplexBodyRelation* body_wall_relation, + std::string variable_name, Real eta); + virtual ~DampingPairwiseWithWall() {}; + protected: + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + private: + StdVec*> wall_Vol_; + StdVec*> wall_variable_; + }; + + /** + * @class DampingWithRandomChoice + * @brief A random choice method for obstaining static equilibrium state + * Note that, if periodic boundary condition is applied, + * the parallelized version of the method requires the one using ghost particles + * because the splitting partition only works in this case. + */ + template + class DampingWithRandomChoice : public DampingAlgorithmType + { + protected: + Real random_ratio_; + bool RandomChoice(); + public: + template + DampingWithRandomChoice(BodyRelationType* body_relation, + Real random_ratio, std::string variable_name, Real eta); + virtual ~DampingWithRandomChoice() {}; + + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override; + }; +} +#endif //PARTICLE_DYNAMICS_DISSIPATION_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/particle_dynamics_dissipation.hpp b/SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/particle_dynamics_dissipation.hpp new file mode 100644 index 0000000000..8304ffb871 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/dissipation_dynamics/particle_dynamics_dissipation.hpp @@ -0,0 +1,418 @@ +/** +* @file particle_dynamics_dissipation.hpp +* @brief This is the particle dynamics aplliable for all type bodies +* @author Chi ZHang and Xiangyu Hu +*/ + + +#ifndef PARTICLE_DYNAMICS_DISSIPATION_HPP +#define PARTICLE_DYNAMICS_DISSIPATION_HPP + + + +#include "particle_dynamics_dissipation.h" + +namespace SPH +{ + //=================================================================================================// + template + DampingBySplittingInner:: + DampingBySplittingInner(BaseBodyRelationInner* body_inner_relation, + std::string variable_name, Real eta) : + InteractionDynamicsSplitting(body_inner_relation->sph_body_), + DissipationDataInner(body_inner_relation), eta_(eta), + Vol_(particles_->Vol_), mass_(particles_->mass_), + variable_(*particles_->getVariableByName(variable_name)) {} + //=================================================================================================// + template + ErrorAndParameters + DampingBySplittingInner::computeErrorAndParameters(size_t index_i, Real dt) + { + Real Vol_i = Vol_[index_i]; + Real mass_i = mass_[index_i]; + VariableType& variable_i = variable_[index_i]; + ErrorAndParameters error_and_parameters(0); + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + //linear projection + VariableType variable_derivative = (variable_i - variable_[index_j]); + Real parameter_b = 2.0 * eta_ * inner_neighborhood.dW_ij_[n] + * Vol_i * Vol_[index_j] * dt / inner_neighborhood.r_ij_[n]; + + error_and_parameters.error_ -= variable_derivative * parameter_b; + error_and_parameters.a_ += parameter_b; + error_and_parameters.c_ += parameter_b * parameter_b; + } + error_and_parameters.a_ -= mass_i; + return error_and_parameters; + } + //=================================================================================================// + template + void DampingBySplittingInner:: + updateStates(size_t index_i, Real dt, const ErrorAndParameters& error_and_parameters) + { + Real parameter_l = error_and_parameters.a_ * error_and_parameters.a_ + error_and_parameters.c_; + VariableType parameter_k = error_and_parameters.error_ / (parameter_l + TinyReal); + variable_[index_i] += parameter_k * error_and_parameters.a_; + + Real Vol_i = Vol_[index_i]; + VariableType& variable_i = variable_[index_i]; + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + + Real parameter_b = 2.0 * eta_ * inner_neighborhood.dW_ij_[n] + * Vol_i * Vol_[index_j] * dt / inner_neighborhood.r_ij_[n]; + + //predicted quantity at particle j + VariableType variable_j = variable_[index_j] - parameter_k * parameter_b; + VariableType variable_derivative = (variable_i - variable_j); + + //exchange in conservation form + variable_[index_j] -= variable_derivative * parameter_b / mass_[index_j]; + } + } + //=================================================================================================// + template + void DampingBySplittingInner::Interaction(size_t index_i, Real dt) + { + ErrorAndParameters error_and_parameters = computeErrorAndParameters(index_i, dt); + updateStates(index_i, dt, error_and_parameters); + } + //=================================================================================================// + template + DampingBySplittingComplex:: + DampingBySplittingComplex(ComplexBodyRelation* body_complex_relation, + std::string variable_name, Real eta) : + DampingBySplittingInner(body_complex_relation->inner_relation_, variable_name, eta), + DissipationDataContact(body_complex_relation->contact_relation_) + { + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); + contact_mass_.push_back(&(contact_particles_[k]->mass_)); + contact_variable_.push_back(contact_particles_[k]->template getVariableByName(variable_name)); + } + } + //=================================================================================================// + template + ErrorAndParameters + DampingBySplittingComplex::computeErrorAndParameters(size_t index_i, Real dt) + { + ErrorAndParameters error_and_parameters + = DampingBySplittingInner::computeErrorAndParameters(index_i, dt); + + VariableType& variable_i = this->variable_[index_i]; + Real Vol_i = this->Vol_[index_i]; + /** Contact interaction. */ + for (size_t k = 0; k < this->contact_configuration_.size(); ++k) + { + StdLargeVec& Vol_k = *(this->contact_Vol_[k]); + StdLargeVec& mass_k = *(this->contact_mass_[k]); + StdLargeVec& variable_k = *(this->contact_variable_[k]); + Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + + //linear projection + VariableType variable_derivative = (variable_i - variable_k[index_j]); + Real parameter_b = 2.0 * this->eta_ * contact_neighborhood.dW_ij_[n] + * Vol_i * Vol_k[index_j] * dt / contact_neighborhood.r_ij_[n]; + + error_and_parameters.error_ -= variable_derivative * parameter_b; + error_and_parameters.a_ += parameter_b; + error_and_parameters.c_ += parameter_b * parameter_b; + } + return error_and_parameters; + } + } + //=================================================================================================// + template + void DampingBySplittingComplex:: + updateStates(size_t index_i, Real dt, const ErrorAndParameters& error_and_parameters) + { + DampingBySplittingInner::updateStates(index_i, dt, error_and_parameters); + + Real parameter_l = error_and_parameters.a_ * error_and_parameters.a_ + error_and_parameters.c_; + VariableType parameter_k = error_and_parameters.error_ / (parameter_l + TinyReal); + VariableType& variable_i = this->variable_[index_i]; + Real Vol_i = this->Vol_[index_i]; + /** Contact interaction. */ + for (size_t k = 0; k < this->contact_configuration_.size(); ++k) + { + StdLargeVec& Vol_k = *(this->contact_Vol_[k]); + StdLargeVec& mass_k = *(this->contact_mass_[k]); + StdLargeVec& variable_k = *(this->contact_variable_[k]); + Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + + //linear projection + Real parameter_b = 2.0 * this->eta_ * contact_neighborhood.dW_ij_[n] + * Vol_i * Vol_k[index_j] * dt / contact_neighborhood.r_ij_[n]; + + //predicted quantity at particle j + VariableType variable_j = this->variable_k[index_j] - parameter_k * parameter_b; + VariableType variable_derivative = (variable_i - variable_j); + + //exchange in conservation form + this->variable_k[index_j] -= variable_derivative * parameter_b / mass_k[index_j]; + } + } + } + //=================================================================================================// + template class BaseDampingBySplittingType> + DampingBySplittingWithWall:: + DampingBySplittingWithWall(ComplexBodyRelation* body_wall_relation, + std::string variable_name, Real eta) : + BaseDampingBySplittingType(body_wall_relation->inner_relation_, variable_name, eta), + DissipationDataWithWall(body_wall_relation->contact_relation_) + { + for (size_t k = 0; k != DissipationDataWithWall::contact_particles_.size(); ++k) + { + wall_Vol_.push_back(&(contact_particles_[k]->Vol_)); + wall_variable_.push_back(contact_particles_[k]->template getVariableByName(variable_name)); + } + } + //=================================================================================================// + template class BaseDampingBySplittingType> + ErrorAndParameters + DampingBySplittingWithWall:: + computeErrorAndParameters(size_t index_i, Real dt) + { + ErrorAndParameters error_and_parameters + = BaseDampingBySplittingType::computeErrorAndParameters(index_i, dt); + + VariableType& variable_i = this->variable_[index_i]; + Real Vol_i = this->Vol_[index_i]; + /** Contact interaction. */ + for (size_t k = 0; k < this->contact_configuration_.size(); ++k) + { + StdLargeVec& Vol_k = *(this->wall_Vol_[k]); + StdLargeVec& variable_k = *(this->wall_variable_[k]); + Neighborhood& contact_neighborhood = (*DissipationDataWithWall::contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + + //linear projection + VariableType variable_derivative = (variable_i - variable_k[index_j]); + Real parameter_b = 2.0 * this->eta_ * contact_neighborhood.dW_ij_[n] + * Vol_i * Vol_k[index_j] * dt / contact_neighborhood.r_ij_[n]; + + error_and_parameters.error_ -= variable_derivative * parameter_b; + error_and_parameters.a_ += parameter_b; + error_and_parameters.c_ += parameter_b * parameter_b; + } + } + return error_and_parameters; + } + //=================================================================================================// + template + DampingPairwiseInner:: + DampingPairwiseInner(BaseBodyRelationInner* body_inner_relation, + std::string variable_name, Real eta) : + InteractionDynamicsSplitting(body_inner_relation->sph_body_), + DissipationDataInner(body_inner_relation), + Vol_(particles_->Vol_), mass_(particles_->mass_), + variable_(*particles_->getVariableByName(variable_name)), + eta_(eta) {} + //=================================================================================================// + template + void DampingPairwiseInner::Interaction(size_t index_i, Real dt) + { + Real Vol_i = Vol_[index_i]; + Real mass_i = mass_[index_i]; + VariableType& variable_i = variable_[index_i]; + + std::array parameter_b; + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + //forward sweep + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + Real mass_j = mass_[index_j]; + + VariableType variable_derivative = (variable_i - variable_[index_j]); + parameter_b[n] = eta_ * inner_neighborhood.dW_ij_[n] + * Vol_i * Vol_[index_j] * dt / inner_neighborhood.r_ij_[n]; + + VariableType increment = parameter_b[n] * variable_derivative + / (mass_i * mass_j - parameter_b[n] * (mass_i + mass_j)); + variable_[index_i] += increment * mass_j; + variable_[index_j] -= increment * mass_i; + } + + //backward sweep + for (size_t n = inner_neighborhood.current_size_; n != 0; --n) + { + size_t index_j = inner_neighborhood.j_[n - 1]; + Real mass_j = mass_[index_j]; + + VariableType variable_derivative = (variable_i - variable_[index_j]); + VariableType increment = parameter_b[n - 1] * variable_derivative + / (mass_i * mass_j - parameter_b[n - 1] * (mass_i + mass_j)); + + variable_[index_i] += increment * mass_j; + variable_[index_j] -= increment * mass_i; + } + } + //=================================================================================================// + template + DampingPairwiseComplex:: + DampingPairwiseComplex(ComplexBodyRelation* body_complex_relation, + std::string variable_name, Real eta) : + DampingPairwiseInner(body_complex_relation->inner_relation_, variable_name, eta), + DissipationDataContact(body_complex_relation->contact_relation_) + { + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); + contact_mass_.push_back(&(contact_particles_[k]->mass_)); + contact_variable_.push_back(contact_particles_[k]->template getVariableByName(variable_name)); + } + } + //=================================================================================================// + template + void DampingPairwiseComplex::Interaction(size_t index_i, Real dt) + { + DampingPairwiseInner::Interaction(index_i, dt); + + Real Vol_i = this->Vol_[index_i]; + Real mass_i = this->mass_[index_i]; + VariableType& variable_i = this->variable_[index_i]; + + std::array parameter_b; + + /** Contact interaction. */ + for (size_t k = 0; k < this->contact_configuration_.size(); ++k) + { + StdLargeVec& Vol_k = *(this->contact_Vol_[k]); + StdLargeVec& mass_k = *(this->contact_mass_[k]); + StdLargeVec& variable_k = *(this->contact_variable_[k]); + Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; + //forward sweep + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + Real mass_j = mass_k[index_j]; + + VariableType variable_derivative = (variable_i - variable_k[index_j]); + parameter_b[n] = this->eta_ * contact_neighborhood.dW_ij_[n] + * Vol_i * Vol_k[index_j] * dt / contact_neighborhood.r_ij_[n]; + + VariableType increment = parameter_b[n] * variable_derivative + / (mass_i * mass_j - parameter_b[n] * (mass_i + mass_j)); + this->variable_[index_i] += increment * mass_j; + variable_k[index_j] -= increment * mass_i; + + } + //backward sweep + for (size_t n = contact_neighborhood.current_size_; n != 0; --n) + { + size_t index_j = contact_neighborhood.j_[n - 1]; + Real mass_j = mass_k[index_j]; + + VariableType variable_derivative = (variable_i - variable_k[index_j]); + VariableType increment = parameter_b[n - 1] * variable_derivative + / (mass_i * mass_j - parameter_b[n - 1] * (mass_i + mass_j)); + + this->variable_[index_i] += increment * mass_j; + variable_k[index_j] -= increment * mass_i; + } + } + } + //=================================================================================================// + template class BaseDampingPairwiseType> + DampingPairwiseWithWall:: + DampingPairwiseWithWall(ComplexBodyRelation* body_wall_relation, + std::string variable_name, Real eta) : + BaseDampingPairwiseType(body_wall_relation->inner_relation_, variable_name, eta), + DissipationDataWithWall(body_wall_relation->contact_relation_) + { + for (size_t k = 0; k != DissipationDataWithWall::contact_particles_.size(); ++k) + { + wall_Vol_.push_back(&(contact_particles_[k]->Vol_)); + wall_variable_.push_back(contact_particles_[k]->template getVariableByName(variable_name)); + } + } + //=================================================================================================// + template class BaseDampingPairwiseType> + void DampingPairwiseWithWall:: + Interaction(size_t index_i, Real dt) + { + BaseDampingPairwiseType::Interaction(index_i, dt); + + Real Vol_i = this->Vol_[index_i]; + Real mass_i = this->mass_[index_i]; + VariableType& variable_i = this->variable_[index_i]; + + std::array parameter_b; + + /** Contact interaction. */ + for (size_t k = 0; k < this->contact_configuration_.size(); ++k) + { + StdLargeVec& Vol_k = *(this->wall_Vol_[k]); + StdLargeVec& variable_k = *(this->wall_variable_[k]); + Neighborhood& contact_neighborhood = (*DissipationDataWithWall::contact_configuration_[k])[index_i]; + //forward sweep + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + + parameter_b[n] = this->eta_ * contact_neighborhood.dW_ij_[n] + * Vol_i * Vol_k[index_j] * dt / contact_neighborhood.r_ij_[n]; + + //only update particle i + this->variable_[index_i] += parameter_b[n] * (variable_i - variable_k[index_j]) + / (mass_i - 2.0 * parameter_b[n]); + } + //backward sweep + for (size_t n = contact_neighborhood.current_size_; n != 0; --n) + { + size_t index_j = contact_neighborhood.j_[n - 1]; + + //only update particle i + this->variable_[index_i] += parameter_b[n - 1] * (variable_i - variable_k[index_j]) + / (mass_i - 2.0 * parameter_b[n - 1]); + } + } + } + //=================================================================================================// + template + template + DampingWithRandomChoice:: + DampingWithRandomChoice(BodyRelationType* body_relation, + Real random_ratio, std::string variable_name, Real eta) : + DampingAlgorithmType(body_relation, variable_name, eta / random_ratio), + random_ratio_(random_ratio) {} + //=================================================================================================// + template + bool DampingWithRandomChoice::RandomChoice() + { + return ((double)rand() / (RAND_MAX)) < random_ratio_ ? true : false; + } + //=================================================================================================// + template + void DampingWithRandomChoice::exec(Real dt) + { + if (RandomChoice()) DampingAlgorithmType::exec(dt); + } + //=================================================================================================// + template + void DampingWithRandomChoice::parallel_exec(Real dt) + { + if (RandomChoice()) DampingAlgorithmType::parallel_exec(dt); + } + //=================================================================================================// +} +#endif //PARTICLE_DYNAMICS_DISSIPATION_HPP \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/electro_physiology/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/electro_physiology/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/electro_physiology/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/electro_physiology/electro_physiology.cpp b/SPHINXsys/src/shared/particle_dynamics/electro_physiology/electro_physiology.cpp new file mode 100644 index 0000000000..2c1f4bdd16 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/electro_physiology/electro_physiology.cpp @@ -0,0 +1,24 @@ +/** + * @file electro_physiology.cpp + * @author Chi Zhang and Xiangyu Hu + */ + +#include "electro_physiology.h" + +using namespace SimTK; + +namespace SPH +{ + namespace electro_physiology + { + //=================================================================================================// + ElectroPhysiologyInitialCondition:: + ElectroPhysiologyInitialCondition(SolidBody* body) : + ParticleDynamicsSimple(body), + ElectroPhysiologyDataDelegateSimple(body), + pos_n_(particles_->pos_n_), species_n_(particles_->species_n_) + { + } + //=================================================================================================// + } +} \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/electro_physiology/electro_physiology.h b/SPHINXsys/src/shared/particle_dynamics/electro_physiology/electro_physiology.h new file mode 100644 index 0000000000..7288d752fd --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/electro_physiology/electro_physiology.h @@ -0,0 +1,149 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file eletro_physiology.h + * @brief In is file, we declaim the dynamics relevant to electrophysiology, + * including diffusion, reaction and muscle activation. + * @author Chi Zhang and Xiangyu Hu + */ + +#ifndef ELECTRO_PHYSIOLOGY_H +#define ELECTRO_PHYSIOLOGY_H + + + +#include "all_particle_dynamics.h" +#include "diffusion_reaction_particles.h" +#include "particle_dynamics_diffusion_reaction.h" +#include "diffusion_reaction.h" +#include "elastic_solid.h" +#include "base_kernel.h" +#include "solid_body.h" +#include "solid_particles.h" + +namespace SPH +{ + namespace electro_physiology + { + typedef DiffusionReactionSimpleData ElectroPhysiologyDataDelegateSimple; + typedef DiffusionReactionInnerData ElectroPhysiologyDataDelegateInner; + /** + * @class ElectroPhysiologyInitialCondition + * @brief set initial condition for a muscle body + * This is a abstract class to be override for case specific initial conditions. + */ + class ElectroPhysiologyInitialCondition : + public ParticleDynamicsSimple, + public ElectroPhysiologyDataDelegateSimple + { + public: + ElectroPhysiologyInitialCondition(SolidBody *body); + virtual ~ElectroPhysiologyInitialCondition() {}; + protected: + StdLargeVec& pos_n_; + StdVec>& species_n_; + }; + /** + * @class GetElectroPhysiologyTimeStepSize + * @brief Computing the time step size from diffusion criteria + */ + class GetElectroPhysiologyTimeStepSize + : public GetDiffusionTimeStepSize + { + public: + explicit GetElectroPhysiologyTimeStepSize(SolidBody* body) + : GetDiffusionTimeStepSize(body) {}; + virtual ~GetElectroPhysiologyTimeStepSize() {}; + }; + /** + * @class ElectroPhysiologyDiffusionRelaxationInner + * @brief Compute the diffusion relaxation process + */ + class ElectroPhysiologyDiffusionRelaxationInner : + public RelaxationOfAllDiffusionSpeciesRK2, + BaseBodyRelationInner> + { + public: + ElectroPhysiologyDiffusionRelaxationInner(BaseBodyRelationInner* body_inner_relation) + : RelaxationOfAllDiffusionSpeciesRK2(body_inner_relation) {}; + virtual ~ElectroPhysiologyDiffusionRelaxationInner() {}; + }; + /** + * @class ElectroPhysiologyDiffusionRelaxationComplex + * @brief Compute the diffusion relaxation process + */ + class ElectroPhysiologyDiffusionRelaxationComplex : + public RelaxationOfAllDiffusionSpeciesRK2, + ComplexBodyRelation> + { + public: + ElectroPhysiologyDiffusionRelaxationComplex(ComplexBodyRelation* body_complex_relation) + : RelaxationOfAllDiffusionSpeciesRK2(body_complex_relation) {}; + virtual ~ElectroPhysiologyDiffusionRelaxationComplex() {}; + }; + /** + * @class ElectroPhysiologyReactionRelaxationForward + * @brief Solve the reaction ODE equation of trans-membrane potential + * using forward sweeping + */ + class ElectroPhysiologyReactionRelaxationForward + : public RelaxationOfAllReactionsForward + { + public: + ElectroPhysiologyReactionRelaxationForward(SolidBody* body) + : RelaxationOfAllReactionsForward(body) {}; + virtual ~ElectroPhysiologyReactionRelaxationForward() {}; + }; + /** + * @class ElectroPhysiologyReactionRelaxationForward + * @brief Solve the reaction ODE equation of trans-membrane potential + * using backward sweeping + */ + class ElectroPhysiologyReactionRelaxationBackward + : public RelaxationOfAllReactionsBackward + { + public: + ElectroPhysiologyReactionRelaxationBackward(SolidBody* body) + : RelaxationOfAllReactionsBackward(body) {}; + virtual ~ElectroPhysiologyReactionRelaxationBackward() {}; + }; + /** + * @class ApplyStimulusCurrents + * @brief Apply specific stimulus currents + * This is a abstract class to be override for case specific implementations. + */ + class ApplyStimulusCurrents : + public ParticleDynamicsSimple, + public ElectroPhysiologyDataDelegateSimple + { + public: + ApplyStimulusCurrents(SolidBody *body) : + ParticleDynamicsSimple(body), + ElectroPhysiologyDataDelegateSimple(body) {} + virtual ~ApplyStimulusCurrents() {}; + }; + } +} +#endif //ELECTRO_PHYSIOLOGY_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/external_force/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/external_force/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/external_force/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/external_force/external_force.cpp b/SPHINXsys/src/shared/particle_dynamics/external_force/external_force.cpp new file mode 100644 index 0000000000..8a9a99bc37 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/external_force/external_force.cpp @@ -0,0 +1,26 @@ +/** + * @file sexternal_froce.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ +#include "external_force.h" +//=================================================================================================// +namespace SPH { +//=================================================================================================// + ExternalForce::ExternalForce() {} +//=================================================================================================// + Gravity::Gravity(Vecd global_acceleration, Vecd reference_position) + : ExternalForce(), global_acceleration_(global_acceleration), + zero_potential_reference_(reference_position) {} + //=================================================================================================// + Vecd Gravity::InducedAcceleration(Vecd& position) + { + return global_acceleration_; + } + //=================================================================================================// + Real Gravity::getPotential(Vecd& position) + { + return dot(InducedAcceleration(position), zero_potential_reference_ - position); + } + //=================================================================================================// +} +//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/external_force/external_force.h b/SPHINXsys/src/shared/particle_dynamics/external_force/external_force.h new file mode 100644 index 0000000000..a493a66a2d --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/external_force/external_force.h @@ -0,0 +1,69 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file external_force.h + * @brief Here, we define the base external force class. + * @details The simple derived classes, such as gravity will be defined in applications. + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#ifndef EXTERNAL_FORCE_H +#define EXTERNAL_FORCE_H + + + +#include "base_data_package.h" + +namespace SPH { + /** + * @class ExternalForce + * @brief This class define external forces. + */ + class ExternalForce + { + public: + ExternalForce(); + virtual ~ExternalForce() {}; + /** This function can be used for runtime control of external force. */ + virtual Vecd InducedAcceleration(Vecd& position) = 0; + }; + + /** + * @class Gravity + * @brief The gravity force, derived class of External force. + */ + class Gravity : public ExternalForce + { + protected: + Vecd global_acceleration_; + Vecd zero_potential_reference_; + public: + Gravity(Vecd gravity_vector, Vecd reference_position = Vecd(0)); + virtual ~Gravity() {}; + + /** This function can be used for runtime control of external force. */ + virtual Vecd InducedAcceleration(Vecd& position) override; + Real getPotential(Vecd& position); + }; +} +#endif //EXTERNAL_FORCE_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/all_fluid_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/all_fluid_dynamics.h new file mode 100644 index 0000000000..b415b2f315 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/all_fluid_dynamics.h @@ -0,0 +1,42 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file all_fluid_dynamics.h +* @brief This is the header file that user code should include to pick up all +* fluid dynamics used in SPHinXsys. +* @details The fluid dynamics algorithms begin for fluid bulk without boundary condition, +* then algorithm interacting with wall is defined, further algorithms for multiphase flow interaction +* built upon these basic algorithms. +* @author Chi ZHang and Xiangyu Hu +*/ +#pragma once + +#include "fluid_dynamics_inner.h" +#include "fluid_dynamics_inner.hpp" +#include "fluid_dynamics_complex.h" +#include "fluid_dynamics_complex.hpp" +#include "fluid_dynamics_compound.h" +#include "fluid_dynamics_compound.hpp" +#include "fluid_dynamics_multi_phase.h" +#include "fluid_dynamics_multi_phase.hpp" +#include "all_eulerian_fluid_dynamics.h" diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/all_eulerian_fluid_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/all_eulerian_fluid_dynamics.h new file mode 100644 index 0000000000..67eee894c3 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/all_eulerian_fluid_dynamics.h @@ -0,0 +1,34 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file all_eulerian_fluid_dynamics.h +* @brief This is the header file that user code should include to pick up all eulerian +* fluid dynamics used in SPHinXsys. +* @details The eulerian fluid dynamics algorithms begin for fluid bulk without boundary condition, +* then algorithm interacting with wall is defined. +* @author Zhentong Wang +*/ +#pragma once + +#include "eulerian_fluid_dynamics_inner.h" +#include "eulerian_fluid_dynamics_inner.hpp" \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.cpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.cpp new file mode 100644 index 0000000000..1f1b8c13c6 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.cpp @@ -0,0 +1,20 @@ +/** + * @file fluid_dynamics_complex.cpp + * @author Chi ZHang and Xiangyu Hu + */ + +#include "eulerian_fluid_dynamics_complex.h" +#include "in_output.h" +#include "geometry_level_set.h" +//=================================================================================================// +using namespace std; +//=================================================================================================// +namespace SPH +{ +//=================================================================================================// + namespace eulerian_fluid_dynamics + { + } +//=================================================================================================// +} +//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.h b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.h new file mode 100644 index 0000000000..a64e5ea844 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.h @@ -0,0 +1,184 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file eulerian_fluid_dynamics_complex.h +* @brief Here, we define the algorithm classes for complex compressible fluid dynamics, +* which is involving with either solid walls (with suffix WithWall) +* or/and other bodies treated as wall for the fluid (with suffix Complex). +* @author Chi ZHang, Xiangyu Hu and Zhentong Wang +*/ + +#pragma once + +#include "eulerian_fluid_dynamics_inner.h" + +namespace SPH +{ + namespace eulerian_fluid_dynamics + { + typedef DataDelegateContact CompressibleFluidWallData; + typedef DataDelegateContact CompressibleFluidContactData; + typedef DataDelegateContact CFSIContactData; //CFSI= Compressible Fluid_Structure Interation + + typedef DataDelegateContact FluidWallData; + typedef DataDelegateContact FluidContactData; + typedef DataDelegateContact FSIContactData; + /** + * @class RelaxationWithWall + * @brief Abstract base class for general relaxation algorithms with wall + */ + template + class RelaxationWithWall : public BaseRelaxationType, public CompressibleFluidWallData + { + public: + template + RelaxationWithWall(BaseBodyRelationType* base_body_relation, + BaseBodyRelationContact* wall_contact_relation); + virtual ~RelaxationWithWall() {}; + protected: + StdVec wall_inv_rho_0_; + StdVec*> wall_mass_, wall_Vol_; + StdVec*> wall_vel_ave_, wall_dvel_dt_ave_, wall_n_; + }; + + /** + * @class ViscousWithWall + * @brief template class viscous acceleration with wall boundary + */ + template + class ViscousWithWall : public RelaxationWithWall + { + public: + // template for different combination of constructing body relations + template + ViscousWithWall(BaseBodyRelationType* base_body_relation, + BaseBodyRelationContact* wall_contact_relation); + virtual ~ViscousWithWall() {}; + protected: + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** template interface class for different pressure relaxation with wall schemes */ + template + class BaseViscousAccelerationWithWall : public BaseViscousAccelerationType + { + public: + BaseViscousAccelerationWithWall(ComplexBodyRelation* fluid_wall_relation); + BaseViscousAccelerationWithWall(BaseBodyRelationInner* fluid_inner_relation, + BaseBodyRelationContact* wall_contact_relation); + BaseViscousAccelerationWithWall(ComplexBodyRelation* fluid_complex_relation, + BaseBodyRelationContact* wall_contact_relation); + }; + using ViscousAccelerationWithWall + = BaseViscousAccelerationWithWall>; + + /** + * @class PressureRelaxation + * @brief template class pressure relaxation scheme with wall boundary + */ + template + class PressureRelaxation : public RelaxationWithWall + { + public: + // template for different combination of constructing body relations + template + PressureRelaxation(BaseBodyRelationType* base_body_relation, + BaseBodyRelationContact* wall_contact_relation); + virtual ~PressureRelaxation() {}; + protected: + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** template interface class for different pressure relaxation with wall schemes */ + template + class BasePressureRelaxationWithWall : public BasePressureRelaxationType + { + public: + BasePressureRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation); + BasePressureRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, + BaseBodyRelationContact* wall_contact_relation); + BasePressureRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, + BaseBodyRelationContact* wall_contact_relation); + }; + using PressureRelaxationHLLCRiemannWithWall + = BasePressureRelaxationWithWall>; + using PressureRelaxationHLLCRiemannAndLimiterWithWall + = BasePressureRelaxationWithWall>; + /** + * @class DensityRelaxation + * @brief template density relaxation scheme without using different Riemann solvers. + * The difference from the free surface version is that no Riemann problem is applied + */ + template + class DensityAndEnergyRelaxation : public RelaxationWithWall + { + public: + // template for different combination of constructing body relations + template + DensityAndEnergyRelaxation(BaseBodyRelationType* base_body_relation, + BaseBodyRelationContact* wall_contact_relation); + virtual ~DensityAndEnergyRelaxation() {}; + protected: + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** template interface class for different density relaxation schemes */ + template + class BaseDensityAndEnergyRelaxationWithWall : public DensityAndEnergyRelaxation + { + public: + BaseDensityAndEnergyRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation); + BaseDensityAndEnergyRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, + BaseBodyRelationContact* wall_contact_relation); + BaseDensityAndEnergyRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, + BaseBodyRelationContact* wall_contact_relation); + }; + using DensityAndEnergyRelaxationHLLCRiemannWithWall = BaseDensityAndEnergyRelaxationWithWall; + using DensityAndEnergyRelaxationHLLCRiemannAndLimiterWithWall = BaseDensityAndEnergyRelaxationWithWall; + + /** + * @class SurfaceNormWithWall + * @brief Modify surface norm when contact with wall + */ + class SurfaceNormWithWall : public InteractionDynamics, public FSIContactData + { + public: + SurfaceNormWithWall(BaseBodyRelationContact* contact_relation, Real contact_angle); + virtual ~SurfaceNormWithWall() {}; + protected: + Real contact_angle_; + Real smoothing_length_; + Real particle_spacing_; + StdVec*> wall_n_; + StdLargeVec* surface_norm_; + StdLargeVec& is_free_surface_; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + } +} diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.hpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.hpp new file mode 100644 index 0000000000..753c694dcc --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_complex.hpp @@ -0,0 +1,232 @@ +/** + * @file eulerian_fluid_dynamics_complex.hpp + * @author Chi ZHang and Xiangyu Hu + */ +#include "eulerian_fluid_dynamics_complex.h" + +//=================================================================================================// +namespace SPH +{ +//=================================================================================================// + namespace eulerian_fluid_dynamics + { + //=================================================================================================// + template + template + RelaxationWithWall:: + RelaxationWithWall(BaseBodyRelationType* base_body_relation, + BaseBodyRelationContact* wall_contact_relation) : + BaseRelaxationType(base_body_relation), CompressibleFluidWallData(wall_contact_relation) + { + if (base_body_relation->sph_body_ != wall_contact_relation->sph_body_) + { + std::cout << "\n Error: the two body_realtions do not have the same source body!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + + for (size_t k = 0; k != CompressibleFluidWallData::contact_particles_.size(); ++k) + { + Real rho_0_k = CompressibleFluidWallData::contact_particles_[k]->rho0_; + wall_inv_rho_0_.push_back(1.0 / rho_0_k); + wall_mass_.push_back(&(CompressibleFluidWallData::contact_particles_[k]->mass_)); + wall_Vol_.push_back(&(CompressibleFluidWallData::contact_particles_[k]->Vol_)); + wall_vel_ave_.push_back(&(CompressibleFluidWallData::contact_particles_[k]->vel_ave_)); + wall_dvel_dt_ave_.push_back(&(CompressibleFluidWallData::contact_particles_[k]->dvel_dt_ave_)); + wall_n_.push_back(&(CompressibleFluidWallData::contact_particles_[k]->n_)); + } + } + //=================================================================================================// + template + template + ViscousWithWall:: + ViscousWithWall(BaseBodyRelationType* base_body_relation, + BaseBodyRelationContact* wall_contact_relation) + : RelaxationWithWall(base_body_relation, wall_contact_relation) {} + //=================================================================================================// + template + void ViscousWithWall::Interaction(size_t index_i, Real dt) + { + BaseViscousAccelerationType::Interaction(index_i, dt); + + Real rho_i = this->rho_n_[index_i]; + Vecd& vel_i = this->vel_n_[index_i]; + + Vecd acceleration(0), vel_derivative(0); + for (size_t k = 0; k < CompressibleFluidWallData::contact_configuration_.size(); ++k) + { + StdLargeVec& Vol_k = *(this->wall_Vol_[k]); + StdLargeVec& vel_ave_k = *(this->wall_vel_ave_[k]); + Neighborhood& contact_neighborhood = (*CompressibleFluidWallData::contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + Real r_ij = contact_neighborhood.r_ij_[n]; + + vel_derivative = 2.0*(vel_i - vel_ave_k[index_j]) / (r_ij + 0.01 * this->smoothing_length_); + acceleration += 2.0 * this->mu_ * vel_derivative + * contact_neighborhood.dW_ij_[n] * Vol_k[index_j] / rho_i; + } + } + + this->dvel_dt_prior_[index_i] += acceleration; + this->dE_dt_prior_[index_i] += rho_i * dot(acceleration, vel_n_[index_i]); + } + //=================================================================================================// + template + BaseViscousAccelerationWithWall:: + BaseViscousAccelerationWithWall(ComplexBodyRelation* fluid_wall_relation) : + BaseViscousAccelerationType(fluid_wall_relation->inner_relation_, + fluid_wall_relation->contact_relation_) {} + //=================================================================================================// + template + BaseViscousAccelerationWithWall:: + BaseViscousAccelerationWithWall(BaseBodyRelationInner* fluid_inner_relation, + BaseBodyRelationContact* wall_contact_relation) : + BaseViscousAccelerationType(fluid_inner_relation, + wall_contact_relation) {} + //=================================================================================================// + template + BaseViscousAccelerationWithWall:: + BaseViscousAccelerationWithWall(ComplexBodyRelation* fluid_complex_relation, + BaseBodyRelationContact* wall_contact_relation) : + BaseViscousAccelerationType(fluid_complex_relation, + wall_contact_relation) {} + //=================================================================================================// + template + template + PressureRelaxation:: + PressureRelaxation(BaseBodyRelationType* base_body_relation, + BaseBodyRelationContact* wall_contact_relation) : + RelaxationWithWall(base_body_relation, + wall_contact_relation) {} + //=================================================================================================// + template + void PressureRelaxation::Interaction(size_t index_i, Real dt) + { + BasePressureRelaxationType::Interaction(index_i, dt); + + CompressibleFluidState state_i(this->rho_n_[index_i], this->vel_n_[index_i], this->p_[index_i], this->E_[index_i]); + Vecd acceleration(0.0); + for (size_t k = 0; k < CompressibleFluidWallData::contact_configuration_.size(); ++k) + { + StdLargeVec& Vol_k = *(this->wall_Vol_[k]); + StdLargeVec& vel_ave_k = *(this->wall_vel_ave_[k]); + StdLargeVec& dvel_dt_ave_k = *(this->wall_dvel_dt_ave_[k]); + StdLargeVec& n_k = *(this->wall_n_[k]); + Neighborhood& wall_neighborhood = (*CompressibleFluidWallData::contact_configuration_[k])[index_i]; + for (size_t n = 0; n != wall_neighborhood.current_size_; ++n) + { + size_t index_j = wall_neighborhood.j_[n]; + Vecd& e_ij = wall_neighborhood.e_ij_[n]; + Real dW_ij = wall_neighborhood.dW_ij_[n]; + Real r_ij = wall_neighborhood.r_ij_[n]; + + Vecd vel_in_wall = 2.0 * vel_ave_k[index_j] - state_i.vel_; + Real p_in_wall = state_i.p_; + Real rho_in_wall = state_i.rho_; + Real E_in_wall = state_i.E_; + CompressibleFluidState state_j(rho_in_wall, vel_in_wall, p_in_wall, E_in_wall); + Real p_star = this->riemann_solver_.getPStar(state_i, state_j, n_k[index_j]); + Vecd vel_star = this->riemann_solver_.getVStar(state_i, state_j, n_k[index_j]); + Real rho_star = this->riemann_solver_.getRhoStar(state_i, state_j, n_k[index_j]); + acceleration -= 2.0 * Vol_k[index_j] * + (SimTK::outer(rho_star * vel_star, vel_star) + p_star * Matd(1.0)) * e_ij * dW_ij / state_i.rho_; + } + } + this->dvel_dt_[index_i] += acceleration; + } + //=================================================================================================// + template + BasePressureRelaxationWithWall:: + BasePressureRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation) : + BasePressureRelaxationType(fluid_wall_relation->inner_relation_, + fluid_wall_relation->contact_relation_) {} + //=================================================================================================// + template + BasePressureRelaxationWithWall:: + BasePressureRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, + BaseBodyRelationContact* wall_contact_relation) : + BasePressureRelaxationType(fluid_inner_relation, + wall_contact_relation) {} + //=================================================================================================// + template + BasePressureRelaxationWithWall:: + BasePressureRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, + BaseBodyRelationContact* wall_contact_relation) : + BasePressureRelaxationType(fluid_complex_relation, + wall_contact_relation) {} + //=================================================================================================// + template + template + DensityAndEnergyRelaxation:: + DensityAndEnergyRelaxation(BaseBodyRelationType* base_body_relation, + BaseBodyRelationContact* wall_contact_relation) : + RelaxationWithWall(base_body_relation, wall_contact_relation) {} + //=================================================================================================// + template + void DensityAndEnergyRelaxation::Interaction(size_t index_i, Real dt) + { + BaseDensityAndenergyRelaxationType::Interaction(index_i, dt); + + CompressibleFluidState state_i(this->rho_n_[index_i], this->vel_n_[index_i], this->p_[index_i], this->E_[index_i]); + Real density_change_rate = 0.0; + Real energy_change_rate = 0.0; + for (size_t k = 0; k < CompressibleFluidWallData::contact_configuration_.size(); ++k) + { + //Vecd& dvel_dt_others_i = this->dvel_dt_others_[index_i]; + + StdLargeVec& Vol_k = *(this->wall_Vol_[k]); + StdLargeVec& vel_ave_k = *(this->wall_vel_ave_[k]); + StdLargeVec& dvel_dt_ave_k = *(this->wall_dvel_dt_ave_[k]); + StdLargeVec& n_k = *(this->wall_n_[k]); + Neighborhood& wall_neighborhood = (*CompressibleFluidWallData::contact_configuration_[k])[index_i]; + for (size_t n = 0; n != wall_neighborhood.current_size_; ++n) + { + size_t index_j = wall_neighborhood.j_[n]; + Vecd& e_ij = wall_neighborhood.e_ij_[n]; + Real r_ij = wall_neighborhood.r_ij_[n]; + Real dW_ij = wall_neighborhood.dW_ij_[n]; + + /*Real face_wall_external_acceleration + = dot((dvel_dt_others_i - dvel_dt_ave_k[index_j]), -e_ij);*/ + Vecd vel_in_wall = 2.0 * vel_ave_k[index_j] - state_i.vel_; + Real p_in_wall = state_i.p_; + Real rho_in_wall = state_i.rho_; + Real E_in_wall = state_i.E_; + CompressibleFluidState state_j(rho_in_wall, vel_in_wall, p_in_wall, E_in_wall); + Real p_star = this->riemann_solver_.getPStar(state_i, state_j, n_k[index_j]); + Vecd vel_star = this->riemann_solver_.getVStar(state_i, state_j, n_k[index_j]); + Real rho_star = this->riemann_solver_.getRhoStar(state_i, state_j, n_k[index_j]); + Real E_star = this->riemann_solver_.getEStar(state_i, state_j, n_k[index_j]); + density_change_rate -= 2.0 * Vol_k[index_j] * dot(rho_star * vel_star, e_ij) * dW_ij; + energy_change_rate -= 2.0 * Vol_k[index_j] * dot(E_star * vel_star + p_star * vel_star, e_ij) * dW_ij; + } + } + this->drho_dt_[index_i] += density_change_rate; + this->dE_dt_[index_i] += energy_change_rate; + } + //=================================================================================================// + template + BaseDensityAndEnergyRelaxationWithWall:: + BaseDensityAndEnergyRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation) : + DensityAndEnergyRelaxation(fluid_wall_relation->inner_relation_, + fluid_wall_relation->contact_relation_) {} + //=================================================================================================// + template + BaseDensityAndEnergyRelaxationWithWall:: + BaseDensityAndEnergyRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, + BaseBodyRelationContact* wall_contact_relation) : + DensityAndEnergyRelaxation(fluid_inner_relation, + wall_contact_relation) {} + //=================================================================================================// + template + BaseDensityAndEnergyRelaxationWithWall:: + BaseDensityAndEnergyRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, + BaseBodyRelationContact* wall_contact_relation) : + DensityAndEnergyRelaxation(fluid_complex_relation, wall_contact_relation) {} + //=================================================================================================// + } +//=================================================================================================// +} +//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.cpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.cpp new file mode 100644 index 0000000000..2a652eaee7 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.cpp @@ -0,0 +1,142 @@ +/** + * @file eulerian_fluid_dynamics.cpp + * @author Chi ZHang, Xiangyu Hu and Zhentong Wang + */ + +#include "eulerian_fluid_dynamics_inner.h" + + //=================================================================================================// +using namespace std; +//=================================================================================================// +namespace SPH +{ +//=================================================================================================// + namespace eulerian_fluid_dynamics + { + //=================================================================================================// + CompressibleFlowTimeStepInitialization + ::CompressibleFlowTimeStepInitialization(SPHBody* body, Gravity* gravity) + : ParticleDynamicsSimple(body), CompressibleFluidDataSimple(body), + rho_n_(particles_->rho_n_), dE_dt_prior_(particles_->dE_dt_prior_), pos_n_(particles_->pos_n_), + vel_n_(particles_->vel_n_), dmom_dt_prior_(particles_->dmom_dt_prior_), + gravity_(gravity) + { + } + //=================================================================================================// + void CompressibleFlowTimeStepInitialization::setupDynamics(Real dt) + { + particles_->total_ghost_particles_ = 0; + } + //=================================================================================================// + void CompressibleFlowTimeStepInitialization::Update(size_t index_i, Real dt) + { + dmom_dt_prior_[index_i] = rho_n_[index_i] * gravity_->InducedAcceleration(pos_n_[index_i]); + dE_dt_prior_[index_i] = rho_n_[index_i] * SimTK::dot(gravity_->InducedAcceleration(pos_n_[index_i]), vel_n_[index_i]); + } + //=================================================================================================// + CompressibleFluidInitialCondition:: + CompressibleFluidInitialCondition(EulerianFluidBody* body) + : ParticleDynamicsSimple(body), CompressibleFluidDataSimple(body), + pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_), mom_(particles_->mom_), + rho_n_(particles_->rho_n_), E_(particles_->E_), p_(particles_->p_) + { + gamma_ = material_->HeatCapacityRatio(); + c0_ = material_->ReferenceSoundSpeed(); + rho0_ = material_->ReferenceDensity(); + } + //=================================================================================================// + ViscousAccelerationInner::ViscousAccelerationInner(BaseBodyRelationInner* inner_relation) : + InteractionDynamics(inner_relation->sph_body_), + CompressibleFluidDataInner(inner_relation), + Vol_(particles_->Vol_), rho_n_(particles_->rho_n_), p_(particles_->p_), + mass_(particles_->mass_), dE_dt_prior_(particles_->dE_dt_prior_), + vel_n_(particles_->vel_n_), dmom_dt_prior_(particles_->dmom_dt_prior_) + { + smoothing_length_ = particle_adaptation_->ReferenceSmoothingLength(); + mu_ = material_->ReferenceViscosity(); + } + //=================================================================================================// + void ViscousAccelerationInner::Interaction(size_t index_i, Real dt) + { + Real rho_i = rho_n_[index_i]; + Vecd& vel_i = vel_n_[index_i]; + + Vecd acceleration(0), vel_derivative(0); + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + + //viscous force + vel_derivative = (vel_i - vel_n_[index_j]) + / (inner_neighborhood.r_ij_[n] + 0.01 * smoothing_length_); + acceleration += 2.0 * mu_ * vel_derivative + * Vol_[index_j] * inner_neighborhood.dW_ij_[n] / rho_i; + } + dmom_dt_prior_[index_i] += rho_n_[index_i] * acceleration; + dE_dt_prior_[index_i] += rho_n_[index_i] * dot(acceleration, vel_n_[index_i]); + } + //=================================================================================================// + AcousticTimeStepSize::AcousticTimeStepSize(EulerianFluidBody* body) + : ParticleDynamicsReduce(body), + CompressibleFluidDataSimple(body), rho_n_(particles_->rho_n_), + p_(particles_->p_), vel_n_(particles_->vel_n_) + { + smoothing_length_ = particle_adaptation_->ReferenceSmoothingLength(); + initial_reference_ = 0.0; + } + //=================================================================================================// + Real AcousticTimeStepSize::ReduceFunction(size_t index_i, Real dt) + { + return material_->getSoundSpeed(p_[index_i], rho_n_[index_i]) + vel_n_[index_i].norm(); + } + //=================================================================================================// + Real AcousticTimeStepSize::OutputResult(Real reduced_value) + { + particles_->signal_speed_max_ = reduced_value; + //since the particle does not change its configuration in pressure relaxation step + //I chose a time-step size according to Eulerian method + return 0.6 * smoothing_length_ / (reduced_value + TinyReal); + } + //=================================================================================================// + BaseRelaxation::BaseRelaxation(BaseBodyRelationInner* inner_relation) : + ParticleDynamics1Level(inner_relation->sph_body_), + CompressibleFluidDataInner(inner_relation), + Vol_(particles_->Vol_), rho_n_(particles_->rho_n_),p_(particles_->p_), + drho_dt_(particles_->drho_dt_), E_(particles_->E_), dE_dt_(particles_->dE_dt_), + dE_dt_prior_(particles_->dE_dt_prior_), + vel_n_(particles_->vel_n_), mom_(particles_->mom_), + dmom_dt_(particles_->dmom_dt_), dmom_dt_prior_(particles_->dmom_dt_prior_) {} + //=================================================================================================// + BasePressureRelaxation:: + BasePressureRelaxation(BaseBodyRelationInner* inner_relation) : + BaseRelaxation(inner_relation) {} + //=================================================================================================// + void BasePressureRelaxation::Initialization(size_t index_i, Real dt) + { + E_[index_i] += dE_dt_[index_i] * dt * 0.5; + rho_n_[index_i] += drho_dt_[index_i] * dt * 0.5; + Real rho_e = E_[index_i] - 0.5 * mom_[index_i].normSqr() / rho_n_[index_i]; + p_[index_i] = material_->getPressure(rho_n_[index_i], rho_e); + } + //=================================================================================================// + void BasePressureRelaxation::Update(size_t index_i, Real dt) + { + mom_[index_i] += dmom_dt_[index_i] * dt; + vel_n_[index_i] = mom_[index_i] / rho_n_[index_i]; + } + //=================================================================================================// + BaseDensityAndEnergyRelaxation:: + BaseDensityAndEnergyRelaxation(BaseBodyRelationInner* inner_relation) : + BaseRelaxation(inner_relation) {} + //=================================================================================================// + void BaseDensityAndEnergyRelaxation::Update(size_t index_i, Real dt) + { + E_[index_i] += dE_dt_[index_i] * dt * 0.5; + rho_n_[index_i] += drho_dt_[index_i] * dt * 0.5; + } + //=================================================================================================// + } +//=================================================================================================// +} +//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.h b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.h new file mode 100644 index 0000000000..0f7578b0b4 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.h @@ -0,0 +1,188 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file eulerian_fluid_dynamics_inner.h +* @brief Here, we define the algorithm classes for fluid dynamics within the body. +* @details We consider here compressible fluids. +* @author Chi ZHang, Xiangyu Hu and Zhentong Wang +*/ + +#pragma once + +#include "all_particle_dynamics.h" +#include "base_kernel.h" +#include "riemann_solver.h" + +namespace SPH +{ + namespace eulerian_fluid_dynamics + { + typedef DataDelegateSimple CompressibleFluidDataSimple; + typedef DataDelegateInner CompressibleFluidDataInner; + + class CompressibleFlowTimeStepInitialization + : public ParticleDynamicsSimple, public CompressibleFluidDataSimple + { + public: + CompressibleFlowTimeStepInitialization(SPHBody* body, Gravity* gravity = new Gravity(Vecd(0))); + virtual ~CompressibleFlowTimeStepInitialization() {}; + protected: + StdLargeVec& rho_n_, & dE_dt_prior_; + StdLargeVec& pos_n_, & vel_n_, & dmom_dt_prior_; + Gravity* gravity_; + virtual void setupDynamics(Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class CompressibleFluidInitialCondition + * @brief Set initial condition for a fluid body. + * This is a abstract class to be override for case specific initial conditions + */ + class CompressibleFluidInitialCondition + : public ParticleDynamicsSimple, public CompressibleFluidDataSimple + { + public: + CompressibleFluidInitialCondition(EulerianFluidBody* body); + virtual ~CompressibleFluidInitialCondition() {}; + protected: + StdLargeVec& pos_n_, & vel_n_, & mom_; + StdLargeVec& rho_n_, & E_, & p_; + Real gamma_, c0_, rho0_; + }; + + /** + * @class ViscousAccelerationInner + * @brief the viscosity force induced acceleration + */ + class ViscousAccelerationInner + : public InteractionDynamics, public CompressibleFluidDataInner + { + public: + ViscousAccelerationInner(BaseBodyRelationInner* inner_relation); + virtual ~ViscousAccelerationInner() {}; + protected: + Real mu_; + Real smoothing_length_; + StdLargeVec& Vol_, & rho_n_, & p_, & mass_, & dE_dt_prior_; + StdLargeVec& vel_n_, & dmom_dt_prior_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class AcousticTimeStepSize + * @brief Computing the acoustic time step size + */ + class AcousticTimeStepSize : + public ParticleDynamicsReduce, public CompressibleFluidDataSimple + { + public: + explicit AcousticTimeStepSize(EulerianFluidBody* body); + virtual ~AcousticTimeStepSize() {}; + protected: + StdLargeVec& rho_n_, & p_; + StdLargeVec& vel_n_; + Real smoothing_length_; + Real ReduceFunction(size_t index_i, Real dt = 0.0) override; + Real OutputResult(Real reduced_value) override; + }; + + /** + * @class BaseRelaxation + * @brief Pure abstract base class for all fluid relaxation schemes + */ + class BaseRelaxation : public ParticleDynamics1Level, public CompressibleFluidDataInner + { + public: + BaseRelaxation(BaseBodyRelationInner* inner_relation); + virtual ~BaseRelaxation() {}; + protected: + StdLargeVec& Vol_, & rho_n_, & p_, & drho_dt_, & E_, & dE_dt_, & dE_dt_prior_; + StdLargeVec& vel_n_, & mom_, & dmom_dt_, & dmom_dt_prior_; + }; + + /** + * @class BasePressureRelaxation + * @brief Abstract base class for all pressure relaxation schemes + */ + class BasePressureRelaxation : public BaseRelaxation + { + public: + BasePressureRelaxation(BaseBodyRelationInner* inner_relation); + virtual ~BasePressureRelaxation() {}; + protected: + virtual void Initialization(size_t index_i, Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class BasePressureRelaxationInner + * @brief Template class for pressure relaxation scheme with the Riemann solver + * as template variable + */ + template + class BasePressureRelaxationInner : public BasePressureRelaxation + { + public: + BasePressureRelaxationInner(BaseBodyRelationInner* inner_relation); + virtual ~BasePressureRelaxationInner() {}; + RiemannSolverType riemann_solver_; + protected: + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + using PressureRelaxationHLLCRiemannInner = BasePressureRelaxationInner; + using PressureRelaxationHLLCWithLimiterRiemannInner = BasePressureRelaxationInner; + + /** + * @class BaseDensityAndEnergyRelaxation + * @brief Abstract base class for all density relaxation schemes + */ + class BaseDensityAndEnergyRelaxation : public BaseRelaxation + { + public: + BaseDensityAndEnergyRelaxation(BaseBodyRelationInner* inner_relation); + virtual ~BaseDensityAndEnergyRelaxation() {}; + protected: + virtual void Initialization(size_t index_i, Real dt = 0.0) override {}; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class BaseDensityAndEnergyRelaxationInner + * @brief Template density relaxation scheme in HLLC Riemann solver with and without limiter + */ + template + class BaseDensityAndEnergyRelaxationInner : public BaseDensityAndEnergyRelaxation + { + public: + BaseDensityAndEnergyRelaxationInner(BaseBodyRelationInner* inner_relation); + virtual ~BaseDensityAndEnergyRelaxationInner() {}; + RiemannSolverType riemann_solver_; + protected: + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + using DensityAndEnergyRelaxationHLLCRiemannInner = BaseDensityAndEnergyRelaxationInner; + using DensityAndEnergyRelaxationHLLCWithLimiterRiemannInner = BaseDensityAndEnergyRelaxationInner; + } +} diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.hpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.hpp new file mode 100644 index 0000000000..173563421f --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/eulerian_fluid_dynamics/eulerian_fluid_dynamics_inner.hpp @@ -0,0 +1,79 @@ +/** + * @file eulerian_fluid_dynamics_inner.hpp + * @author Chi ZHang, Xiangyu Hu and Zhentong Wang + */ + +#include "eulerian_fluid_dynamics_inner.h" + +//=================================================================================================// +namespace SPH +{ +//=================================================================================================// + namespace eulerian_fluid_dynamics + { + //=================================================================================================// + template + BasePressureRelaxationInner:: + BasePressureRelaxationInner(BaseBodyRelationInner* inner_relation) : + BasePressureRelaxation(inner_relation), + riemann_solver_(*material_, *material_) {} + //=================================================================================================// + template + void BasePressureRelaxationInner::Interaction(size_t index_i, Real dt) + { + CompressibleFluidState state_i(rho_n_[index_i], vel_n_[index_i], p_[index_i], E_[index_i]); + Vecd momentum_change_rate = dmom_dt_prior_[index_i]; + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + Real dW_ij = inner_neighborhood.dW_ij_[n]; + Vecd& e_ij = inner_neighborhood.e_ij_[n]; + + CompressibleFluidState state_j(rho_n_[index_j], vel_n_[index_j], p_[index_j], E_[index_j]); + Real p_star = riemann_solver_.getPStar(state_i, state_j, e_ij); + Vecd vel_star = riemann_solver_.getVStar(state_i, state_j, e_ij); + Real rho_star = riemann_solver_.getRhoStar(state_i, state_j, e_ij); + + momentum_change_rate -= 2.0 * Vol_[index_j] * + (SimTK::outer(rho_star * vel_star, vel_star) + p_star * Matd(1.0)) * e_ij * dW_ij; + } + dmom_dt_[index_i] = momentum_change_rate; + } + //=================================================================================================// + template + BaseDensityAndEnergyRelaxationInner:: + BaseDensityAndEnergyRelaxationInner(BaseBodyRelationInner* inner_relation) : + BaseDensityAndEnergyRelaxation(inner_relation), + riemann_solver_(*material_, *material_) {} + //=================================================================================================// + template + void BaseDensityAndEnergyRelaxationInner::Interaction(size_t index_i, Real dt) + { + CompressibleFluidState state_i(rho_n_[index_i], vel_n_[index_i], p_[index_i], E_[index_i]); + Real density_change_rate = 0.0; + Real energy_change_rate = dE_dt_prior_[index_i]; + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + Vecd& e_ij = inner_neighborhood.e_ij_[n]; + Real dW_ij = inner_neighborhood.dW_ij_[n]; + + CompressibleFluidState state_j(rho_n_[index_j], vel_n_[index_j], p_[index_j], E_[index_j]); + Vecd vel_star = riemann_solver_.getVStar(state_i, state_j, e_ij); + Real p_star = riemann_solver_.getPStar(state_i, state_j, e_ij); + Real rho_star = riemann_solver_.getRhoStar(state_i, state_j, e_ij); + Real E_star = riemann_solver_.getEStar(state_i, state_j, e_ij); + + density_change_rate -= 2.0 * Vol_[index_j] * dot(rho_star * vel_star, e_ij) * dW_ij; + energy_change_rate -= 2.0 * Vol_[index_j] * dot(E_star * vel_star + p_star * vel_star, e_ij) * dW_ij; + } + drho_dt_[index_i] = density_change_rate; + dE_dt_[index_i] = energy_change_rate; + }; + //=================================================================================================// + } +//=================================================================================================// +} +//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.cpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.cpp new file mode 100644 index 0000000000..e5b591be03 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.cpp @@ -0,0 +1,299 @@ +/** + * @file fluid_dynamics_complex.cpp + * @author Chi ZHang and Xiangyu Hu + */ + +#include "fluid_dynamics_complex.h" +#include "in_output.h" +#include "geometry_level_set.h" + +namespace SPH +{ +//=================================================================================================// + namespace fluid_dynamics + { + //=================================================================================================// + FreeSurfaceIndicationComplex::FreeSurfaceIndicationComplex(BaseBodyRelationInner* inner_relation, + BaseBodyRelationContact* conatct_relation, Real thereshold) + : FreeSurfaceIndicationInner(inner_relation, thereshold), FluidContactData(conatct_relation) + { + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + Real rho0_k = contact_particles_[k]->rho0_; + contact_inv_rho0_.push_back(1.0 / rho0_k); + contact_mass_.push_back(&(contact_particles_[k]->mass_)); + } + } + //=================================================================================================// + FreeSurfaceIndicationComplex::FreeSurfaceIndicationComplex(ComplexBodyRelation* body_complex_relation, + Real thereshold) + : FreeSurfaceIndicationComplex(body_complex_relation->inner_relation_, + body_complex_relation->contact_relation_, thereshold){} + //=================================================================================================// + void FreeSurfaceIndicationComplex::Interaction(size_t index_i, Real dt) + { + FreeSurfaceIndicationInner::Interaction(index_i, dt); + + Real pos_div = 0.0; + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + StdLargeVec& contact_mass_k = *(contact_mass_[k]); + Real contact_inv_rho0_k = contact_inv_rho0_[k]; + Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + pos_div -= contact_neighborhood.dW_ij_[n] * contact_neighborhood.r_ij_[n] + * contact_inv_rho0_k * contact_mass_k[contact_neighborhood.j_[n]]; + } + } + pos_div_[index_i] += pos_div; + } + //=================================================================================================// + SpatialTemporalFreeSurfaceIdentificationComplex::SpatialTemporalFreeSurfaceIdentificationComplex + (BaseBodyRelationInner* inner_relation,BaseBodyRelationContact* conatct_relation, Real thereshold) + : FreeSurfaceIndicationComplex(inner_relation, conatct_relation, thereshold), + particle_spacing_(body_->particle_adaptation_->ReferenceSpacing()), + previous_surface_indicator_(*particles_->createAVariable("PreviousSurfaceIndicator")) + { + particles_->registerASortableVariable("PreviousSurfaceIndicator"); + for (size_t n = 0; n != particles_->total_real_particles_; ++n) + previous_surface_indicator_[n] = 1; + } + //=================================================================================================// + SpatialTemporalFreeSurfaceIdentificationComplex::SpatialTemporalFreeSurfaceIdentificationComplex + (ComplexBodyRelation* body_complex_relation,Real thereshold) + : SpatialTemporalFreeSurfaceIdentificationComplex(body_complex_relation->inner_relation_, + body_complex_relation->contact_relation_, thereshold) {} + //=================================================================================================// + void SpatialTemporalFreeSurfaceIdentificationComplex::Interaction(size_t index_i, Real dt) + { + FreeSurfaceIndicationInner::Interaction(index_i, dt); + + Real pos_div = 0.0; + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + StdLargeVec& contact_mass_k = *(contact_mass_[k]); + Real contact_inv_rho_0_k = contact_inv_rho0_[k]; + Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + pos_div -= contact_neighborhood.dW_ij_[n] * contact_neighborhood.r_ij_[n] + * contact_inv_rho_0_k * contact_mass_k[contact_neighborhood.j_[n]]; + } + } + pos_div_[index_i] += pos_div; + + surface_indicator_[index_i] = 0; + if (pos_div_[index_i] < thereshold_by_dimensions_) + { + if (previous_surface_indicator_[index_i] == 1) surface_indicator_[index_i] = 1; + else + { + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + if (previous_surface_indicator_[inner_neighborhood.j_[n]] == 1 || + previous_surface_indicator_[inner_neighborhood.j_[n]] == 2 ) + surface_indicator_[index_i] = 1; + } + } + } + } + //=================================================================================================// + void SpatialTemporalFreeSurfaceIdentificationComplex::Update(size_t index_i, Real dt) + { + previous_surface_indicator_[index_i] = surface_indicator_[index_i]; + } + //=================================================================================================// + TransportVelocityCorrectionComplex:: + TransportVelocityCorrectionComplex(ComplexBodyRelation* body_complex_relation) : + TransportVelocityCorrectionComplex(body_complex_relation->inner_relation_, + body_complex_relation->contact_relation_) {} + //=================================================================================================// + TransportVelocityCorrectionComplex:: + TransportVelocityCorrectionComplex(ComplexBodyRelation* complex_relation, + BaseBodyRelationContact* extra_conatct_relation) : + ParticleDynamicsComplex( + complex_relation, extra_conatct_relation) + { + prepareContactData(); + } + //=================================================================================================// + void TransportVelocityCorrectionComplex::prepareContactData() + { + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); + } + } + //=================================================================================================// + void TransportVelocityCorrectionComplex::Interaction(size_t index_i, Real dt) + { + TransportVelocityCorrectionInner::Interaction(index_i, dt); + + Real rho_i = rho_n_[index_i]; + + Vecd acceleration_trans(0); + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + StdLargeVec& Vol_k = *(contact_Vol_[k]); + Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + Vecd nablaW_ij = contact_neighborhood.dW_ij_[n] * contact_neighborhood.e_ij_[n]; + + //acceleration for transport velocity + acceleration_trans -= 2.0 * p_background_ * Vol_k[index_j] * nablaW_ij / rho_i; + } + } + + /** correcting particle position */ + if (surface_indicator_[index_i] == 0) pos_n_[index_i] += acceleration_trans * dt * dt * 0.5; + } + //=================================================================================================// + void PressureRelaxationRiemannWithWallOldroyd_B::Interaction(size_t index_i, Real dt) + { + PressureRelaxation::Interaction(index_i, dt); + + Real rho_i = rho_n_[index_i]; + Matd tau_i = tau_[index_i]; + + Vecd acceleration(0); + for (size_t k = 0; k < FluidWallData::contact_configuration_.size(); ++k) + { + StdLargeVec& Vol_k = *(wall_Vol_[k]); + Neighborhood& wall_neighborhood = (*FluidWallData::contact_configuration_[k])[index_i]; + for (size_t n = 0; n != wall_neighborhood.current_size_; ++n) + { + size_t index_j = wall_neighborhood.j_[n]; + Vecd nablaW_ij = wall_neighborhood.dW_ij_[n] * wall_neighborhood.e_ij_[n]; + /** stress boundary condition. */ + acceleration += 2.0 * tau_i * nablaW_ij * Vol_k[index_j] / rho_i; + } + } + + dvel_dt_[index_i] += acceleration; + } + //=================================================================================================// + void DensityRelaxationRiemannWithWallOldroyd_B::Interaction(size_t index_i, Real dt) + { + DensityRelaxation::Interaction(index_i, dt); + + Vecd vel_i = vel_n_[index_i]; + Matd tau_i = tau_[index_i]; + + Matd stress_rate(0); + for (size_t k = 0; k < FluidWallData::contact_configuration_.size(); ++k) + { + StdLargeVec& Vol_k = *(wall_Vol_[k]); + StdLargeVec& vel_ave_k = *(wall_vel_ave_[k]); + Neighborhood& wall_neighborhood = (*FluidWallData::contact_configuration_[k])[index_i]; + for (size_t n = 0; n != wall_neighborhood.current_size_; ++n) + { + size_t index_j = wall_neighborhood.j_[n]; + Vecd nablaW_ij = wall_neighborhood.dW_ij_[n] * wall_neighborhood.e_ij_[n]; + + Matd velocity_gradient = -SimTK::outer((vel_i - vel_ave_k[index_j]), nablaW_ij) * Vol_k[index_j] * 2.0; + stress_rate += ~velocity_gradient * tau_i + tau_i * velocity_gradient + - tau_i / lambda_ + (~velocity_gradient + velocity_gradient) * mu_p_ / lambda_; + } + } + dtau_dt_[index_i] += stress_rate; + } + //=================================================================================================// + ColorFunctionGradientComplex::ColorFunctionGradientComplex(BaseBodyRelationInner* inner_relation, + BaseBodyRelationContact* conatct_relation) + : ColorFunctionGradientInner(inner_relation), FluidContactData(conatct_relation) + { + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); + } + } + //=================================================================================================// + ColorFunctionGradientComplex::ColorFunctionGradientComplex(ComplexBodyRelation* body_complex_relation) + : ColorFunctionGradientComplex(body_complex_relation->inner_relation_, + body_complex_relation->contact_relation_){} + //=================================================================================================// + void ColorFunctionGradientComplex::Interaction(size_t index_i, Real dt) + { + ColorFunctionGradientInner::Interaction(index_i, dt); + + Vecd gradient(0.0); + if (pos_div_[index_i] < thereshold_by_dimensions_) + { + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + StdLargeVec& contact_vol_k = *(contact_Vol_[k]); + Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + gradient -= contact_neighborhood.dW_ij_[n] * contact_neighborhood.e_ij_[n] + * contact_vol_k[contact_neighborhood.j_[n]]; + } + } + } + color_grad_[index_i] += gradient; + surface_norm_[index_i] = color_grad_[index_i] / (color_grad_[index_i].norm() + TinyReal); + } + //=================================================================================================// + SurfaceNormWithWall::SurfaceNormWithWall(BaseBodyRelationContact* contact_relation, Real contact_angle) + : InteractionDynamics(contact_relation->sph_body_), FSIContactData(contact_relation), + contact_angle_(contact_angle), + surface_indicator_(particles_->surface_indicator_), + surface_norm_(*particles_->getVariableByName("SurfaceNormal")), + pos_div_(*particles_->getVariableByName("PositionDivergence")) + { + particle_spacing_ = contact_relation->sph_body_->particle_adaptation_->ReferenceSpacing(); + smoothing_length_ = contact_relation->sph_body_->particle_adaptation_->ReferenceSmoothingLength(); + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + wall_n_.push_back(&(contact_particles_[k]->n_)); + } + } + //=================================================================================================// + void SurfaceNormWithWall::Interaction(size_t index_i, Real dt) + { + Real large_dist(1.0e6); + Vecd n_i = surface_norm_[index_i]; + Real smoothing_factor(1.0); + Vecd smooth_norm(0); + Vecd n_i_w(0); + /** Contact interaction. */ + if(surface_indicator_[index_i] == 1) + { + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + StdLargeVec& n_k = *(wall_n_[k]); + Neighborhood& wall_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != wall_neighborhood.current_size_; ++n) + { + size_t index_j = wall_neighborhood.j_[n]; + if(wall_neighborhood.r_ij_[n] < large_dist) + { + Vecd n_w_t = n_i - dot(n_i, n_k[index_j]) * n_k[index_j]; + Vecd n_t = n_w_t / (n_w_t.norm() + TinyReal); + n_i_w = n_t * sin(contact_angle_) + cos(contact_angle_) * n_k[index_j]; + /** No change for multi-resolution. */ + Real r_ij = wall_neighborhood.r_ij_[n] * dot(n_k[index_j], wall_neighborhood.e_ij_[n]); + if(r_ij <= smoothing_length_) + { + smoothing_factor = 0.0; + }else + { + smoothing_factor = (r_ij - smoothing_length_) / smoothing_length_; + } + large_dist = wall_neighborhood.r_ij_[n]; + smooth_norm = smoothing_factor * n_i + (1.0 - smoothing_factor) * n_i_w; + surface_norm_[index_i] = smooth_norm / (smooth_norm.norm() + TinyReal); + } + } + } + } + } + //=================================================================================================// + } +//=================================================================================================// +} +//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.h b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.h new file mode 100644 index 0000000000..23ea05742a --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.h @@ -0,0 +1,365 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file fluid_dynamics_complex.h +* @brief Here, we define the algorithm classes for complex fluid dynamics, +* which is involving with either solid walls (with suffix WithWall) +* or/and other bodies treated as wall for the fluid (with suffix Complex). +* @author Chi ZHang and Xiangyu Hu +*/ + + +#ifndef FLUID_DYNAMCIS_COMPLEX_H +#define FLUID_DYNAMCIS_COMPLEX_H + + + +#include "fluid_dynamics_inner.h" + +namespace SPH +{ + namespace fluid_dynamics + { + typedef DataDelegateContact FluidWallData; + typedef DataDelegateContact FluidContactData; + typedef DataDelegateContact FSIContactData; + /** + * @class RelaxationWithWall + * @brief Abstract base class for general relaxation algorithms with wall + */ + template + class RelaxationWithWall : public BaseRelaxationType, public FluidWallData + { + public: + template + RelaxationWithWall(BaseBodyRelationType* base_body_relation, + BaseBodyRelationContact* wall_contact_relation); + virtual ~RelaxationWithWall() {}; + protected: + StdVec wall_inv_rho0_; + StdVec*> wall_mass_, wall_Vol_; + StdVec*> wall_vel_ave_, wall_dvel_dt_ave_, wall_n_; + }; + + /** + * @class FreeSurfaceIndicationComplex + * @brief indicate the particles near the free fluid surface. + */ + class FreeSurfaceIndicationComplex : public FreeSurfaceIndicationInner, public FluidContactData + { + public: + FreeSurfaceIndicationComplex(BaseBodyRelationInner* inner_relation, + BaseBodyRelationContact* contact_relation, Real thereshold = 0.75); + FreeSurfaceIndicationComplex(ComplexBodyRelation* body_complex_relation, Real thereshold = 0.75); + virtual ~FreeSurfaceIndicationComplex() {}; + protected: + StdVec contact_inv_rho0_; + StdVec*> contact_mass_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class Spatial-temporalFreeSurfaceIdentificationComplex + * @brief using the spatial-temporal method to indicate the surface particles without misjudgement. + * @brief the indicator index of 1st layer surface particles is set to 1. + * @brief the indicator index of internal fluid particles is set to 0. + */ + class SpatialTemporalFreeSurfaceIdentificationComplex : public FreeSurfaceIndicationComplex + { + public: + SpatialTemporalFreeSurfaceIdentificationComplex(BaseBodyRelationInner* inner_relation, + BaseBodyRelationContact* contact_relation, Real thereshold = 0.75); + SpatialTemporalFreeSurfaceIdentificationComplex(ComplexBodyRelation* body_complex_relation, + Real thereshold = 0.75); + virtual ~SpatialTemporalFreeSurfaceIdentificationComplex() {}; + protected: + Real particle_spacing_; + StdLargeVec& previous_surface_indicator_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + + + /** + * @class DensitySummation + * @brief computing density by summation considering contribution from contact bodies + */ + template + class DensitySummation : public ParticleDynamicsComplex + { + public: + DensitySummation(BaseBodyRelationInner* inner_relation, BaseBodyRelationContact* contact_relation); + DensitySummation(ComplexBodyRelation* body_complex_relation); + DensitySummation(ComplexBodyRelation* complex_relation, BaseBodyRelationContact* extra_contact_relation); + virtual ~DensitySummation() {}; + protected: + StdVec contact_inv_rho0_; + StdVec*> contact_mass_; + + virtual void prepareContactData() override; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + /** the case without free surface */ + using DensitySummationComplex = DensitySummation; + /** the case with free surface */ + using DensitySummationFreeSurfaceComplex = DensitySummation; + /** the case with free stream */ + using DensitySummationFreeStreamComplex = DensitySummation; + + /** + * @class ViscousWithWall + * @brief template class viscous acceleration with wall boundary + */ + template + class ViscousWithWall : public RelaxationWithWall + { + public: + // template for different combination of constructing body relations + template + ViscousWithWall(BaseBodyRelationType* base_body_relation, + BaseBodyRelationContact* wall_contact_relation); + virtual ~ViscousWithWall() {}; + protected: + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** template interface class for different pressure relaxation with wall schemes */ + template + class BaseViscousAccelerationWithWall : public BaseViscousAccelerationType + { + public: + BaseViscousAccelerationWithWall(ComplexBodyRelation* fluid_wall_relation); + BaseViscousAccelerationWithWall(BaseBodyRelationInner* fluid_inner_relation, + BaseBodyRelationContact* wall_contact_relation); + BaseViscousAccelerationWithWall(ComplexBodyRelation* fluid_complex_relation, + BaseBodyRelationContact* wall_contact_relation); + }; + using ViscousAccelerationWithWall + = BaseViscousAccelerationWithWall>; + /** + * @class TransportVelocityCorrectionComplex + * @brief transport velocity correction consdiering the contribution from contact bodies + */ + class TransportVelocityCorrectionComplex + : public ParticleDynamicsComplex + { + public: + TransportVelocityCorrectionComplex(BaseBodyRelationInner* inner_relation, + BaseBodyRelationContact* conatct_relation); + + TransportVelocityCorrectionComplex(ComplexBodyRelation* body_complex_relation); + + TransportVelocityCorrectionComplex(ComplexBodyRelation* complex_relation, + BaseBodyRelationContact* extra_conatct_relation); + virtual ~TransportVelocityCorrectionComplex() {}; + protected: + StdVec*> contact_Vol_; + + virtual void prepareContactData() override; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class PressureRelaxation + * @brief template class pressure relaxation scheme with wall boundary + */ + template + class PressureRelaxation : public RelaxationWithWall + { + public: + // template for different combination of constructing body relations + template + PressureRelaxation(BaseBodyRelationType* base_body_relation, + BaseBodyRelationContact* wall_contact_relation); + virtual ~PressureRelaxation() {}; + protected: + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + virtual Vecd computeNonConservativeAcceleration(size_t index_i) override; + }; + + /** + * @class ExtendPressureRelaxation + * @brief template class for pressure relaxation scheme with wall boundary + * and considering non-conservative acceleration term and wall penalty to prevent + * particle penetration. + */ + template + class ExtendPressureRelaxation : public PressureRelaxation + { + public: + // template for different combination of constructing body relations + template + ExtendPressureRelaxation(BaseBodyRelationType* base_body_relation, + BaseBodyRelationContact* wall_contact_relation, Real penalty_strength = 1.0); + + virtual ~ExtendPressureRelaxation() {}; + protected: + Real penalty_strength_; + StdLargeVec& non_cnsrv_dvel_dt_; + + virtual void Initialization(size_t index_i, Real dt = 0.0) override; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + virtual Vecd computeNonConservativeAcceleration(size_t index_i) override; + }; + + /** template interface class for different pressure relaxation with wall schemes */ + template + class BasePressureRelaxationWithWall : public BasePressureRelaxationType + { + public: + BasePressureRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation); + BasePressureRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, + BaseBodyRelationContact* wall_contact_relation); + BasePressureRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, + BaseBodyRelationContact* wall_contact_relation); + }; + using PressureRelaxationWithWall + = BasePressureRelaxationWithWall>; + using PressureRelaxationRiemannWithWall + = BasePressureRelaxationWithWall>; + + /** template interface class for the extended pressure relaxation with wall schemes */ + template + class ExtendPressureRelaxationWithWall : public BasePressureRelaxationType + { + public: + ExtendPressureRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation, Real penalty_strength = 1.0); + ExtendPressureRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, + BaseBodyRelationContact* wall_contact_relation, Real penalty_strength = 1.0); + ExtendPressureRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, + BaseBodyRelationContact* wall_contact_relation, Real penalty_strength = 1.0); + }; + using ExtendPressureRelaxationRiemannWithWall + = ExtendPressureRelaxationWithWall>; + + /** + * @class DensityRelaxation + * @brief template density relaxation scheme without using different Riemann solvers. + * The difference from the free surface version is that no Riemann problem is applied + */ + template + class DensityRelaxation : public RelaxationWithWall + { + public: + // template for different combination of constructing body relations + template + DensityRelaxation(BaseBodyRelationType* base_body_relation, + BaseBodyRelationContact* wall_contact_relation); + virtual ~DensityRelaxation() {}; + protected: + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** template interface class for different density relaxation schemes */ + template + class BaseDensityRelaxationWithWall : public DensityRelaxation + { + public: + BaseDensityRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation); + BaseDensityRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, + BaseBodyRelationContact* wall_contact_relation); + BaseDensityRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, + BaseBodyRelationContact* wall_contact_relation); + }; + using DensityRelaxationWithWall = BaseDensityRelaxationWithWall; + using DensityRelaxationRiemannWithWall = BaseDensityRelaxationWithWall; + + /** + * @class PressureRelaxationRiemannWithWallOldroyd_B + * @brief first half of the pressure relaxation scheme using Riemann solver. + */ + class PressureRelaxationRiemannWithWallOldroyd_B : + public PressureRelaxation + { + public: + PressureRelaxationRiemannWithWallOldroyd_B(ComplexBodyRelation* fluid_wall_relation) : + PressureRelaxation(fluid_wall_relation->inner_relation_, + fluid_wall_relation->contact_relation_) {}; + + virtual ~PressureRelaxationRiemannWithWallOldroyd_B() {}; + protected: + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class DensityRelaxationRiemannWithWallOldroyd_B + * @brief second half of the pressure relaxation scheme using Riemann solver. + */ + class DensityRelaxationRiemannWithWallOldroyd_B : + public DensityRelaxation + { + public: + DensityRelaxationRiemannWithWallOldroyd_B(ComplexBodyRelation* fluid_wall_relation) : + DensityRelaxation(fluid_wall_relation->inner_relation_, + fluid_wall_relation->contact_relation_) {}; + + virtual ~DensityRelaxationRiemannWithWallOldroyd_B() {}; + protected: + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class ColorFunctionGradientComplex + * @brief indicate the particles near the free fluid surface. + */ + class ColorFunctionGradientComplex : public ColorFunctionGradientInner, public FluidContactData + { + public: + ColorFunctionGradientComplex(BaseBodyRelationInner* inner_relation, BaseBodyRelationContact* contact_relation); + ColorFunctionGradientComplex(ComplexBodyRelation* body_complex_relation); + virtual ~ColorFunctionGradientComplex() {}; + protected: + StdVec*> contact_Vol_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class SurfaceNormWithWall + * @brief Modify surface norm when contact with wall + */ + class SurfaceNormWithWall : public InteractionDynamics, public FSIContactData + { + public: + SurfaceNormWithWall(BaseBodyRelationContact* contact_relation, Real contact_angle); + virtual ~SurfaceNormWithWall() {}; + protected: + Real contact_angle_; + Real smoothing_length_; + Real particle_spacing_; + StdLargeVec& surface_indicator_; + StdLargeVec& surface_norm_; + StdLargeVec& pos_div_; + StdVec*> wall_n_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + } +} +#endif //FLUID_DYNAMCIS_COMPLEX_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.hpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.hpp new file mode 100644 index 0000000000..a07c6e742f --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_complex.hpp @@ -0,0 +1,368 @@ +/** + * @file fluid_dynamics_complex.hpp + * @author Chi ZHang and Xiangyu Hu + */ + +#pragma once + +#include "fluid_dynamics_complex.h" + +//=================================================================================================// +namespace SPH +{ +//=================================================================================================// + namespace fluid_dynamics + { + //=================================================================================================// + template + template + RelaxationWithWall:: + RelaxationWithWall(BaseBodyRelationType* base_body_relation, + BaseBodyRelationContact* wall_contact_relation) : + BaseRelaxationType(base_body_relation), FluidWallData(wall_contact_relation) + { + if (base_body_relation->sph_body_ != wall_contact_relation->sph_body_) + { + std::cout << "\n Error: the two body_realtions do not have the same source body!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + + for (size_t k = 0; k != FluidWallData::contact_particles_.size(); ++k) + { + Real rho0_k = FluidWallData::contact_particles_[k]->rho0_; + wall_inv_rho0_.push_back(1.0 / rho0_k); + wall_mass_.push_back(&(FluidWallData::contact_particles_[k]->mass_)); + wall_Vol_.push_back(&(FluidWallData::contact_particles_[k]->Vol_)); + wall_vel_ave_.push_back(&(FluidWallData::contact_particles_[k]->vel_ave_)); + wall_dvel_dt_ave_.push_back(&(FluidWallData::contact_particles_[k]->dvel_dt_ave_)); + wall_n_.push_back(&(FluidWallData::contact_particles_[k]->n_)); + } + } + //=================================================================================================// + template + DensitySummation:: + DensitySummation(BaseBodyRelationInner* inner_relation, BaseBodyRelationContact* contact_relation) : + ParticleDynamicsComplex(inner_relation, contact_relation) + { + prepareContactData(); + } + //=================================================================================================// + template + DensitySummation:: + DensitySummation(ComplexBodyRelation* body_complex_relation) : + DensitySummation(body_complex_relation->inner_relation_, + body_complex_relation->contact_relation_) {} + //=================================================================================================// + template + DensitySummation:: + DensitySummation(ComplexBodyRelation* complex_relation, BaseBodyRelationContact* extra_contact_relation) : + ParticleDynamicsComplex(complex_relation, extra_contact_relation) + { + prepareContactData(); + } + //=================================================================================================// + template + void DensitySummation::prepareContactData() + { + for (size_t k = 0; k != this->contact_particles_.size(); ++k) { + Real rho0_k = this->contact_particles_[k]->rho0_; + contact_inv_rho0_.push_back(1.0 / rho0_k); + contact_mass_.push_back(&(this->contact_particles_[k]->mass_)); + } + } + //=================================================================================================// + template + void DensitySummation::Interaction(size_t index_i, Real dt) + { + DensitySummationInnerType::Interaction(index_i, dt); + + /** Contact interaction. */ + Real sigma(0.0); + Real inv_Vol_0_i = this->rho0_ / this->mass_[index_i]; + for (size_t k = 0; k < this->contact_configuration_.size(); ++k) + { + StdLargeVec& contact_mass_k = *(this->contact_mass_[k]); + Real contact_inv_rho0_k = contact_inv_rho0_[k]; + Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + sigma += contact_neighborhood.W_ij_[n] * inv_Vol_0_i + * contact_inv_rho0_k * contact_mass_k[contact_neighborhood.j_[n]]; + } + } + this->rho_sum_[index_i] += sigma * this->rho0_ * this->inv_sigma0_; + } + //=================================================================================================// + template + template + ViscousWithWall:: + ViscousWithWall(BaseBodyRelationType* base_body_relation, + BaseBodyRelationContact* wall_contact_relation) + : RelaxationWithWall(base_body_relation, wall_contact_relation) {} + //=================================================================================================// + template + void ViscousWithWall::Interaction(size_t index_i, Real dt) + { + BaseViscousAccelerationType::Interaction(index_i, dt); + + Real rho_i = this->rho_n_[index_i]; + Vecd& vel_i = this->vel_n_[index_i]; + + Vecd acceleration(0), vel_derivative(0); + for (size_t k = 0; k < FluidWallData::contact_configuration_.size(); ++k) + { + StdLargeVec& Vol_k = *(this->wall_Vol_[k]); + StdLargeVec& vel_ave_k = *(this->wall_vel_ave_[k]); + Neighborhood& contact_neighborhood = (*FluidWallData::contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + Real r_ij = contact_neighborhood.r_ij_[n]; + + vel_derivative = 2.0*(vel_i - vel_ave_k[index_j]) / (r_ij + 0.01 * this->smoothing_length_); + acceleration += 2.0 * this->mu_ * vel_derivative + * contact_neighborhood.dW_ij_[n] * Vol_k[index_j] / rho_i; + } + } + + this->dvel_dt_prior_[index_i] += acceleration; + } + //=================================================================================================// + template + BaseViscousAccelerationWithWall:: + BaseViscousAccelerationWithWall(ComplexBodyRelation* fluid_wall_relation) : + BaseViscousAccelerationType(fluid_wall_relation->inner_relation_, + fluid_wall_relation->contact_relation_) {} + //=================================================================================================// + template + BaseViscousAccelerationWithWall:: + BaseViscousAccelerationWithWall(BaseBodyRelationInner* fluid_inner_relation, + BaseBodyRelationContact* wall_contact_relation) : + BaseViscousAccelerationType(fluid_inner_relation, + wall_contact_relation) {} + //=================================================================================================// + template + BaseViscousAccelerationWithWall:: + BaseViscousAccelerationWithWall(ComplexBodyRelation* fluid_complex_relation, + BaseBodyRelationContact* wall_contact_relation) : + BaseViscousAccelerationType(fluid_complex_relation, + wall_contact_relation) {} + //=================================================================================================// + template + template + PressureRelaxation:: + PressureRelaxation(BaseBodyRelationType* base_body_relation, + BaseBodyRelationContact* wall_contact_relation) : + RelaxationWithWall(base_body_relation, + wall_contact_relation) {} + //=================================================================================================// + template + void PressureRelaxation::Interaction(size_t index_i, Real dt) + { + BasePressureRelaxationType::Interaction(index_i, dt); + + FluidState state_i(this->rho_n_[index_i], this->vel_n_[index_i], this->p_[index_i]); + Vecd dvel_dt_prior_i = computeNonConservativeAcceleration(index_i); + + Vecd acceleration(0.0); + for (size_t k = 0; k < FluidWallData::contact_configuration_.size(); ++k) + { + StdLargeVec& Vol_k = *(this->wall_Vol_[k]); + StdLargeVec& vel_ave_k = *(this->wall_vel_ave_[k]); + StdLargeVec& dvel_dt_ave_k = *(this->wall_dvel_dt_ave_[k]); + StdLargeVec& n_k = *(this->wall_n_[k]); + Neighborhood& wall_neighborhood = (*FluidWallData::contact_configuration_[k])[index_i]; + for (size_t n = 0; n != wall_neighborhood.current_size_; ++n) + { + size_t index_j = wall_neighborhood.j_[n]; + Vecd& e_ij = wall_neighborhood.e_ij_[n]; + Real dW_ij = wall_neighborhood.dW_ij_[n]; + Real r_ij = wall_neighborhood.r_ij_[n]; + + Real face_wall_external_acceleration = dot((dvel_dt_prior_i - dvel_dt_ave_k[index_j]), -e_ij); + Vecd vel_in_wall = 2.0 * vel_ave_k[index_j] - state_i.vel_; + Real p_in_wall = state_i.p_ + state_i.rho_ * r_ij * SMAX(0.0, face_wall_external_acceleration); + Real rho_in_wall = this->material_->DensityFromPressure(p_in_wall); + FluidState state_j(rho_in_wall, vel_in_wall, p_in_wall); + Real p_star = this->riemann_solver_.getPStar(state_i, state_j, n_k[index_j]); + acceleration -= 2.0 * p_star * e_ij * Vol_k[index_j] * dW_ij / state_i.rho_; + } + } + this->dvel_dt_[index_i] += acceleration; + } + //=================================================================================================// + template + Vecd PressureRelaxation::computeNonConservativeAcceleration(size_t index_i) + { + return this->dvel_dt_prior_[index_i]; + } + //=================================================================================================// + template + template + ExtendPressureRelaxation:: + ExtendPressureRelaxation(BaseBodyRelationType* base_body_relation, + BaseBodyRelationContact* wall_contact_relation, Real penalty_strength) : + PressureRelaxation(base_body_relation, wall_contact_relation), + penalty_strength_(penalty_strength), + non_cnsrv_dvel_dt_(*(this->particles_->template createAVariable("NonConservativeAcceleration"))) {} + //=================================================================================================// + template + void ExtendPressureRelaxation::Initialization(size_t index_i, Real dt) + { + BasePressureRelaxationType::Initialization(index_i, dt); + non_cnsrv_dvel_dt_[index_i] = Vecd(0); + } + //=================================================================================================// + template + void ExtendPressureRelaxation::Interaction(size_t index_i, Real dt) + { + PressureRelaxation::Interaction(index_i, dt); + + Real rho_i = this->rho_n_[index_i]; + Real penalty_pressure = fabs(this->p_[index_i]); + Vecd acceleration(0); + for (size_t k = 0; k < FluidWallData::contact_configuration_.size(); ++k) + { + Real particle_spacing_j1 = 1.0 / FluidWallData::contact_bodies_[k]->particle_adaptation_->ReferenceSpacing(); + Real particle_spacing_ratio2 = 1.0 / (this->body_->particle_adaptation_->ReferenceSpacing() * particle_spacing_j1); + particle_spacing_ratio2 *= 0.1 * particle_spacing_ratio2; + + StdLargeVec& Vol_k = *(this->wall_Vol_[k]); + StdLargeVec& n_k = *(this->wall_n_[k]); + Neighborhood& wall_neighborhood = (*FluidWallData::contact_configuration_[k])[index_i]; + for (size_t n = 0; n != wall_neighborhood.current_size_; ++n) + { + size_t index_j = wall_neighborhood.j_[n]; + Vecd& e_ij = wall_neighborhood.e_ij_[n]; + Real dW_ij = wall_neighborhood.dW_ij_[n]; + Real r_ij = wall_neighborhood.r_ij_[n]; + Vecd& n_j = n_k[index_j]; + + /** penalty method to prevent particle running into boundary */ + Real projection = dot(e_ij, n_j); + Real delta = 2.0 * projection * r_ij * particle_spacing_j1; + Real beta = delta < 1.0 ? (1.0 - delta) * (1.0 - delta) * particle_spacing_ratio2 : 0.0; + Real penalty = penalty_strength_ * beta * projection * penalty_pressure; + + //pressure force + acceleration -= 2.0 * penalty * n_j * Vol_k[index_j] * dW_ij / rho_i; + } + } + this->dvel_dt_[index_i] += acceleration; + } + //=================================================================================================// + template + Vecd ExtendPressureRelaxation::computeNonConservativeAcceleration(size_t index_i) + { + Vecd acceleration = BasePressureRelaxationType::computeNonConservativeAcceleration(index_i); + non_cnsrv_dvel_dt_[index_i] = acceleration; + return acceleration; + } + //=================================================================================================// + template + BasePressureRelaxationWithWall:: + BasePressureRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation) : + BasePressureRelaxationType(fluid_wall_relation->inner_relation_, + fluid_wall_relation->contact_relation_) {} + //=================================================================================================// + template + BasePressureRelaxationWithWall:: + BasePressureRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, + BaseBodyRelationContact* wall_contact_relation) : + BasePressureRelaxationType(fluid_inner_relation, + wall_contact_relation) {} + //=================================================================================================// + template + BasePressureRelaxationWithWall:: + BasePressureRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, + BaseBodyRelationContact* wall_contact_relation) : + BasePressureRelaxationType(fluid_complex_relation, + wall_contact_relation) {} + //=================================================================================================// + template + ExtendPressureRelaxationWithWall:: + ExtendPressureRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation, Real penalty_strength) : + BasePressureRelaxationType(fluid_wall_relation->inner_relation_, + fluid_wall_relation->contact_relation_, penalty_strength) {} + //=================================================================================================// + template + ExtendPressureRelaxationWithWall:: + ExtendPressureRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, + BaseBodyRelationContact* wall_contact_relation, Real penalty_strength) : + BasePressureRelaxationType(fluid_inner_relation, + wall_contact_relation, penalty_strength) {} + //=================================================================================================// + template + ExtendPressureRelaxationWithWall:: + ExtendPressureRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, + BaseBodyRelationContact* wall_contact_relation, Real penalty_strength) : + BasePressureRelaxationType(fluid_complex_relation, + wall_contact_relation, penalty_strength) {} + //=================================================================================================// + template + template + DensityRelaxation:: + DensityRelaxation(BaseBodyRelationType* base_body_relation, + BaseBodyRelationContact* wall_contact_relation) : + RelaxationWithWall(base_body_relation, wall_contact_relation) {} + //=================================================================================================// + template + void DensityRelaxation::Interaction(size_t index_i, Real dt) + { + BaseDensityRelaxationType::Interaction(index_i, dt); + + FluidState state_i(this->rho_n_[index_i], this->vel_n_[index_i], this->p_[index_i]); + Real density_change_rate = 0.0; + for (size_t k = 0; k < FluidWallData::contact_configuration_.size(); ++k) + { + Vecd& dvel_dt_prior_i = this->dvel_dt_prior_[index_i]; + + StdLargeVec& Vol_k = *(this->wall_Vol_[k]); + StdLargeVec& vel_ave_k = *(this->wall_vel_ave_[k]); + StdLargeVec& dvel_dt_ave_k = *(this->wall_dvel_dt_ave_[k]); + StdLargeVec& n_k = *(this->wall_n_[k]); + Neighborhood& wall_neighborhood = (*FluidWallData::contact_configuration_[k])[index_i]; + for (size_t n = 0; n != wall_neighborhood.current_size_; ++n) + { + size_t index_j = wall_neighborhood.j_[n]; + Vecd& e_ij = wall_neighborhood.e_ij_[n]; + Real r_ij = wall_neighborhood.r_ij_[n]; + Real dW_ij = wall_neighborhood.dW_ij_[n]; + + Real face_wall_external_acceleration + = dot((dvel_dt_prior_i - dvel_dt_ave_k[index_j]), -e_ij); + Vecd vel_in_wall = 2.0 * vel_ave_k[index_j] - state_i.vel_; + Real p_in_wall = state_i.p_ + state_i.rho_ * r_ij * SMAX(0.0, face_wall_external_acceleration); + Real rho_in_wall = this->material_->DensityFromPressure(p_in_wall); + FluidState state_j(rho_in_wall, vel_in_wall, p_in_wall); + Vecd vel_star = this->riemann_solver_.getVStar(state_i, state_j, n_k[index_j]); + density_change_rate += 2.0 * state_i.rho_ * Vol_k[index_j] * dot(state_i.vel_ - vel_star, e_ij) * dW_ij; + } + } + this->drho_dt_[index_i] += density_change_rate; + } + //=================================================================================================// + template + BaseDensityRelaxationWithWall:: + BaseDensityRelaxationWithWall(ComplexBodyRelation* fluid_wall_relation) : + DensityRelaxation(fluid_wall_relation->inner_relation_, + fluid_wall_relation->contact_relation_) {} + //=================================================================================================// + template + BaseDensityRelaxationWithWall:: + BaseDensityRelaxationWithWall(BaseBodyRelationInner* fluid_inner_relation, + BaseBodyRelationContact* wall_contact_relation) : + DensityRelaxation(fluid_inner_relation, + wall_contact_relation) {} + //=================================================================================================// + template + BaseDensityRelaxationWithWall:: + BaseDensityRelaxationWithWall(ComplexBodyRelation* fluid_complex_relation, + BaseBodyRelationContact* wall_contact_relation) : + DensityRelaxation(fluid_complex_relation, wall_contact_relation) {} + //=================================================================================================// + } +//=================================================================================================// +} +//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.cpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.cpp new file mode 100644 index 0000000000..1e5a52b18f --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.cpp @@ -0,0 +1,18 @@ +/** + * @file fluid_dynamics_compound.cpp + * @author Chi ZHang and Xiangyu Hu + */ + +#include "fluid_dynamics_compound.h" +#include "in_output.h" +#include "geometry_level_set.h" + +namespace SPH +{ +//=================================================================================================// + namespace fluid_dynamics + { + + } +} +//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.h b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.h new file mode 100644 index 0000000000..c195698f52 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.h @@ -0,0 +1,103 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file fluid_dynamics_compound.h +* @brief Here, we define the algorithm classes for compound fluid dynamics, +* which is involving with fluid dynamcis inner and fluid dynamics complex. +* @author Chi ZHang and Xiangyu Hu +*/ + + +#ifndef FLUID_DYNAMCIS_COMPOUND_H +#define FLUID_DYNAMCIS_COMPOUND_H + +#include "fluid_dynamics_complex.h" + +namespace SPH +{ + namespace fluid_dynamics + { + /** + * @class SurfaceParticlesIndicator + * @brief this compound class contains SpatialTemporalFreeSurfaceIdentificationComplex method, + * @brief MultilayeredSurfaceParticlesIdentification method and FreeStreamInletOutletSurfaceParticleIdentification method. + * @brief SpatialTemporalFreeSurfaceIdentificationComplex method detect first layer suface particles. + * @brief MultilayeredSurfaceParticlesIdentification method detect 2nd and 3rd layer surface particles. + * @brief FreeStreamInletOutletSurfaceParticleIdentification method can detect inlet and outlet surface particles seperately. + * @brief you can also combine these three subclasses according to your detailed case. + */ + class SurfaceParticlesIndicator + { + protected: + /** + * @class FirstyLayerSurfaceParticleIndicator + * @brief detect first layer suface particles. + */ + class FirstyLayerSurfaceParticleIndicator : public SpatialTemporalFreeSurfaceIdentificationComplex + { + public: + FirstyLayerSurfaceParticleIndicator(BaseBodyRelationInner* inner_relation, + BaseBodyRelationContact* contact_relation, Real thereshold = 0.75) : + SpatialTemporalFreeSurfaceIdentificationComplex(inner_relation, contact_relation, thereshold) {}; + FirstyLayerSurfaceParticleIndicator(ComplexBodyRelation* body_complex_relation, Real thereshold = 0.75) : + FirstyLayerSurfaceParticleIndicator(body_complex_relation->inner_relation_, + body_complex_relation->contact_relation_, thereshold) {}; + virtual ~FirstyLayerSurfaceParticleIndicator() {}; + }; + + /** + * @class SecondAndThirdLayersSurfaceParticlesIndicator + * @brief detect 2nd and 3rd layer surface particles at inlet and outlet. + */ + class SecondAndThirdLayersSurfaceParticlesIndicator : public MultilayeredSurfaceParticlesIdentification + { + public: + SecondAndThirdLayersSurfaceParticlesIndicator(BaseBodyRelationInner* inner_relation) : + MultilayeredSurfaceParticlesIdentification(inner_relation) {}; + virtual ~SecondAndThirdLayersSurfaceParticlesIndicator() {}; + }; + + /** + * @class InletOutletSurfaceParticleIndicator + * @brief detect inlet and outlet surface particles independantly. + */ + class InletOutletSurfaceParticleIndicator : public FreeStreamInletOutletSurfaceParticleIdentification + { + public: + InletOutletSurfaceParticleIndicator(BaseBodyRelationInner* inner_relation, int axis_direction) : + FreeStreamInletOutletSurfaceParticleIdentification(inner_relation, axis_direction) {}; + virtual ~InletOutletSurfaceParticleIndicator() {}; + }; + + public: + SurfaceParticlesIndicator(ComplexBodyRelation* body_complex_relation, BaseBodyRelationInner* inner_relation, int axis_direction = 0) : + first_layer(body_complex_relation), second_and_third_layers(inner_relation), inlet_outlet_layers(inner_relation, axis_direction) {}; + virtual ~SurfaceParticlesIndicator() {}; + + FirstyLayerSurfaceParticleIndicator first_layer; + SecondAndThirdLayersSurfaceParticlesIndicator second_and_third_layers; + InletOutletSurfaceParticleIndicator inlet_outlet_layers; + }; + } +} +#endif //FLUID_DYNAMCIS_COMPOUND_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.hpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.hpp new file mode 100644 index 0000000000..d1c26f997e --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_compound.hpp @@ -0,0 +1,21 @@ +/** + * @file fluid_dynamics_compound.hpp + * @author Chi ZHang and Xiangyu Hu + */ + +#pragma once + +#include "fluid_dynamics_compound.h" + +//=================================================================================================// +namespace SPH +{ +//=================================================================================================// + namespace fluid_dynamics + { + + //=================================================================================================// + } +//=================================================================================================// +} +//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.cpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.cpp new file mode 100644 index 0000000000..d353dec971 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.cpp @@ -0,0 +1,768 @@ +/** + * @file fluid_dynamics.cpp + * @author Chi ZHang and Xiangyu Hu + */ + +#include "fluid_dynamics_inner.h" +#include "in_output.h" +#include "geometry_level_set.h" + +namespace SPH +{ +//=================================================================================================// + namespace fluid_dynamics + { + //=================================================================================================// + FluidInitialCondition:: + FluidInitialCondition(FluidBody* body) + : ParticleDynamicsSimple(body), FluidDataSimple(body), + pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_) + { + } + //=================================================================================================// + FreeSurfaceIndicationInner:: + FreeSurfaceIndicationInner(BaseBodyRelationInner* inner_relation, Real thereshold) : + InteractionDynamicsWithUpdate(inner_relation->sph_body_), + FluidDataInner(inner_relation), + thereshold_by_dimensions_(thereshold*(Real)Dimensions), + Vol_(particles_->Vol_), + pos_div_(*particles_->createAVariable("PositionDivergence")), + surface_indicator_(particles_->surface_indicator_) + { + smoothing_length_ = inner_relation->sph_body_->particle_adaptation_->ReferenceSmoothingLength(); + } + //=================================================================================================// + void FreeSurfaceIndicationInner::Interaction(size_t index_i, Real dt) + { + Real pos_div = 0.0; + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + pos_div -= inner_neighborhood.dW_ij_[n] + * inner_neighborhood.r_ij_[n] * Vol_[inner_neighborhood.j_[n]]; + } + pos_div_[index_i] = pos_div; + } + //=================================================================================================// + void FreeSurfaceIndicationInner::Update(size_t index_i, Real dt) + { + bool is_free_surface = pos_div_[index_i] < thereshold_by_dimensions_ ? true : false; + + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + /** Two layer particles.*/ + if (pos_div_[inner_neighborhood.j_[n]] < thereshold_by_dimensions_ && inner_neighborhood.r_ij_[n] < smoothing_length_) + { + is_free_surface = true; + break; + } + } + surface_indicator_[index_i] = is_free_surface ? 1 : 0; + } + //=================================================================================================// + MultilayeredSurfaceParticlesIdentification:: + MultilayeredSurfaceParticlesIdentification(BaseBodyRelationInner* inner_relation) : + InteractionDynamicsWithUpdate(inner_relation->sph_body_), + FluidDataInner(inner_relation),pos_n_(particles_->pos_n_), + surface_indicator_(particles_->surface_indicator_), + previous_surface_indicator_(*particles_->createAVariable("PreviousSurfaceIndicator")) {} + //=================================================================================================// + void MultilayeredSurfaceParticlesIdentification::Interaction(size_t index_i, Real dt) + { + if (surface_indicator_[index_i] != 1) + { + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + if (surface_indicator_[inner_neighborhood.j_[n]] == 1) + { + surface_indicator_[index_i] = 2; + break; + } + } + } + } + //=================================================================================================// + void MultilayeredSurfaceParticlesIdentification::Update(size_t index_i, Real dt) + { + if (surface_indicator_[index_i] == 2)surface_indicator_[index_i] = 1; + previous_surface_indicator_[index_i] = surface_indicator_[index_i]; + } + //=================================================================================================// + FreeStreamInletOutletSurfaceParticleIdentification :: + FreeStreamInletOutletSurfaceParticleIdentification(BaseBodyRelationInner* inner_relation, + int axis_direction) :ParticleDynamicsSimple(inner_relation->sph_body_), + FluidDataInner(inner_relation), pos_n_(particles_->pos_n_), + fluid_body_domain_bounds_(body_->getBodyDomainBounds()), axis_(axis_direction), + particle_spacing_(body_->particle_adaptation_->ReferenceSpacing()), + surface_indicator_(particles_->surface_indicator_), + previous_surface_indicator_(*particles_->createAVariable("PreviousSurfaceIndicator")) {} + //=================================================================================================// + void FreeStreamInletOutletSurfaceParticleIdentification::Update(size_t index_i, Real dt) + { + if (surface_indicator_[index_i] == 1) + { + Real distance_to_inlet_bound = pos_n_[index_i][axis_] - fluid_body_domain_bounds_.first[axis_]; + Real distance_to_outlet_bound = fluid_body_domain_bounds_.second[axis_] - pos_n_[index_i][axis_]; + + if (distance_to_inlet_bound < 3.5 * particle_spacing_ || distance_to_outlet_bound < 3.5 * particle_spacing_) + surface_indicator_[index_i] = 2; + } + previous_surface_indicator_[index_i] = surface_indicator_[index_i]; + } + //=================================================================================================// + TransportVelocityCorrectionComplex:: + TransportVelocityCorrectionComplex(BaseBodyRelationInner* inner_relation, + BaseBodyRelationContact* conatct_relation) : + ParticleDynamicsComplex( + inner_relation, conatct_relation) + { + prepareContactData(); + } + //=================================================================================================// + DensitySummationInner::DensitySummationInner(BaseBodyRelationInner* inner_relation) : + InteractionDynamicsWithUpdate(inner_relation->sph_body_), + FluidDataInner(inner_relation), + Vol_(particles_->Vol_), rho_n_(particles_->rho_n_), mass_(particles_->mass_), + rho_sum_(particles_->rho_sum_) + { + W0_ = particle_adaptation_->getKernel()->W0(Vecd(0)); + rho0_ = particles_->rho0_; + inv_sigma0_ = 1.0 / particles_->sigma0_; + } + //=================================================================================================// + void DensitySummationInner::Interaction(size_t index_i, Real dt) + { + /** Inner interaction. */ + Real sigma = W0_; + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + sigma += inner_neighborhood.W_ij_[n]; + + rho_sum_[index_i] = sigma * rho0_ * inv_sigma0_; + } + //=================================================================================================// + void DensitySummationInner::Update(size_t index_i, Real dt) + { + rho_n_[index_i] = ReinitializedDensity(rho_sum_[index_i], rho0_, rho_n_[index_i]); + Vol_[index_i] = mass_[index_i] / rho_n_[index_i]; + } + //=================================================================================================// + void DensitySummationFreeStreamInner::Update(size_t index_i, Real dt) + { + if (surface_indicator_[index_i] == 1 || surface_indicator_[index_i] == 2) + rho_n_[index_i] = ReinitializedDensity(rho_sum_[index_i], rho0_, rho_n_[index_i]); + else + rho_n_[index_i] = rho_sum_[index_i]; + + Vol_[index_i] = mass_[index_i] / rho_n_[index_i]; + } + //=================================================================================================// + ViscousAccelerationInner::ViscousAccelerationInner(BaseBodyRelationInner* inner_relation) : + InteractionDynamics(inner_relation->sph_body_), + FluidDataInner(inner_relation), + Vol_(particles_->Vol_), rho_n_(particles_->rho_n_), p_(particles_->p_), + vel_n_(particles_->vel_n_), dvel_dt_prior_(particles_->dvel_dt_prior_) + { + mu_ = material_->ReferenceViscosity(); + smoothing_length_ = particle_adaptation_->ReferenceSmoothingLength(); + } + //=================================================================================================// + void ViscousAccelerationInner::Interaction(size_t index_i, Real dt) + { + Real rho_i = rho_n_[index_i]; + Vecd& vel_i = vel_n_[index_i]; + + Vecd acceleration(0), vel_derivative(0); + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + + //viscous force + vel_derivative = (vel_i - vel_n_[index_j]) + / (inner_neighborhood.r_ij_[n] + 0.01 * smoothing_length_); + acceleration += 2.0 * mu_ * vel_derivative + * Vol_[index_j] * inner_neighborhood.dW_ij_[n] / rho_i; + } + + dvel_dt_prior_[index_i] += acceleration; + } + //=================================================================================================// + void AngularConservativeViscousAccelerationInner::Interaction(size_t index_i, Real dt) + { + Real rho_i = rho_n_[index_i]; + Vecd& vel_i = vel_n_[index_i]; + + Vecd acceleration(0); + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + Vecd& e_ij = inner_neighborhood.e_ij_[n]; + Real r_ij = inner_neighborhood.r_ij_[n]; + + /** The following viscous force is given in Monaghan 2005 (Rep. Prog. Phys.), it seems that + * this formulation is more accurate than the previous one for Taylor-Green-Vortex flow. */ + Real v_r_ij = dot(vel_i - vel_n_[index_j], r_ij * e_ij); + Real eta_ij = 8.0 * mu_ * v_r_ij / (r_ij * r_ij + 0.01 * smoothing_length_); + acceleration += eta_ij * Vol_[index_j] / rho_i + * inner_neighborhood.dW_ij_[n] * e_ij; + } + + dvel_dt_prior_[index_i] += acceleration; + } + //=================================================================================================// + TransportVelocityCorrectionInner:: + TransportVelocityCorrectionInner(BaseBodyRelationInner* inner_relation) : + InteractionDynamics(inner_relation->sph_body_), + FluidDataInner(inner_relation), + Vol_(particles_->Vol_), rho_n_(particles_->rho_n_), + pos_n_(particles_->pos_n_), + surface_indicator_(particles_->surface_indicator_), p_background_(0){} + //=================================================================================================// + void TransportVelocityCorrectionInner::setupDynamics(Real dt) + { + Real speed_max = particles_->speed_max_; + Real density = material_->ReferenceDensity(); + p_background_ = 7.0 * density * speed_max * speed_max; + } + //=================================================================================================// + void TransportVelocityCorrectionInner::Interaction(size_t index_i, Real dt) + { + Real rho_i = rho_n_[index_i]; + + Vecd acceleration_trans(0); + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + Vecd nablaW_ij = inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]; + + //acceleration for transport velocity + acceleration_trans -= 2.0 * p_background_*Vol_[index_j] * nablaW_ij / rho_i; + } + + if (surface_indicator_[index_i] == 0) pos_n_[index_i] += acceleration_trans * dt * dt * 0.5; + } + //=================================================================================================// + AcousticTimeStepSize::AcousticTimeStepSize(FluidBody* body) + : ParticleDynamicsReduce(body), + FluidDataSimple(body), rho_n_(particles_->rho_n_), + p_(particles_->p_), vel_n_(particles_->vel_n_) + { + smoothing_length_ = particle_adaptation_->ReferenceSmoothingLength(); + initial_reference_ = 0.0; + } + //=================================================================================================// + Real AcousticTimeStepSize::ReduceFunction(size_t index_i, Real dt) + { + return material_->getSoundSpeed(p_[index_i], rho_n_[index_i]) + vel_n_[index_i].norm(); + } + //=================================================================================================// + Real AcousticTimeStepSize::OutputResult(Real reduced_value) + { + particles_->signal_speed_max_ = reduced_value; + //since the particle does not change its configuration in pressure relaxation step + //I chose a time-step size according to Eulerian method + return 0.6 * smoothing_length_ / (reduced_value + TinyReal); + } + //=================================================================================================// + AdvectionTimeStepSize::AdvectionTimeStepSize(FluidBody* body, Real U_max) + : ParticleDynamicsReduce(body), + FluidDataSimple(body), vel_n_(particles_->vel_n_) + { + smoothing_length_ = particle_adaptation_->ReferenceSmoothingLength(); + Real rho_0 = material_->ReferenceDensity(); + Real mu = material_->ReferenceViscosity(); + Real viscous_speed = mu / rho_0 / smoothing_length_; + Real u_max = SMAX(viscous_speed, U_max); + initial_reference_ = u_max * u_max; + } + //=================================================================================================// + Real AdvectionTimeStepSize::ReduceFunction(size_t index_i, Real dt) + { + return vel_n_[index_i].normSqr(); + } + //=================================================================================================// + Real AdvectionTimeStepSize::OutputResult(Real reduced_value) + { + Real speed_max = sqrt(reduced_value); + particles_->speed_max_ = speed_max; + return 0.25 * smoothing_length_ / (speed_max + TinyReal); + } + //=================================================================================================// + AdvectionTimeStepSizeForImplicitViscosity:: + AdvectionTimeStepSizeForImplicitViscosity(FluidBody* body, Real U_max) + : AdvectionTimeStepSize(body, U_max) + { + initial_reference_ = U_max * U_max; + } + //=================================================================================================// + VorticityInner:: + VorticityInner(BaseBodyRelationInner* body_inner_relation) : + InteractionDynamics(body_inner_relation->sph_body_), + FluidDataInner(body_inner_relation), + Vol_(particles_->Vol_), vel_n_(particles_->vel_n_), + vorticity_(*particles_->createAVariable("VorticityInner")) + { + particles_->addAVariableToWrite("VorticityInner"); + } + //=================================================================================================// + void VorticityInner::Interaction(size_t index_i, Real dt) + { + Vecd& vel_i = vel_n_[index_i]; + + AngularVecd vorticity(0); + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + Vecd r_ij = inner_neighborhood.r_ij_[n] * inner_neighborhood.e_ij_[n]; + + Vecd vel_diff = vel_i - vel_n_[index_j]; + vorticity += SimTK::cross(vel_diff, r_ij) * Vol_[index_j] * inner_neighborhood.dW_ij_[n]; + } + + vorticity_[index_i] = vorticity; + } + //=================================================================================================// + BaseRelaxation::BaseRelaxation(BaseBodyRelationInner* inner_relation) : + ParticleDynamics1Level(inner_relation->sph_body_), + FluidDataInner(inner_relation), + Vol_(particles_->Vol_), mass_(particles_->mass_), rho_n_(particles_->rho_n_), + p_(particles_->p_), drho_dt_(particles_->drho_dt_), + pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_), + dvel_dt_(particles_->dvel_dt_), dvel_dt_prior_(particles_->dvel_dt_prior_) {} + //=================================================================================================// + BasePressureRelaxation:: + BasePressureRelaxation(BaseBodyRelationInner* inner_relation) : + BaseRelaxation(inner_relation) {} + //=================================================================================================// + void BasePressureRelaxation::Initialization(size_t index_i, Real dt) + { + rho_n_[index_i] += drho_dt_[index_i] * dt * 0.5; + Vol_[index_i] = mass_[index_i] / rho_n_[index_i]; + p_[index_i] = material_->getPressure(rho_n_[index_i]); + pos_n_[index_i] += vel_n_[index_i] * dt * 0.5; + } + //=================================================================================================// + void BasePressureRelaxation::Update(size_t index_i, Real dt) + { + vel_n_[index_i] += dvel_dt_[index_i] * dt; + } + //=================================================================================================// + Vecd BasePressureRelaxation::computeNonConservativeAcceleration(size_t index_i) + { + Real rho_i = rho_n_[index_i]; + Real p_i = p_[index_i]; + Vecd acceleration = dvel_dt_prior_[index_i]; + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + Real dW_ij = inner_neighborhood.dW_ij_[n]; + Vecd& e_ij = inner_neighborhood.e_ij_[n]; + + Real rho_j = rho_n_[index_j]; + Real p_j = p_[index_j]; + + Real p_star = (rho_i * p_j + rho_j * p_i) / (rho_i + rho_j); + acceleration += (p_i - p_star) * Vol_[index_j] * dW_ij * e_ij / rho_i; + } + return acceleration; + } + //=================================================================================================// + BaseDensityRelaxation:: + BaseDensityRelaxation(BaseBodyRelationInner* inner_relation) : + BaseRelaxation(inner_relation) {} + //=================================================================================================// + void BaseDensityRelaxation::Initialization(size_t index_i, Real dt) + { + pos_n_[index_i] += vel_n_[index_i] * dt * 0.5; + } + //=================================================================================================// + void BaseDensityRelaxation::Update(size_t index_i, Real dt) + { + rho_n_[index_i] += drho_dt_[index_i] * dt * 0.5; + } + //=================================================================================================// + void FreeStreamBoundaryVelocityCorrection::Update(size_t index_i, Real dt) + { + vel_n_[index_i] += dvel_dt_[index_i] * dt; + dvel_dt_[index_i] = Vecd(0.0, 0.0); + + if (surface_indicator_[index_i] == 1) + { + Real run_time_ = GlobalStaticVariables::physical_time_; + Real u_ave_ = run_time_ < t_ref_ ? 0.5 * u_ref_ * (1.0 - cos(Pi * run_time_ / t_ref_)) : u_ref_; + vel_n_[index_i][0] = u_ave_ + SMIN(rho_sum[index_i], rho_ref_) * (vel_n_[index_i][0] - u_ave_) / rho_ref_; + } + } + //=================================================================================================// + PressureRelaxationRiemannInnerOldroyd_B :: + PressureRelaxationRiemannInnerOldroyd_B(BaseBodyRelationInner* inner_relation) : + PressureRelaxationRiemannInner(inner_relation), + tau_(dynamic_cast(body_->base_particles_)->tau_), + dtau_dt_(dynamic_cast(body_->base_particles_)->dtau_dt_) {} + //=================================================================================================// + void PressureRelaxationRiemannInnerOldroyd_B::Initialization(size_t index_i, Real dt) + { + PressureRelaxationRiemannInner::Initialization(index_i, dt); + + tau_[index_i] += dtau_dt_[index_i] * dt * 0.5; + } + //=================================================================================================// + void PressureRelaxationRiemannInnerOldroyd_B::Interaction(size_t index_i, Real dt) + { + PressureRelaxationRiemannInner::Interaction(index_i, dt); + + Real rho_i = rho_n_[index_i]; + Matd tau_i = tau_[index_i]; + + Vecd acceleration(0); + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + Vecd nablaW_ij = inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]; + + //elastic force + acceleration += (tau_i + tau_[index_j]) * nablaW_ij * Vol_[index_j] / rho_i; + } + + dvel_dt_[index_i] += acceleration; + } + //=================================================================================================// + DensityRelaxationRiemannInnerOldroyd_B:: + DensityRelaxationRiemannInnerOldroyd_B(BaseBodyRelationInner* inner_relation) : + DensityRelaxationRiemannInner(inner_relation), + tau_(dynamic_cast(body_->base_particles_)->tau_), + dtau_dt_(dynamic_cast(body_->base_particles_)->dtau_dt_) + { + Oldroyd_B_Fluid *oldroy_b_fluid + = dynamic_cast(body_->base_particles_->base_material_); + mu_p_ = oldroy_b_fluid->ReferencePolymericViscosity(); + lambda_ = oldroy_b_fluid->getReferenceRelaxationTime(); + } + //=================================================================================================// + void DensityRelaxationRiemannInnerOldroyd_B::Interaction(size_t index_i, Real dt) + { + DensityRelaxationRiemannInner::Interaction(index_i, dt); + + Vecd vel_i = vel_n_[index_i]; + Matd tau_i = tau_[index_i]; + + Matd stress_rate(0); + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + Vecd nablaW_ij = inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]; + + Matd velocity_gradient = - SimTK::outer((vel_i - vel_n_[index_j]), nablaW_ij) * Vol_[index_j]; + stress_rate += ~velocity_gradient * tau_i + tau_i * velocity_gradient + - tau_i / lambda_ + (~velocity_gradient + velocity_gradient) * mu_p_ / lambda_; + } + + dtau_dt_[index_i] = stress_rate; + } + //=================================================================================================// + void DensityRelaxationRiemannInnerOldroyd_B::Update(size_t index_i, Real dt) + { + DensityRelaxationRiemannInner::Update(index_i, dt); + + tau_[index_i] += dtau_dt_[index_i] * dt * 0.5; + } + //=================================================================================================// + FlowRelaxationBuffer:: + FlowRelaxationBuffer(FluidBody* body, BodyPartByCell* body_part) : + PartDynamicsByCell(body, body_part), FluidDataSimple(body), + pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_), relaxation_rate_(0.3) + { + }; + //=================================================================================================// + void FlowRelaxationBuffer + ::Update(size_t index_i, Real dt) + { + vel_n_[index_i] += + relaxation_rate_ * ( getTargetVelocity(pos_n_[index_i], vel_n_[index_i]) - vel_n_[index_i]); + } + //=================================================================================================// + DampingBoundaryCondition:: + DampingBoundaryCondition(FluidBody* body, BodyPartByCell* body_part) : + PartDynamicsByCell(body, body_part), FluidDataSimple(body), + pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_), strength_(5.0) + { + damping_zone_bounds_ = body_part->BodyPartBounds(); + }; + //=================================================================================================// + void DampingBoundaryCondition::Update(size_t index_i, Real dt) + { + Real damping_factor = (pos_n_[index_i][0] - damping_zone_bounds_.first[0]) / + (damping_zone_bounds_.second[0]- damping_zone_bounds_.first[0]); + vel_n_[index_i] *= (1.0 - dt * strength_ * damping_factor * damping_factor); + } + //=================================================================================================// + StaticConfinementDensity:: + StaticConfinementDensity(FluidBody* body, NearShapeSurface* near_surface) : + PartDynamicsByCell(body, near_surface), FluidDataSimple(body), + rho0_(particles_->rho0_), inv_sigma0_(1.0 / particles_->sigma0_), + mass_(particles_->mass_), rho_sum_(particles_->rho_sum_), pos_n_(particles_->pos_n_), + level_set_complex_shape_(near_surface->getLevelSetComplexShape()) {} + //=================================================================================================// + void StaticConfinementDensity::Update(size_t index_i, Real dt) + { + Real inv_Vol_0_i = rho0_ / mass_[index_i]; + rho_sum_[index_i] += + level_set_complex_shape_->computeKernelIntegral(pos_n_[index_i]) * inv_Vol_0_i * rho0_ * inv_sigma0_ ; + } + //=================================================================================================// + StaticConfinementPressureRelaxation:: + StaticConfinementPressureRelaxation(FluidBody* body, NearShapeSurface* near_surface) : + PartDynamicsByCell(body, near_surface), FluidDataSimple(body), + rho_n_(particles_->rho_n_), p_(particles_->p_), + pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_), + dvel_dt_(particles_->dvel_dt_), + level_set_complex_shape_(near_surface->getLevelSetComplexShape()), + riemann_solver_(*material_, *material_) {} + //=================================================================================================// + void StaticConfinementPressureRelaxation::Update(size_t index_i, Real dt) + { + Vecd kernel_gradient = level_set_complex_shape_->computeKernelGradientIntegral(pos_n_[index_i]); + Vecd normal_to_fluid = -kernel_gradient / (kernel_gradient.norm() + TinyReal); + + FluidState state(rho_n_[index_i], vel_n_[index_i], p_[index_i]); + Vecd vel_in_wall = -state.vel_; + FluidState state_in_wall(rho_n_[index_i], vel_in_wall, p_[index_i]); + + //always solving one-side Riemann problem for wall boundaries + Real p_star = riemann_solver_.getPStar(state, state_in_wall, normal_to_fluid); + dvel_dt_[index_i] -= 2.0 * p_star * kernel_gradient / state.rho_; + } + //=================================================================================================// + StaticConfinementDensityRelaxation:: + StaticConfinementDensityRelaxation(FluidBody* body, NearShapeSurface* near_surface) : + PartDynamicsByCell(body, near_surface), FluidDataSimple(body), + rho_n_(particles_->rho_n_), p_(particles_->p_), drho_dt_(particles_->drho_dt_), + pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_), + level_set_complex_shape_(near_surface->getLevelSetComplexShape()), + riemann_solver_(*material_, *material_) {} + //=================================================================================================// + void StaticConfinementDensityRelaxation::Update(size_t index_i, Real dt) + { + Vecd kernel_gradient = level_set_complex_shape_->computeKernelGradientIntegral(pos_n_[index_i]); + Vecd normal_to_fluid = -kernel_gradient / (kernel_gradient.norm() + TinyReal); + + FluidState state(rho_n_[index_i], vel_n_[index_i], p_[index_i]); + Vecd vel_in_wall = -state.vel_; + FluidState state_in_wall(rho_n_[index_i], vel_in_wall, p_[index_i]); + + //always solving one-side Riemann problem for wall boundaries + Vecd vel_star = riemann_solver_.getVStar(state, state_in_wall, normal_to_fluid); + drho_dt_[index_i] += 2.0 * state.rho_ * dot(state.vel_ - vel_star, kernel_gradient); + } + //=================================================================================================// + StaticConfinement::StaticConfinement(FluidBody* body, NearShapeSurface* near_surface) : + density_summation_(body, near_surface), pressure_relaxation_(body, near_surface), + density_relaxation_(body, near_surface) {} + //=================================================================================================// + EmitterInflowCondition:: + EmitterInflowCondition(FluidBody* body, BodyPartByParticle* body_part) : + PartSimpleDynamicsByParticle(body, body_part), FluidDataSimple(body), + rho_n_(particles_->rho_n_), p_(particles_->p_), + pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_), inflow_pressure_(0) + { + rho0_ = material_->ReferenceDensity(); + } + //=================================================================================================// + void EmitterInflowCondition + ::Update(size_t unsorted_index_i, Real dt) + { + size_t sorted_index_i = sorted_id_[unsorted_index_i]; + vel_n_[sorted_index_i] = getTargetVelocity(pos_n_[sorted_index_i], vel_n_[sorted_index_i]); + rho_n_[sorted_index_i] = rho0_; + p_[sorted_index_i] = material_->getPressure(rho_n_[sorted_index_i]); + } + //=================================================================================================// + InletOutletInflowCondition:: + InletOutletInflowCondition(FluidBody* body, BodyPartByParticle* body_part) : + EmitterInflowCondition(body, body_part){} + //=================================================================================================// + void InletOutletInflowCondition + ::Update(size_t unsorted_index_i, Real dt) + { + size_t sorted_index_i = sorted_id_[unsorted_index_i]; + vel_n_[sorted_index_i] = getTargetVelocity(pos_n_[sorted_index_i], vel_n_[sorted_index_i]); + } + //=================================================================================================// + EmitterInflowInjecting + ::EmitterInflowInjecting(FluidBody* body, BodyPartByParticle* body_part, + size_t body_buffer_width, int axis_direction, bool positive) + : PartSimpleDynamicsByParticle(body, body_part), FluidDataSimple(body), + pos_n_(particles_->pos_n_),rho_n_(particles_->rho_n_), p_(particles_->p_), + axis_(axis_direction), periodic_translation_(0), body_buffer_width_(body_buffer_width) + { + body_part_bounds_ = body_part->getBodyPartShape()->findBounds(); + periodic_translation_[axis_] = body_part_bounds_.second[axis_] - body_part_bounds_.first[axis_]; + + size_t total_body_buffer_particles = body_part_particles_.size() * body_buffer_width_; + particles_->addBufferParticles(total_body_buffer_particles); + body_->allocateConfigurationMemoriesForBufferParticles(); + + checking_bound_ = positive ? + std::bind(&EmitterInflowInjecting::checkUpperBound, this, _1, _2) + : std::bind(&EmitterInflowInjecting::checkLowerBound, this, _1, _2); + } + //=================================================================================================// + void EmitterInflowInjecting::checkUpperBound(size_t unsorted_index_i, Real dt) + { + size_t sorted_index_i = sorted_id_[unsorted_index_i]; + if (pos_n_[sorted_index_i][axis_] > body_part_bounds_.second[axis_]) { + if (particles_->total_real_particles_ >= particles_->real_particles_bound_) + { + std::cout << "EmitterInflowBoundaryCondition::ConstraintAParticle: \n" + << "Not enough body buffer particles! Exit the code." << "\n"; + exit(0); + } + /** Buffer Particle state copied from real particle. */ + particles_->copyFromAnotherParticle(particles_->total_real_particles_, sorted_index_i); + /** Realize the buffer particle by increas�ng the number of real particle in the body. */ + particles_->total_real_particles_ += 1; + /** Periodic bounding. */ + pos_n_[sorted_index_i][axis_] -= periodic_translation_[axis_]; + rho_n_[sorted_index_i] = material_->ReferenceDensity(); + p_[sorted_index_i] = material_->getPressure(rho_n_[sorted_index_i]); + } + } + //=================================================================================================// + void EmitterInflowInjecting::checkLowerBound(size_t unsorted_index_i, Real dt) + { + size_t sorted_index_i = sorted_id_[unsorted_index_i]; + if (pos_n_[sorted_index_i][axis_] < body_part_bounds_.first[axis_]) { + if (particles_->total_real_particles_ >= particles_->real_particles_bound_) + { + std::cout << "EmitterInflowBoundaryCondition::ConstraintAParticle: \n" + << "Not enough body buffer particles! Exit the code." << "\n"; + exit(0); + } + /** Buffer Particle state copied from real particle. */ + particles_->copyFromAnotherParticle(particles_->total_real_particles_, sorted_index_i); + /** Realize the buffer particle by increasing the number of real particle in the body. */ + particles_->total_real_particles_ += 1; + pos_n_[sorted_index_i][axis_] += periodic_translation_[axis_]; + } + } + //=================================================================================================// + ColorFunctionGradientInner::ColorFunctionGradientInner(BaseBodyRelationInner* inner_relation) + : InteractionDynamics(inner_relation->sph_body_), FluidDataInner(inner_relation), + Vol_(particles_->Vol_), + surface_indicator_(particles_->surface_indicator_), + color_grad_(*particles_->createAVariable("ColorGradient")), + surface_norm_(*particles_->createAVariable("SurfaceNormal")), + pos_div_(*particles_->getVariableByName("PositionDivergence")) + { + //register particle variable defined in this class + thereshold_by_dimensions_ = (0.75 * (Real)Dimensions); + } + //=================================================================================================// + void ColorFunctionGradientInner::Interaction(size_t index_i, Real dt) + { + Vecd gradient(0); + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + if(pos_div_[index_i] < thereshold_by_dimensions_) + { + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + gradient -= inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n] * Vol_[index_j]; + } + + } + color_grad_[index_i] = gradient; + surface_norm_[index_i] = gradient / (gradient.norm() + TinyReal); + } + //=================================================================================================// + ColorFunctionGradientInterplationInner::ColorFunctionGradientInterplationInner(BaseBodyRelationInner* inner_relation) + : InteractionDynamics(inner_relation->sph_body_), FluidDataInner(inner_relation), Vol_(particles_->Vol_), + surface_indicator_(particles_->surface_indicator_), + color_grad_(*particles_->getVariableByName("ColorGradient")), + surface_norm_(*particles_->getVariableByName("SurfaceNormal")), + pos_div_(*particles_->getVariableByName("PositionDivergence")) + { + thereshold_by_dimensions_ = (0.75 * (Real)Dimensions); + particles_->addAVariableToWrite("SurfaceNormal"); + particles_->addAVariableToWrite("ColorGradient"); + } + //=================================================================================================// + void ColorFunctionGradientInterplationInner::Interaction(size_t index_i, Real dt) + { + Vecd grad(0); + Real weight(0); + Real total_weight(0); + if (surface_indicator_[index_i] == 1 && pos_div_[index_i] > thereshold_by_dimensions_) + { + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + if (surface_indicator_[index_j] == 1 && pos_div_[index_j] < thereshold_by_dimensions_) + { + weight = inner_neighborhood.W_ij_[n] * Vol_[index_j]; + grad += weight * color_grad_[index_j]; + total_weight += weight; + } + } + Vecd grad_norm = grad / (total_weight + TinyReal); + color_grad_[index_i] = grad_norm; + surface_norm_[index_i] = grad_norm / (grad_norm.norm() + TinyReal); + } + } + //=================================================================================================// + SurfaceTensionAccelerationInner::SurfaceTensionAccelerationInner(BaseBodyRelationInner* inner_relation, Real gamma) + : InteractionDynamics(inner_relation->sph_body_), FluidDataInner(inner_relation), + gamma_(gamma), Vol_(particles_->Vol_), + mass_(particles_->mass_), dvel_dt_prior_(particles_->dvel_dt_prior_), surface_indicator_(particles_->surface_indicator_), + color_grad_(*particles_->getVariableByName("ColorGradient")), + surface_norm_(*particles_->getVariableByName("SurfaceNormal")){} + //=================================================================================================// + SurfaceTensionAccelerationInner::SurfaceTensionAccelerationInner(BaseBodyRelationInner* inner_relation) + : SurfaceTensionAccelerationInner(inner_relation, 1.0) {} + //=================================================================================================// + void SurfaceTensionAccelerationInner::Interaction(size_t index_i, Real dt) + { + Vecd n_i = surface_norm_[index_i]; + Real curvature(0.0); + Real renormal_curvature(0); + Real pos_div(0); + if(surface_indicator_[index_i] == 1) + { + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + if(surface_indicator_[index_j] == 1) + { + Vecd n_j = surface_norm_[index_j]; + Vecd n_ij = n_i - n_j; + curvature -= inner_neighborhood.dW_ij_[n] * Vol_[index_j] * dot(n_ij, inner_neighborhood.e_ij_[n]); + pos_div -= inner_neighborhood.dW_ij_[n] * inner_neighborhood.r_ij_[n] * Vol_[index_j]; + } + } + } + /** + Adami et al. 2010 is wrong in equation. + (dv / dt)_s = (1.0 / rho) (-sigma * k * n * delta) + = (1/rho) * curvature * color_grad + = (1/m) * curvature * color_grad * vol + */ + renormal_curvature = (Real)Dimensions * curvature / ABS(pos_div + TinyReal); + Vecd acceleration = gamma_ * renormal_curvature* color_grad_[index_i] * Vol_[index_i]; + dvel_dt_prior_[index_i] -= acceleration / mass_[index_i]; + } + //=================================================================================================// + } +//=================================================================================================// +} +//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.h b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.h new file mode 100644 index 0000000000..5e06c576eb --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.h @@ -0,0 +1,747 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file fluid_dynamics_inner.h +* @brief Here, we define the algorithm classes for fluid dynamics within the body. +* @details We consider here weakly compressible fluids. The algorithms may be +* different for free surface flow and the one without free surface. +* @author Chi ZHang and Xiangyu Hu +*/ + + +#ifndef FLUID_DYNAMCIS_INNER_H +#define FLUID_DYNAMCIS_INNER_H + + + +#include "all_particle_dynamics.h" +#include "base_kernel.h" +#include "riemann_solver.h" + +namespace SPH +{ + namespace fluid_dynamics + { + typedef DataDelegateSimple FluidDataSimple; + typedef DataDelegateInner FluidDataInner; + + /** + * @class FluidInitialCondition + * @brief Set initial condition for a fluid body. + * This is a abstract class to be override for case specific initial conditions + */ + class FluidInitialCondition + : public ParticleDynamicsSimple, public FluidDataSimple + { + public: + FluidInitialCondition(FluidBody* body); + virtual ~FluidInitialCondition() {}; + protected: + StdLargeVec& pos_n_, & vel_n_; + }; + + /** + * @class FreeSurfaceIndicationInner + * @brief indicate the particles near the free surface of a fluid body. + * Note that, SPHinXsys does not require this function for simulating general free surface flow problems. + * However, some other applications may use this function, such as transport velocity formulation, + * for masking some function which is only applicable for the bulk of the fluid body. + */ + class FreeSurfaceIndicationInner + : public InteractionDynamicsWithUpdate, public FluidDataInner + { + public: + FreeSurfaceIndicationInner(BaseBodyRelationInner* inner_relation, Real thereshold = 0.75); + virtual ~FreeSurfaceIndicationInner() {}; + + protected: + Real smoothing_length_; + Real thereshold_by_dimensions_; + StdLargeVec& Vol_, & pos_div_; + StdLargeVec& surface_indicator_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class MultilayeredSurfaceParticlesIdentification + * @brief extendedly indicate the 2nd and 3rd layers of surface particles. + * @brief In total, all indicator indexs of 3 layers of surface paritlces are set to 1. + * @brief in detail, index of second and third layer particles should be changed to 2 firstly as a temporary value. + * @brief then index 2 will be changed to 1 in the end. + * @brief applied in inlet-outlet case with wall boundary. + */ + class MultilayeredSurfaceParticlesIdentification + : public InteractionDynamicsWithUpdate, public FluidDataInner + { + public: + MultilayeredSurfaceParticlesIdentification(BaseBodyRelationInner* inner_relation); + virtual ~MultilayeredSurfaceParticlesIdentification() {}; + + protected: + StdLargeVec& pos_n_; + StdLargeVec& surface_indicator_, &previous_surface_indicator_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class FreeStreamIndicateInletOutletSurfaceParticle + * @brief further indicate the surface particles sepearately at inlet and outlet. + * @brief In total, index of internal fluid particles is 0. that of 3-layered surface particles is 1 or 2. + * @brief In detail, based on the result of MultilayeredSurfaceParticlesIdentification, + * @brief surface particles close to inlet and outler have the index 2,while other surface particles own index 1. + * @brief applied in the free-stream case without wall boundary. + */ + class FreeStreamInletOutletSurfaceParticleIdentification + : public ParticleDynamicsSimple, public FluidDataInner + { + public: + FreeStreamInletOutletSurfaceParticleIdentification(BaseBodyRelationInner* inner_relation, int axis_direction); + virtual ~FreeStreamInletOutletSurfaceParticleIdentification() {}; + + protected: + StdLargeVec& pos_n_; + /**< lower and upper body domain bound for checking. */ + BoundingBox fluid_body_domain_bounds_; + /** the axis direction for bounding*/ + const int axis_; + Real particle_spacing_; + StdLargeVec& surface_indicator_, &previous_surface_indicator_; + + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class DensitySummationInner + * @brief computing density by summation + */ + class DensitySummationInner + : public InteractionDynamicsWithUpdate, public FluidDataInner + { + public: + DensitySummationInner(BaseBodyRelationInner* inner_relation); + virtual ~DensitySummationInner() {}; + protected: + Real W0_, rho0_, inv_sigma0_; + StdLargeVec& Vol_, & rho_n_, & mass_, & rho_sum_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + virtual Real ReinitializedDensity(Real rho_sum, Real rho_0, Real rho_n) { return rho_sum; }; + }; + + /** + * @class DensitySummationFreeSurfaceInner + * @brief computing density by summation with a re-normalization for free surface flows + */ + class DensitySummationFreeSurfaceInner : public DensitySummationInner + { + public: + DensitySummationFreeSurfaceInner(BaseBodyRelationInner* inner_relation) : + DensitySummationInner(inner_relation) {}; + virtual ~DensitySummationFreeSurfaceInner() {}; + protected: + virtual Real ReinitializedDensity(Real rho_sum, Real rho_0, Real rho_n) override + { + return rho_sum + SMAX(0.0, (rho_n - rho_sum)) * rho_0 / rho_n; + }; + }; + /** + * @class DensitySummationFreeStreamInner + * @brief the density of three-layer surface particles is calculated by DensitySummationFreeSurface, + * @brief and the density of other internal particles is obtained by DensitySummation. + * @brief applied in free stream flow without wall boundary. + */ + class DensitySummationFreeStreamInner : public DensitySummationFreeSurfaceInner + { + public: + DensitySummationFreeStreamInner(BaseBodyRelationInner* inner_relation) : + DensitySummationFreeSurfaceInner(inner_relation), + surface_indicator_(*particles_->getVariableByName("SurfaceIndicator")) {}; + virtual ~DensitySummationFreeStreamInner() {}; + protected: + StdLargeVec& surface_indicator_; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class ViscousAccelerationInner + * @brief the viscosity force induced acceleration + */ + class ViscousAccelerationInner + : public InteractionDynamics, public FluidDataInner + { + public: + ViscousAccelerationInner(BaseBodyRelationInner* inner_relation); + virtual ~ViscousAccelerationInner() {}; + protected: + Real mu_; + Real smoothing_length_; + StdLargeVec &Vol_, &rho_n_, &p_; + StdLargeVec &vel_n_, &dvel_dt_prior_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class AngularConservativeViscousAccelerationInner + * @brief the viscosity force induced acceleration, a formulation for conserving + * angular momentum, to be tested for its practical applications. + */ + class AngularConservativeViscousAccelerationInner : public ViscousAccelerationInner + { + public: + AngularConservativeViscousAccelerationInner(BaseBodyRelationInner* inner_relation) : + ViscousAccelerationInner(inner_relation) {}; + virtual ~AngularConservativeViscousAccelerationInner() {}; + protected: + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class TransportVelocityCorrectionInner + * @brief transport velocity correction + */ + class TransportVelocityCorrectionInner + : public InteractionDynamics, public FluidDataInner + { + public: + TransportVelocityCorrectionInner(BaseBodyRelationInner* inner_relation); + virtual ~TransportVelocityCorrectionInner() {}; + protected: + StdLargeVec& Vol_, & rho_n_; + StdLargeVec& pos_n_; + StdLargeVec& surface_indicator_; + Real p_background_; + + virtual void setupDynamics(Real dt = 0.0) override; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class AcousticTimeStepSize + * @brief Computing the acoustic time step size + */ + class AcousticTimeStepSize : + public ParticleDynamicsReduce, public FluidDataSimple + { + public: + explicit AcousticTimeStepSize(FluidBody* body); + virtual ~AcousticTimeStepSize() {}; + protected: + StdLargeVec& rho_n_, & p_; + StdLargeVec& vel_n_; + Real smoothing_length_; + Real ReduceFunction(size_t index_i, Real dt = 0.0) override; + Real OutputResult(Real reduced_value) override; + }; + + /** + * @class AdvectionTimeStepSize + * @brief Computing the advection time step size + */ + class AdvectionTimeStepSize : + public ParticleDynamicsReduce, public FluidDataSimple + { + public: + explicit AdvectionTimeStepSize(FluidBody* body, Real U_max); + virtual ~AdvectionTimeStepSize() {}; + protected: + Real smoothing_length_; + StdLargeVec& vel_n_; + Real ReduceFunction(size_t index_i, Real dt = 0.0) override; + Real OutputResult(Real reduced_value) override; + }; + + /** + * @class AdvectionTimeStepSizeForImplicitViscosity + * @brief Computing the advection time step size when viscosity is handled implicitly + */ + class AdvectionTimeStepSizeForImplicitViscosity : public AdvectionTimeStepSize + { + public: + explicit AdvectionTimeStepSizeForImplicitViscosity(FluidBody* body, Real U_max); + virtual ~AdvectionTimeStepSizeForImplicitViscosity() {}; + }; + + /** + * @class VorticityInner + * @brief compute vorticity in the fluid field + */ + class VorticityInner + : public InteractionDynamics, public FluidDataInner + { + public: + VorticityInner(BaseBodyRelationInner* body_inner_relation); + virtual ~VorticityInner() {}; + protected: + StdLargeVec& Vol_; + StdLargeVec& vel_n_; + StdLargeVec & vorticity_; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class BaseRelaxation + * @brief Pure abstract base class for all fluid relaxation schemes + */ + class BaseRelaxation : public ParticleDynamics1Level, public FluidDataInner + { + public: + BaseRelaxation(BaseBodyRelationInner* inner_relation); + virtual ~BaseRelaxation() {}; + protected: + StdLargeVec& Vol_, & mass_, & rho_n_, & p_, & drho_dt_; + StdLargeVec& pos_n_, & vel_n_, & dvel_dt_, & dvel_dt_prior_; + }; + + /** + * @class BasePressureRelaxation + * @brief Abstract base class for all pressure relaxation schemes + */ + class BasePressureRelaxation : public BaseRelaxation + { + public: + BasePressureRelaxation(BaseBodyRelationInner* inner_relation); + virtual ~BasePressureRelaxation() {}; + protected: + virtual void Initialization(size_t index_i, Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + virtual Vecd computeNonConservativeAcceleration(size_t index_i); + }; + + /** + * @class BasePressureRelaxationInner + * @brief Template class for pressure relaxation scheme with the Riemann solver + * as template variable + */ + template + class BasePressureRelaxationInner : public BasePressureRelaxation + { + public: + BasePressureRelaxationInner(BaseBodyRelationInner* inner_relation); + virtual ~BasePressureRelaxationInner() {}; + RiemannSolverType riemann_solver_; + protected: + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + using PressureRelaxationInner = BasePressureRelaxationInner; + /** define the mostly used pressure relaxation scheme using Riemann solver */ + using PressureRelaxationRiemannInner = BasePressureRelaxationInner; + using PressureRelaxationDissipativeRiemannInner = BasePressureRelaxationInner; + + /** + * @class BaseDensityRelaxation + * @brief Abstract base class for all density relaxation schemes + */ + class BaseDensityRelaxation : public BaseRelaxation + { + public: + BaseDensityRelaxation(BaseBodyRelationInner* inner_relation); + virtual ~BaseDensityRelaxation() {}; + protected: + virtual void Initialization(size_t index_i, Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class FreeStreamBoundaryVelocityCorrection + * @brief this function is applied to freetream flows + * @brief modify the velocity of free surface particles with far-field velocity + */ + class FreeStreamBoundaryVelocityCorrection: public ParticleDynamicsSimple, public FluidDataInner + { + public: + FreeStreamBoundaryVelocityCorrection(BaseBodyRelationInner* inner_relation) + : ParticleDynamicsSimple(inner_relation->sph_body_), + FluidDataInner(inner_relation), u_ref_(1.0), t_ref_(2.0), + rho_ref_(material_->ReferenceDensity()), rho_sum(particles_->rho_sum_), + vel_n_(particles_->vel_n_),dvel_dt_(particles_->dvel_dt_), + surface_indicator_(*particles_->getVariableByName("SurfaceIndicator")) {}; + virtual ~FreeStreamBoundaryVelocityCorrection() {}; + protected: + Real u_ref_, t_ref_, rho_ref_; + StdLargeVec& rho_sum; + StdLargeVec& vel_n_, &dvel_dt_; + StdLargeVec& surface_indicator_; + + virtual void Update(size_t index_i, Real dt = 0.0) override ; + }; + + /** + * @class DensityRelaxationInner + * @brief Template density relaxation scheme with different Riemann solver + */ + template + class BaseDensityRelaxationInner : public BaseDensityRelaxation + { + public: + BaseDensityRelaxationInner(BaseBodyRelationInner* inner_relation); + virtual ~BaseDensityRelaxationInner() {}; + RiemannSolverType riemann_solver_; + protected: + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + using DensityRelaxationInner = BaseDensityRelaxationInner; + /** define the mostly used density relaxation scheme using Riemann solver */ + using DensityRelaxationRiemannInner = BaseDensityRelaxationInner; + + /** + * @class Oldroyd_B_FluidInitialCondition + * @brief set initial condition for Oldroyd_B_Fluid dynamics + * This is a abstract class to be override for case specific initial conditions + */ + class Oldroyd_B_FluidInitialCondition + : public ParticleDynamicsSimple, public FluidDataSimple + { + public: + Oldroyd_B_FluidInitialCondition(FluidBody* body) + : ParticleDynamicsSimple(body), FluidDataSimple(body) {}; + virtual ~Oldroyd_B_FluidInitialCondition() {}; + }; + + /** + * @class PressureRelaxationRiemannInnerOldroyd_B + * @brief Pressure relaxation scheme with the mostly used Riemann solver. + */ + class PressureRelaxationRiemannInnerOldroyd_B : public PressureRelaxationRiemannInner + { + public: + PressureRelaxationRiemannInnerOldroyd_B(BaseBodyRelationInner* inner_relation); + virtual ~PressureRelaxationRiemannInnerOldroyd_B() {}; + protected: + StdLargeVec& tau_, & dtau_dt_; + virtual void Initialization(size_t index_i, Real dt = 0.0) override; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class DensityRelaxationRiemannInnerOldroyd_B + * @brief Density relaxation scheme with the mostly used Riemann solver. + */ + class DensityRelaxationRiemannInnerOldroyd_B : public DensityRelaxationRiemannInner + { + public: + DensityRelaxationRiemannInnerOldroyd_B(BaseBodyRelationInner* inner_relation); + virtual ~DensityRelaxationRiemannInnerOldroyd_B() {}; + protected: + StdLargeVec& tau_, & dtau_dt_; + Real mu_p_, lambda_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class FlowRelaxationBuffer + * @brief Flow buffer in which the particles relaxes to a given target velocity profile. + * This technique will be used for applying several boundary conditions, + * such as freestream, inflow, damping boundary conditions. + */ + class FlowRelaxationBuffer : public PartDynamicsByCell, public FluidDataSimple + { + public: + FlowRelaxationBuffer(FluidBody* body, BodyPartByCell* body_part); + virtual ~FlowRelaxationBuffer() {}; + protected: + StdLargeVec& pos_n_, & vel_n_; + /** default value is 0.1 suggests reaching target inflow velocity in about 10 time steps */ + Real relaxation_rate_; + + /** inflow profile to be defined in applications */ + virtual Vecd getTargetVelocity(Vecd& position, Vecd& velocity) = 0; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class InflowBoundaryCondition + * @brief inflow boundary condition which relaxes + * the particles to a given velocity profile. + */ + class InflowBoundaryCondition : public FlowRelaxationBuffer + { + public: + InflowBoundaryCondition(FluidBody* body, BodyPartByCell* body_part) : + FlowRelaxationBuffer(body, body_part) {};; + virtual ~InflowBoundaryCondition() {}; + }; + + /** + * @class DampingBoundaryCondition + * @brief damping boundary condition which relaxes + * the particles to zero velocity profile. + */ + class DampingBoundaryCondition + : public PartDynamicsByCell, public FluidDataSimple + { + public: + DampingBoundaryCondition(FluidBody* body, BodyPartByCell* body_part); + virtual ~DampingBoundaryCondition() {}; + protected: + StdLargeVec& pos_n_, & vel_n_; + /** default value is 0.1 suggests reaching target inflow velocity in about 10 time steps */ + Real strength_; + BoundingBox damping_zone_bounds_; + virtual void Update(size_t index_particle_i, Real dt = 0.0) override; + }; + + + /** + * @class StaticConfinementDensity + * @brief static confinement condition for density summation + */ + class StaticConfinementDensity + : public PartDynamicsByCell, public FluidDataSimple + { + public: + StaticConfinementDensity(FluidBody* body, NearShapeSurface* near_surface); + virtual ~StaticConfinementDensity() {}; + protected: + Real rho0_, inv_sigma0_; + StdLargeVec& mass_, & rho_sum_; + StdLargeVec& pos_n_; + LevelSetComplexShape* level_set_complex_shape_; + + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class StaticConfinementPressureRelaxation + * @brief static confinement condition for pressure relaxation + */ + class StaticConfinementPressureRelaxation + : public PartDynamicsByCell, public FluidDataSimple + { + public: + StaticConfinementPressureRelaxation(FluidBody* body, NearShapeSurface* near_surface); + virtual ~StaticConfinementPressureRelaxation() {}; + protected: + StdLargeVec& rho_n_, & p_; + StdLargeVec& pos_n_, & vel_n_, & dvel_dt_; + LevelSetComplexShape* level_set_complex_shape_; + AcousticRiemannSolver riemann_solver_; + + + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class StaticConfinementDensityRelaxation + * @brief static confinement condition for density relaxation + */ + class StaticConfinementDensityRelaxation + : public PartDynamicsByCell, public FluidDataSimple + { + public: + StaticConfinementDensityRelaxation(FluidBody* body, NearShapeSurface* near_surface); + virtual ~StaticConfinementDensityRelaxation() {}; + protected: + StdLargeVec& rho_n_, & p_, & drho_dt_; + StdLargeVec& pos_n_, & vel_n_; + LevelSetComplexShape* level_set_complex_shape_; + AcousticRiemannSolver riemann_solver_; + + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class StaticConfinement + * @brief Static confined boundary condition for complex structures. + */ + class StaticConfinement + { + public: + StaticConfinementDensity density_summation_; + StaticConfinementPressureRelaxation pressure_relaxation_; + StaticConfinementDensityRelaxation density_relaxation_; + + StaticConfinement(FluidBody* body, NearShapeSurface* near_surface); + virtual ~StaticConfinement() {}; + }; + + /** + * @class EmitterInflowCondition + * @brief Inflow boundary condition. + * The body part region is required to + * have parallel lower- and upper-bound surfaces. + */ + class EmitterInflowCondition + : public PartSimpleDynamicsByParticle, public FluidDataSimple + { + public: + explicit EmitterInflowCondition(FluidBody* body, BodyPartByParticle* body_part); + virtual ~EmitterInflowCondition() {}; + protected: + StdLargeVec& rho_n_, & p_; + StdLargeVec& pos_n_, & vel_n_; + /** inflow pressure condition */ + Real inflow_pressure_; + Real rho0_; + + /** inflow velocity profile to be defined in applications */ + virtual Vecd getTargetVelocity(Vecd& position, Vecd& velocity) = 0; + /** inflow parameters to be defined in applications */ + virtual void SetInflowParameters() = 0; + + virtual void Update(size_t unsorted_index_i, Real dt = 0.0) override; + }; + + /** + * @class InletOutletInflowCondition + * @brief this function is for inlet-outlet flow + */ + class InletOutletInflowCondition + : public EmitterInflowCondition + { + public: + explicit InletOutletInflowCondition(FluidBody* body, BodyPartByParticle* body_part); + virtual ~InletOutletInflowCondition() {}; + protected: + /** inflow velocity profile to be defined in applications */ + virtual Vecd getTargetVelocity(Vecd& position, Vecd& velocity) = 0; + /** inflow parameters to be defined in applications */ + virtual void SetInflowParameters() = 0; + + virtual void Update(size_t unsorted_index_i, Real dt = 0.0) override; + }; + + /** + * @class EmitterInflowInjecting + * @brief Inject particles into the computational domain. + */ + class EmitterInflowInjecting + : public PartSimpleDynamicsByParticle, public FluidDataSimple + { + public: + explicit EmitterInflowInjecting(FluidBody* body, BodyPartByParticle* body_part, + size_t body_buffer_width, int axis_direction, bool positive); + virtual ~EmitterInflowInjecting() {}; + + /** This class is only implemented in sequential due to memory conflicts. */ + virtual void parallel_exec(Real dt = 0.0) override { exec(); }; + protected: + StdLargeVec& pos_n_; + StdLargeVec& rho_n_, &p_; + const int axis_; /**< the axis direction for bounding*/ + Vecd periodic_translation_; + size_t body_buffer_width_; + BoundingBox body_part_bounds_; + + virtual void checkLowerBound(size_t unsorted_index_i, Real dt = 0.0); + virtual void checkUpperBound(size_t unsorted_index_i, Real dt = 0.0); + ParticleFunctor checking_bound_; + + virtual void Update(size_t unsorted_index_i, Real dt = 0.0) override { + checking_bound_(unsorted_index_i, dt); + }; + }; + + /** + * @class FreeSurfaceProbeOnFluidBody + * @brief Probe the free surface profile for a fluid body part by reduced operation. + */ + class FreeSurfaceProbeOnFluidBody : public PartDynamicsByCellReduce, + public FluidDataSimple + { + public: + FreeSurfaceProbeOnFluidBody(FluidBody* body, BodyPartByCell* body_part) + : PartDynamicsByCellReduce(body, body_part), FluidDataSimple(body), + pos_n_(particles_->pos_n_) + { + quantity_name_ = "FreeSurfaceProbeOnFluidBody"; + initial_reference_ = 0.0; + } + virtual ~FreeSurfaceProbeOnFluidBody() {}; + protected: + StdLargeVec& pos_n_; + virtual void SetupReduce() override {}; + virtual Real ReduceFunction(size_t index_i, Real dt = 0.0) override { return pos_n_[index_i][1]; }; + }; + /** + * @class ColorFunctionGradientInner + * @brief indicate the particles near the interface of a fluid-fluid interaction and computing norm + */ + class ColorFunctionGradientInner : public InteractionDynamics, public FluidDataInner + { + public: + ColorFunctionGradientInner(BaseBodyRelationInner* inner_relation); + virtual ~ColorFunctionGradientInner() {}; + protected: + Real thereshold_by_dimensions_; + StdLargeVec &Vol_; + StdLargeVec& surface_indicator_; + StdLargeVec& color_grad_; + StdLargeVec& surface_norm_; + StdLargeVec &pos_div_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class ColorFunctionGradientInterplationInner + * @brief the viscous force induced acceleration + */ + class ColorFunctionGradientInterplationInner + : public InteractionDynamics, public FluidDataInner + { + public: + ColorFunctionGradientInterplationInner(BaseBodyRelationInner* inner_relation); + virtual ~ColorFunctionGradientInterplationInner() {}; + protected: + Real thereshold_by_dimensions_; + StdLargeVec &Vol_; + StdLargeVec& surface_indicator_; + StdLargeVec& color_grad_; + StdLargeVec& surface_norm_; + StdLargeVec& pos_div_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class SurfaceTensionAccelerationInner + * @brief the viscous force induced acceleration + */ + class SurfaceTensionAccelerationInner + : public InteractionDynamics, public FluidDataInner + { + public: + SurfaceTensionAccelerationInner(BaseBodyRelationInner* inner_relation, Real gamma); + SurfaceTensionAccelerationInner(BaseBodyRelationInner* inner_relation); + virtual ~SurfaceTensionAccelerationInner() {}; + protected: + Real gamma_; + StdLargeVec &Vol_, &mass_; + StdLargeVec &dvel_dt_prior_; + StdLargeVec& surface_indicator_; + StdLargeVec& color_grad_; + StdLargeVec& surface_norm_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + } +} +#endif //FLUID_DYNAMCIS_INNER_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.hpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.hpp new file mode 100644 index 0000000000..2428f35c24 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_inner.hpp @@ -0,0 +1,70 @@ +/** + * @file fluid_dynamics_inner.hpp + * @author Chi ZHang and Xiangyu Hu + */ + +#pragma once + +#include "fluid_dynamics_inner.h" + +//=================================================================================================// +namespace SPH +{ +//=================================================================================================// + namespace fluid_dynamics + { + //=================================================================================================// + template + BasePressureRelaxationInner:: + BasePressureRelaxationInner(BaseBodyRelationInner* inner_relation) : + BasePressureRelaxation(inner_relation), + riemann_solver_(*material_, *material_) {} + //=================================================================================================// + template + void BasePressureRelaxationInner::Interaction(size_t index_i, Real dt) + { + FluidState state_i(rho_n_[index_i], vel_n_[index_i], p_[index_i]); + Vecd acceleration = dvel_dt_prior_[index_i]; + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + Real dW_ij = inner_neighborhood.dW_ij_[n]; + Vecd& e_ij = inner_neighborhood.e_ij_[n]; + + FluidState state_j(rho_n_[index_j], vel_n_[index_j], p_[index_j]); + Real p_star = riemann_solver_.getPStar(state_i, state_j, e_ij); + acceleration -= 2.0 * p_star * Vol_[index_j] * dW_ij * e_ij / state_i.rho_; + } + dvel_dt_[index_i] = acceleration; + } + //=================================================================================================// + template + BaseDensityRelaxationInner:: + BaseDensityRelaxationInner(BaseBodyRelationInner* inner_relation) : + BaseDensityRelaxation(inner_relation), + riemann_solver_(*material_, *material_) {} + //=================================================================================================// + template + void BaseDensityRelaxationInner::Interaction(size_t index_i, Real dt) + { + FluidState state_i(rho_n_[index_i], vel_n_[index_i], p_[index_i]); + Real density_change_rate = 0.0; + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + Vecd& e_ij = inner_neighborhood.e_ij_[n]; + Real dW_ij = inner_neighborhood.dW_ij_[n]; + + FluidState state_j(rho_n_[index_j], vel_n_[index_j], p_[index_j]); + Vecd vel_star = riemann_solver_.getVStar(state_i, state_j, e_ij); + density_change_rate += 2.0 * state_i.rho_ * Vol_[index_j] * dot(state_i.vel_ - vel_star, e_ij) * dW_ij; + } + drho_dt_[index_i] = density_change_rate; + }; + //=================================================================================================// + } +//=================================================================================================// +} +//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.cpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.cpp new file mode 100644 index 0000000000..6550e89fba --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.cpp @@ -0,0 +1,111 @@ +/** + * @file fluid_dynamics_multi_phase.cpp + * @author Chi ZHang and Xiangyu Hu + */ + +#include "fluid_dynamics_multi_phase.h" + +//=================================================================================================// +namespace SPH +{ +//=================================================================================================// + namespace fluid_dynamics + { + //=================================================================================================// + ViscousAccelerationMultiPhase::ViscousAccelerationMultiPhase(BaseBodyRelationInner* inner_relation, + BaseBodyRelationContact* contact_relation) + : ViscousAccelerationInner(inner_relation), MultiPhaseContactData(contact_relation) + { + if (inner_relation->sph_body_ != contact_relation->sph_body_) + { + std::cout << "\n Error: the two body_realtions do not have the same source body!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); + contact_vel_n_.push_back(&(contact_particles_[k]->vel_n_)); + } + } + //=================================================================================================// + ViscousAccelerationMultiPhase::ViscousAccelerationMultiPhase(ComplexBodyRelation* complex_relation) + : ViscousAccelerationMultiPhase(complex_relation->inner_relation_, complex_relation->contact_relation_) {} + //=================================================================================================// + void ViscousAccelerationMultiPhase::Interaction(size_t index_i, Real dt) + { + ViscousAccelerationInner::Interaction(index_i, dt); + + Real rho_i = this->rho_n_[index_i]; + Vecd& vel_i = this->vel_n_[index_i]; + + Vecd acceleration(0), vel_derivative(0); + for (size_t k = 0; k < this->contact_configuration_.size(); ++k) + { + Real mu_j = this->contact_material_[k]->ReferenceViscosity(); + StdLargeVec& Vol_k = *(this->contact_Vol_[k]); + StdLargeVec& vel_k = *(this->contact_vel_n_[k]); + Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + Real r_ij = contact_neighborhood.r_ij_[n]; + + vel_derivative = 2.0*(vel_i - vel_k[index_j]) / (r_ij + 0.01 * this->smoothing_length_); + Real mu_ij = 2.0 * this->mu_ * mu_j / (this->mu_ + mu_j); + acceleration += 2.0 * mu_ij * vel_derivative + * contact_neighborhood.dW_ij_[n] * Vol_k[index_j] / rho_i; + } + } + + dvel_dt_prior_[index_i] += acceleration; + } + //=================================================================================================// + MultiPhaseColorFunctionGradient:: + MultiPhaseColorFunctionGradient(BaseBodyRelationContact* contact_relation) : + InteractionDynamics(contact_relation->sph_body_), MultiPhaseData(contact_relation), + rho0_(particles_->rho0_), Vol_(particles_->Vol_), + pos_div_(*particles_->getVariableByName("PositionDivergence")), + surface_indicator_(particles_->surface_indicator_), + color_grad_(*particles_->createAVariable("ColorGradient")), + surface_norm_(*particles_->createAVariable("SurfaceNormal")) + { + for (size_t k = 0; k != contact_particles_.size(); ++k) + { Real rho0_k = contact_particles_[k]->rho0_; + contact_rho0_.push_back(rho0_k); + contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); + } + } + //=================================================================================================// + void MultiPhaseColorFunctionGradient::Interaction(size_t index_i, Real dt) + { + Real vol_i = Vol_[index_i]; + Real pos_div = 0.0; + Vecd gradient(0.0); + if(surface_indicator_[index_i]) + { + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + Real rho0_k = contact_rho0_[k]; + StdLargeVec& contact_vol_k = *(contact_Vol_[k]); + Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + pos_div -= contact_neighborhood.dW_ij_[n] * contact_neighborhood.r_ij_[n] * contact_vol_k[index_j]; + /** Norm of interface.*/ + Real rho_ij = rho0_ / (rho0_ + rho0_k); + Real area_ij = (vol_i * vol_i + contact_vol_k[index_j] * contact_vol_k[index_j]) * contact_neighborhood.dW_ij_[n]; + gradient += rho_ij * area_ij * contact_neighborhood.e_ij_[n] / vol_i; + } + } + } + color_grad_[index_i] = gradient; + surface_norm_[index_i] = gradient / (gradient.norm() + TinyReal); + } + //=================================================================================================// + } +//=================================================================================================// +} +//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.h b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.h new file mode 100644 index 0000000000..71b18edb2c --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.h @@ -0,0 +1,153 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file fluid_dynamics_multi_phase.h +* @brief Here, we define the algorithm classes for the dynamics involving multiple fluids. +* @author Chi ZHang and Xiangyu Hu +*/ + + +#ifndef FLUID_DYNAMICS_MULTI_PHASE_H +#define FLUID_DYNAMICS_MULTI_PHASE_H + + + +#include "fluid_dynamics_complex.h" +namespace SPH +{ + namespace fluid_dynamics + { + typedef DataDelegateContact MultiPhaseContactData; + typedef DataDelegateContact MultiPhaseData; + /** + * @class ViscousAccelerationMultiPhase + * @brief the viscosity force induced acceleration + */ + class ViscousAccelerationMultiPhase : public ViscousAccelerationInner, public MultiPhaseContactData + { + public: + ViscousAccelerationMultiPhase(BaseBodyRelationInner* inner_relation, + BaseBodyRelationContact* contact_relation); + ViscousAccelerationMultiPhase(ComplexBodyRelation* complex_relation); + virtual ~ViscousAccelerationMultiPhase() {}; + protected: + StdVec*> contact_Vol_; + StdVec*> contact_vel_n_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + using ViscousAccelerationMultiPhaseWithWall = + BaseViscousAccelerationWithWall>; + + /** + * @class ViscousAccelerationMultiPhase + * @brief Abstract base class for general multiphase fluid dynamics + */ + template + class RelaxationMultiPhase : public RelaxationInnerType, public MultiPhaseContactData + { + public: + RelaxationMultiPhase(BaseBodyRelationInner* inner_relation, + BaseBodyRelationContact* contact_relation); + virtual ~RelaxationMultiPhase() {}; + protected: + StdVec*> contact_Vol_, contact_p_, contact_rho_n_; + StdVec*> contact_vel_n_; + }; + + /** + * @class BasePressureRelaxationMultiPhase + * @brief template class for multiphase pressure relaxation scheme + */ + template + class BasePressureRelaxationMultiPhase : public RelaxationMultiPhase + { + public: + BasePressureRelaxationMultiPhase(BaseBodyRelationInner* inner_relation, + BaseBodyRelationContact* contact_relation); + BasePressureRelaxationMultiPhase(ComplexBodyRelation* complex_relation); + virtual ~BasePressureRelaxationMultiPhase() {}; + protected: + using CurrentRiemannSolver = decltype(PressureRelaxationInnerType::riemann_solver_); + StdVec riemann_solvers_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + virtual Vecd computeNonConservativeAcceleration(size_t index_i) override; + }; + using MultiPhasePressureRelaxation = BasePressureRelaxationMultiPhase; + using MultiPhasePressureRelaxationRiemann = BasePressureRelaxationMultiPhase; + + using MultiPhasePressureRelaxationWithWall = + BasePressureRelaxationWithWall>; + using MultiPhasePressureRelaxationRiemannWithWall = + BasePressureRelaxationWithWall>; + using ExtendMultiPhasePressureRelaxationRiemannWithWall = + ExtendPressureRelaxationWithWall>; + + + /** + * @class BaseDensityRelaxationMultiPhase + * @brief template class pressure relaxation scheme with wall boundary + */ + template + class BaseDensityRelaxationMultiPhase : public RelaxationMultiPhase + { + public: + BaseDensityRelaxationMultiPhase(BaseBodyRelationInner* inner_relation, + BaseBodyRelationContact* contact_relation); + BaseDensityRelaxationMultiPhase(ComplexBodyRelation* complex_relation); + virtual ~BaseDensityRelaxationMultiPhase() {}; + protected: + using CurrentRiemannSolver = decltype(DensityRelaxationInnerType::riemann_solver_); + StdVec riemann_solvers_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + using MultiPhaseDensityRelaxation = BaseDensityRelaxationMultiPhase; + using MultiPhaseDensityRelaxationRiemann = BaseDensityRelaxationMultiPhase; + using MultiPhaseDensityRelaxationWithWall = BaseDensityRelaxationMultiPhase; + using MultiPhaseDensityRelaxationRiemannWithWall = BaseDensityRelaxationWithWall; + + /** + * @class MultiPhaseColorFunctionGradient + * @brief indicate the particles near the interface of a fluid-fluid interaction and computing norm + */ + class MultiPhaseColorFunctionGradient : public InteractionDynamics, public MultiPhaseData + { + public: + MultiPhaseColorFunctionGradient(BaseBodyRelationContact* contact_relation); + virtual ~MultiPhaseColorFunctionGradient() {}; + protected: + Real rho0_; + StdVec contact_rho0_; + StdLargeVec& Vol_, & pos_div_; + StdLargeVec& surface_indicator_; + StdLargeVec& color_grad_, & surface_norm_; + StdVec*> contact_Vol_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + } +} +#endif //FLUID_DYNAMICS_MULTI_PHASE_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.hpp b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.hpp new file mode 100644 index 0000000000..3b43803310 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/fluid_dynamics/fluid_dynamics_multi_phase.hpp @@ -0,0 +1,167 @@ +/** + * @file fluid_dynamics_multi_phase.hpp + * @author Chi ZHang and Xiangyu Hu + */ + +#pragma once + +#include "fluid_dynamics_multi_phase.h" + +//=================================================================================================// +namespace SPH +{ +//=================================================================================================// + namespace fluid_dynamics + { + //=================================================================================================// + template + RelaxationMultiPhase:: + RelaxationMultiPhase(BaseBodyRelationInner* inner_relation, + BaseBodyRelationContact* contact_relation) : + RelaxationInnerType(inner_relation), MultiPhaseContactData(contact_relation) + { + if (inner_relation->sph_body_ != contact_relation->sph_body_) + { + std::cout << "\n Error: the two body_realtions do not have the same source body!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); + contact_p_.push_back(&(contact_particles_[k]->p_)); + contact_rho_n_.push_back(&(contact_particles_[k]->rho_n_)); + contact_vel_n_.push_back(&(contact_particles_[k]->vel_n_)); + } + } + //=================================================================================================// + template + BasePressureRelaxationMultiPhase:: + BasePressureRelaxationMultiPhase(BaseBodyRelationInner* inner_relation, + BaseBodyRelationContact* contact_relation) : + RelaxationMultiPhase(inner_relation, + contact_relation) + { + for (size_t k = 0; k != this->contact_particles_.size(); ++k) + { + riemann_solvers_.push_back(new CurrentRiemannSolver(*this->material_, *this->contact_material_[k])); + } + } + //=================================================================================================// + template + BasePressureRelaxationMultiPhase:: + BasePressureRelaxationMultiPhase(ComplexBodyRelation* complex_relation) : + BasePressureRelaxationMultiPhase(complex_relation->inner_relation_, + complex_relation->contact_relation_) {} + //=================================================================================================// + template + void BasePressureRelaxationMultiPhase::Interaction(size_t index_i, Real dt) + { + PressureRelaxationInnerType::Interaction(index_i, dt); + + FluidState state_i(this->rho_n_[index_i], this->vel_n_[index_i], this->p_[index_i]); + Vecd acceleration(0.0); + for (size_t k = 0; k < this->contact_configuration_.size(); ++k) + { + StdLargeVec& Vol_k = *(this->contact_Vol_[k]); + StdLargeVec& rho_k = *(this->contact_rho_n_[k]); + StdLargeVec& p_k = *(this->contact_p_[k]); + StdLargeVec& vel_k = *(this->contact_vel_n_[k]); + CurrentRiemannSolver* riemann_solver_k = riemann_solvers_[k]; + Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + Vecd& e_ij = contact_neighborhood.e_ij_[n]; + Real dW_ij = contact_neighborhood.dW_ij_[n]; + + FluidState state_j(rho_k[index_j], vel_k[index_j], p_k[index_j]); + Real p_star = riemann_solver_k->getPStar(state_i, state_j, e_ij); + acceleration -= 2.0 * p_star * e_ij * Vol_k[index_j] * dW_ij / state_i.rho_; + } + } + this->dvel_dt_[index_i] += acceleration; + } + //=================================================================================================// + template + Vecd BasePressureRelaxationMultiPhase:: + computeNonConservativeAcceleration(size_t index_i) + { + Vecd acceleration = PressureRelaxationInnerType::computeNonConservativeAcceleration(index_i); + + Real rho_i = this->rho_n_[index_i]; + Real p_i = this->p_[index_i]; + for (size_t k = 0; k < this->contact_configuration_.size(); ++k) + { + StdLargeVec& rho_k = *(this->contact_rho_n_[k]); + StdLargeVec& p_k = *(this->contact_p_[k]); + Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + Vecd& e_ij = contact_neighborhood.e_ij_[n]; + Real dW_ij = contact_neighborhood.dW_ij_[n]; + + Real rho_j = rho_k[index_j]; + Real p_j = p_k[index_j]; + + Real p_star = (rho_i * p_j + rho_j * p_i) / (rho_i + rho_j); + acceleration += (p_i - p_star) * this->Vol_[index_j] * dW_ij * e_ij / rho_i; + } + } + return acceleration; + } + //=================================================================================================// + template + BaseDensityRelaxationMultiPhase:: + BaseDensityRelaxationMultiPhase(BaseBodyRelationInner* inner_relation, + BaseBodyRelationContact* contact_relation) : + RelaxationMultiPhase(inner_relation, + contact_relation) + { + for (size_t k = 0; k != this->contact_particles_.size(); ++k) + { + riemann_solvers_.push_back(new CurrentRiemannSolver(*this->material_, *this->contact_material_[k])); + } + } + //=================================================================================================// + template + BaseDensityRelaxationMultiPhase:: + BaseDensityRelaxationMultiPhase(ComplexBodyRelation* complex_relation) : + BaseDensityRelaxationMultiPhase(complex_relation->inner_relation_, + complex_relation->contact_relation_) {} + //=================================================================================================// + template + void BaseDensityRelaxationMultiPhase::Interaction(size_t index_i, Real dt) + { + DensityRelaxationInnerType::Interaction(index_i, dt); + + FluidState state_i(this->rho_n_[index_i], this->vel_n_[index_i], this->p_[index_i]); + Real density_change_rate = 0.0; + for (size_t k = 0; k < this->contact_configuration_.size(); ++k) + { + StdLargeVec& Vol_k = *(this->contact_Vol_[k]); + StdLargeVec& rho_k = *(this->contact_rho_n_[k]); + StdLargeVec& p_k = *(this->contact_p_[k]); + StdLargeVec& vel_k = *(this->contact_vel_n_[k]); + CurrentRiemannSolver* riemann_solver_k = riemann_solvers_[k]; + Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + Vecd& e_ij = contact_neighborhood.e_ij_[n]; + Real dW_ij = contact_neighborhood.dW_ij_[n]; + + FluidState state_j(rho_k[index_j], vel_k[index_j], p_k[index_j]); + Vecd vel_star = riemann_solver_k->getVStar(state_i, state_j, e_ij); + density_change_rate += 2.0 * state_i.rho_ * Vol_k[index_j] * dot(state_i.vel_ - vel_star, e_ij) * dW_ij; + } + } + this->drho_dt_[index_i] += density_change_rate; + } + //=================================================================================================// + } +//=================================================================================================// +} +//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/general_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/general_dynamics/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/general_dynamics/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/general_dynamics/general_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/general_dynamics/general_dynamics.cpp new file mode 100644 index 0000000000..42c8ddab16 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/general_dynamics/general_dynamics.cpp @@ -0,0 +1,535 @@ +/** + * @file general_dynamics.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#include "general_dynamics.h" + +namespace SPH { + //=================================================================================================// + TimeStepInitialization + ::TimeStepInitialization(SPHBody* body, Gravity* gravity) + : ParticleDynamicsSimple(body), GeneralDataDelegateSimple(body), + pos_n_(particles_->pos_n_), dvel_dt_prior_(particles_->dvel_dt_prior_), + gravity_(gravity) + { + } + //=================================================================================================// + void TimeStepInitialization::setupDynamics(Real dt) + { + particles_->total_ghost_particles_ = 0; + } + //=================================================================================================// + void TimeStepInitialization::Update(size_t index_i, Real dt) + { + dvel_dt_prior_[index_i] = gravity_->InducedAcceleration(pos_n_[index_i]); + } + //=================================================================================================// + RandomizePartilePosition::RandomizePartilePosition(SPHBody* body) + : ParticleDynamicsSimple(body), DataDelegateSimple(body), + pos_n_(particles_->pos_n_) + { + randomize_scale_ = body->particle_adaptation_->MinimumSpacing(); + } + //=================================================================================================// + void RandomizePartilePosition::Update(size_t index_i, Real dt) + { + Vecd& pos_n_i = pos_n_[index_i]; + for (int k = 0; k < pos_n_i.size(); ++k) + { + pos_n_i[k] += dt * (((double)rand() / (RAND_MAX)) - 0.5) * 2.0 * randomize_scale_; + } + } + //=================================================================================================// + BoundingInAxisDirection::BoundingInAxisDirection(RealBody* real_body, int axis_direction) : + ParticleDynamics(real_body), DataDelegateSimple(real_body), + axis_(axis_direction), body_domain_bounds_(real_body->getBodyDomainBounds()), + pos_n_(particles_->pos_n_), + cell_linked_list_(real_body->cell_linked_list_) + { + Real h_ratio_min = 1.0 / particle_adaptation_->MaximumSpacingRatio(); + cut_off_radius_max_ = particle_adaptation_->getKernel()->CutOffRadius(h_ratio_min); + } + //=================================================================================================// + void PeriodicConditionInAxisDirection:: + setPeriodicTranslation(BoundingBox& body_domain_bounds, int axis_direction) + { + periodic_translation_[axis_direction] = + body_domain_bounds.second[axis_direction] - body_domain_bounds.first[axis_direction]; + } + //=================================================================================================// + PeriodicConditionInAxisDirection:: + PeriodicConditionInAxisDirection(RealBody* real_body, int axis_direction) : + periodic_translation_(0.0) + { + BoundingBox body_domain_bounds = real_body->getBodyDomainBounds(); + setPeriodicTranslation(body_domain_bounds, axis_direction); + bound_cells_.resize(2); + BaseCellLinkedList* cell_linked_list = real_body->cell_linked_list_; + cell_linked_list->tagBodyDomainBoundingCells(bound_cells_, body_domain_bounds, axis_direction); + if (periodic_translation_.norm() < real_body->particle_adaptation_->ReferenceSpacing()) + { + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + std::cout << "\n Periodic bounding failure: bounds not defined!" << std::endl; + exit(1); + } + } + //=================================================================================================// + void PeriodicConditionInAxisDirection::PeriodicBounding::checkLowerBound(size_t index_i, Real dt) + { + if (pos_n_[index_i][axis_] < body_domain_bounds_.first[axis_]) + pos_n_[index_i][axis_] += periodic_translation_[axis_]; + } + //=================================================================================================// + void PeriodicConditionInAxisDirection::PeriodicBounding::checkUpperBound(size_t index_i, Real dt) + { + if (pos_n_[index_i][axis_] > body_domain_bounds_.second[axis_]) + pos_n_[index_i][axis_] -= periodic_translation_[axis_]; + } + //=================================================================================================// + void PeriodicConditionInAxisDirection::PeriodicBounding::exec(Real dt) + { + setupDynamics(dt); + + //check lower bound + CellLists& lower_bound_cells = bound_cells_[0]; + for (size_t i = 0; i != lower_bound_cells.size(); ++i) + { + IndexVector& particle_indexes = lower_bound_cells[i]->real_particle_indexes_; + for (size_t num = 0; num < particle_indexes.size(); ++num) checkLowerBound(particle_indexes[num], dt); + } + + //check upper bound + CellLists& upper_bound_cells = bound_cells_[1]; + for (size_t i = 0; i != upper_bound_cells.size(); ++i) + { + IndexVector& particle_indexes = upper_bound_cells[i]->real_particle_indexes_; + for (size_t num = 0; num < particle_indexes.size(); ++num) checkUpperBound(particle_indexes[num], dt); + } + } + //=================================================================================================// + void PeriodicConditionInAxisDirection::PeriodicBounding::parallel_exec(Real dt) + { + setupDynamics(dt); + + //check lower bound + CellLists& lower_bound_cells = bound_cells_[0]; + parallel_for(blocked_range(0, lower_bound_cells.size()), + [&](const blocked_range& r) { + for (size_t i = r.begin(); i < r.end(); ++i) { + IndexVector& particle_indexes = lower_bound_cells[i]->real_particle_indexes_; + for (size_t num = 0; num < particle_indexes.size(); ++num) checkLowerBound(particle_indexes[num], dt); + } + }, ap); + + //check upper bound + CellLists& upper_bound_cells = bound_cells_[1]; + parallel_for(blocked_range(0, upper_bound_cells.size()), + [&](const blocked_range& r) { + for (size_t i = r.begin(); i < r.end(); ++i) { + IndexVector& particle_indexes = upper_bound_cells[i]->real_particle_indexes_; + for (size_t num = 0; num < particle_indexes.size(); ++num) checkUpperBound(particle_indexes[num], dt); + } + }, ap); + } + //=================================================================================================// + void PeriodicConditionInAxisDirection::PeriodicCondition::exec(Real dt) + { + setupDynamics(dt); + + //check lower bound + CellLists& lower_bound_cells = bound_cells_[0]; + for (size_t i = 0; i != lower_bound_cells.size(); ++i) { + ListDataVector& cell_list_data = lower_bound_cells[i]->cell_list_data_; + for (size_t num = 0; num < cell_list_data.size(); ++num) checkLowerBound(cell_list_data[num], dt); + } + + //check upper bound + CellLists& upper_bound_cells = bound_cells_[1]; + for (size_t i = 0; i != upper_bound_cells.size(); ++i) { + ListDataVector& cell_list_data = upper_bound_cells[i]->cell_list_data_; + for (size_t num = 0; num < cell_list_data.size(); ++num) checkUpperBound(cell_list_data[num], dt); + } + } + //=================================================================================================// + void PeriodicConditionInAxisDirectionUsingCellLinkedList:: + PeriodicCellLinkedList::checkUpperBound(ListData& list_data, Real dt) + { + Vecd particle_position = list_data.second; + if (particle_position[axis_] < body_domain_bounds_.second[axis_] + && particle_position[axis_] > (body_domain_bounds_.second[axis_] - cut_off_radius_max_)) + { + Vecd translated_position = particle_position - periodic_translation_; + /** insert ghost particle to cell linked list */ + cell_linked_list_->InsertACellLinkedListDataEntry(list_data.first, translated_position); + } + } + //=================================================================================================// + void PeriodicConditionInAxisDirectionUsingCellLinkedList:: + PeriodicCellLinkedList::checkLowerBound(ListData& list_data, Real dt) + { + Vecd particle_position = list_data.second; + if (particle_position[axis_] > body_domain_bounds_.first[axis_] + && particle_position[axis_] < (body_domain_bounds_.first[axis_] + cut_off_radius_max_)) + { + Vecd translated_position = particle_position + periodic_translation_; + /** insert ghost particle to cell linked list */ + cell_linked_list_->InsertACellLinkedListDataEntry(list_data.first, translated_position); + } + } + //=================================================================================================// + OpenBoundaryConditionInAxisDirection:: + OpenBoundaryConditionInAxisDirection(RealBody* real_body, int axis_direction, bool positive): + particle_type_transfer(this->bound_cells_, real_body, axis_direction, positive) + { + BoundingBox body_domain_bounds = real_body->getBodyDomainBounds(); + bound_cells_.resize(2); + BaseCellLinkedList* cell_linked_list = real_body->cell_linked_list_; + cell_linked_list->tagBodyDomainBoundingCells(bound_cells_, body_domain_bounds, axis_direction); + } + //=================================================================================================// + void OpenBoundaryConditionInAxisDirection :: + ParticleTypeTransfer::checkLowerBound(size_t index_i, Real dt) + { + Vecd particle_position = pos_n_[index_i]; + if (particle_position[axis_] < body_domain_bounds_.first[axis_]) + particles_->switchToBufferParticle(index_i); + } + //=================================================================================================// + void OpenBoundaryConditionInAxisDirection :: + ParticleTypeTransfer::checkUpperBound(size_t index_i, Real dt) + { + Vecd particle_position = pos_n_[index_i]; + if (particle_position[axis_] > body_domain_bounds_.second[axis_]) + particles_->switchToBufferParticle(index_i); + } + //=================================================================================================// + void OpenBoundaryConditionInAxisDirection::ParticleTypeTransfer::exec(Real dt) + { + setupDynamics(dt); + + //check lower bound + CellLists& lower_bound_cells = bound_cells_[0]; + for (size_t i = 0; i != lower_bound_cells.size(); ++i) + { + IndexVector& particle_indexes = lower_bound_cells[i]->real_particle_indexes_; + for (size_t num = 0; num < particle_indexes.size(); ++num) checking_bound_(particle_indexes[num], dt); + } + + //check upper bound + CellLists& upper_bound_cells = bound_cells_[1]; + for (size_t i = 0; i != upper_bound_cells.size(); ++i) + { + IndexVector& particle_indexes = upper_bound_cells[i]->real_particle_indexes_; + for (size_t num = 0; num < particle_indexes.size(); ++num) checking_bound_(particle_indexes[num], dt); + } + } + //=================================================================================================// + void PeriodicConditionInAxisDirectionUsingGhostParticles:: + CreatPeriodicGhostParticles::setupDynamics(Real dt) + { + for (size_t i = 0; i != ghost_particles_.size(); ++i) ghost_particles_[i].clear(); + } + //=================================================================================================// + void PeriodicConditionInAxisDirectionUsingGhostParticles:: + CreatPeriodicGhostParticles::checkLowerBound(size_t index_i, Real dt) + { + Vecd particle_position = pos_n_[index_i]; + if (particle_position[axis_] > body_domain_bounds_.first[axis_] + && particle_position[axis_] < (body_domain_bounds_.first[axis_] + cut_off_radius_max_)) + { + size_t expected_particle_index = particles_->insertAGhostParticle(index_i); + ghost_particles_[0].push_back(expected_particle_index); + Vecd translated_position = particle_position + periodic_translation_; + /** insert ghost particle to cell linked list */ + cell_linked_list_->InsertACellLinkedListDataEntry(expected_particle_index, translated_position); + } + } + //=================================================================================================// + void PeriodicConditionInAxisDirectionUsingGhostParticles:: + CreatPeriodicGhostParticles::checkUpperBound(size_t index_i, Real dt) + { + Vecd particle_position = pos_n_[index_i]; + if (particle_position[axis_] < body_domain_bounds_.second[axis_] + && particle_position[axis_] > (body_domain_bounds_.second[axis_] - cut_off_radius_max_)) + { + size_t expected_particle_index = particles_->insertAGhostParticle(index_i); + ghost_particles_[1].push_back(expected_particle_index); + Vecd translated_position = particle_position - periodic_translation_; + /** insert ghost particle to cell linked list */ + cell_linked_list_->InsertACellLinkedListDataEntry(expected_particle_index, translated_position); + } + } + //=================================================================================================// + void PeriodicConditionInAxisDirectionUsingGhostParticles:: + UpdatePeriodicGhostParticles::checkLowerBound(size_t index_i, Real dt) + { + particles_->updateFromAnotherParticle(index_i, sorted_id_[index_i]); + pos_n_[index_i] += periodic_translation_; + } + //=================================================================================================// + void PeriodicConditionInAxisDirectionUsingGhostParticles:: + UpdatePeriodicGhostParticles::checkUpperBound(size_t index_i, Real dt) + { + particles_->updateFromAnotherParticle(index_i, sorted_id_[index_i]); + pos_n_[index_i] -= periodic_translation_; + } + //=================================================================================================// + void PeriodicConditionInAxisDirectionUsingGhostParticles:: + UpdatePeriodicGhostParticles::exec(Real dt) + { + for (size_t i = 0; i != ghost_particles_[0].size(); ++i) + { + checkLowerBound(ghost_particles_[0][i], dt); + } + for (size_t i = 0; i != ghost_particles_[1].size(); ++i) + { + checkUpperBound(ghost_particles_[1][i], dt); + } + } + //=================================================================================================// + void PeriodicConditionInAxisDirectionUsingGhostParticles:: + UpdatePeriodicGhostParticles::parallel_exec(Real dt) + { + parallel_for(blocked_range(0, ghost_particles_[0].size()), + [&](const blocked_range& r) { + for (size_t i = r.begin(); i < r.end(); ++i) { + checkLowerBound(ghost_particles_[0][i], dt); + } + }, ap); + + parallel_for(blocked_range(0, ghost_particles_[1].size()), + [&](const blocked_range& r) { + for (size_t i = r.begin(); i < r.end(); ++i) { + checkUpperBound(ghost_particles_[1][i], dt); + } + }, ap); + } + //=================================================================================================// + MirrorBoundaryConditionInAxisDirection::MirrorBounding + ::MirrorBounding(CellLists& bound_cells, RealBody* real_body, int axis_direction, bool positive) + : BoundingInAxisDirection(real_body, axis_direction), + bound_cells_(bound_cells), vel_n_(particles_->vel_n_) + { + checking_bound_ = positive ? + std::bind(&MirrorBoundaryConditionInAxisDirection::MirrorBounding::checkUpperBound, this, _1, _2) + : std::bind(&MirrorBoundaryConditionInAxisDirection::MirrorBounding::checkLowerBound, this, _1, _2); + } + //=================================================================================================// + MirrorBoundaryConditionInAxisDirection + ::CreatingGhostParticles::CreatingGhostParticles(IndexVector& ghost_particles, + CellLists& bound_cells, RealBody* real_body, int axis_direction, bool positive) + : MirrorBounding(bound_cells, real_body, axis_direction, positive), ghost_particles_(ghost_particles) {} + //=================================================================================================// + MirrorBoundaryConditionInAxisDirection::UpdatingGhostStates + ::UpdatingGhostStates(IndexVector& ghost_particles, CellLists& bound_cells, + RealBody* real_body, int axis_direction, bool positive) + : MirrorBounding(bound_cells, real_body, axis_direction, positive), ghost_particles_(ghost_particles) + { + checking_bound_update_ = positive ? + std::bind(&MirrorBoundaryConditionInAxisDirection::UpdatingGhostStates::checkUpperBound, this, _1, _2) + : std::bind(&MirrorBoundaryConditionInAxisDirection::UpdatingGhostStates::checkLowerBound, this, _1, _2); + } + //=================================================================================================// + void MirrorBoundaryConditionInAxisDirection + ::MirrorBounding::checkLowerBound(size_t index_i, Real dt) + { + if (pos_n_[index_i][axis_] < body_domain_bounds_.first[axis_]) { + mirrorInAxisDirection(index_i, body_domain_bounds_.first, axis_); + } + } + //=================================================================================================// + void MirrorBoundaryConditionInAxisDirection::MirrorBounding + ::checkUpperBound(size_t index_i, Real dt) + { + if (pos_n_[index_i][axis_] > body_domain_bounds_.second[axis_]) { + mirrorInAxisDirection(index_i, body_domain_bounds_.second, axis_); + } + } + //=================================================================================================// + void MirrorBoundaryConditionInAxisDirection::MirrorBounding:: + mirrorInAxisDirection(size_t particle_index_i, Vecd body_bound, int axis_direction) + { + pos_n_[particle_index_i][axis_direction] + = 2.0 * body_bound[axis_direction] - pos_n_[particle_index_i][axis_direction]; + vel_n_[particle_index_i][axis_direction] *= -1.0; + } + //=================================================================================================// + void MirrorBoundaryConditionInAxisDirection::MirrorBounding::exec(Real dt) + { + setupDynamics(dt); + for (size_t i = 0; i != bound_cells_.size(); ++i) { + ListDataVector& list_data = bound_cells_[i]->cell_list_data_; + for (size_t num = 0; num < list_data.size(); ++num) checking_bound_(list_data[num].first, dt); + } + } + //=================================================================================================// + void MirrorBoundaryConditionInAxisDirection::MirrorBounding ::parallel_exec(Real dt) + { + setupDynamics(dt); + parallel_for(blocked_range(0, bound_cells_.size()), + [&](const blocked_range& r) { + for (size_t i = r.begin(); i < r.end(); ++i) { + ListDataVector& list_data = bound_cells_[i]->cell_list_data_; + for (size_t num = 0; num < list_data.size(); ++num) checking_bound_(list_data[num].first, dt); + } + }, ap); + } + //=================================================================================================// + void MirrorBoundaryConditionInAxisDirection + ::CreatingGhostParticles::checkLowerBound(size_t index_i, Real dt) + { + Vecd particle_position = pos_n_[index_i]; + if (particle_position[axis_] > body_domain_bounds_.first[axis_] + && particle_position[axis_] < (body_domain_bounds_.first[axis_] + cut_off_radius_max_)) + { + size_t expected_particle_index = particles_->insertAGhostParticle(index_i); + ghost_particles_.push_back(expected_particle_index); + /** mirror boundary condition */ + mirrorInAxisDirection(expected_particle_index, body_domain_bounds_.first, axis_); + Vecd translated_position = particles_->pos_n_[expected_particle_index]; + /** insert ghost particle to cell linked list */ + cell_linked_list_->InsertACellLinkedListDataEntry(expected_particle_index, translated_position); + } + } + //=================================================================================================// + void MirrorBoundaryConditionInAxisDirection + ::CreatingGhostParticles::checkUpperBound(size_t index_i, Real dt) + { + Vecd particle_position = pos_n_[index_i]; + if (particle_position[axis_] < body_domain_bounds_.second[axis_] + && particle_position[axis_] > (body_domain_bounds_.second[axis_] - cut_off_radius_max_)) + { + size_t expected_particle_index = particles_->insertAGhostParticle(index_i); + ghost_particles_.push_back(expected_particle_index); + /** mirror boundary condition */ + mirrorInAxisDirection(expected_particle_index, body_domain_bounds_.second, axis_); + Vecd translated_position = particles_->pos_n_[expected_particle_index]; + /** insert ghost particle to cell linked list */ + cell_linked_list_->InsertACellLinkedListDataEntry(expected_particle_index, translated_position); + } + } + //=================================================================================================// + void MirrorBoundaryConditionInAxisDirection::UpdatingGhostStates + ::checkLowerBound(size_t index_i, Real dt) + { + particles_->updateFromAnotherParticle(index_i, sorted_id_[index_i]); + mirrorInAxisDirection(index_i, body_domain_bounds_.first, axis_); + } + //=================================================================================================// + void MirrorBoundaryConditionInAxisDirection::UpdatingGhostStates + ::checkUpperBound(size_t index_i, Real dt) + { + particles_->updateFromAnotherParticle(index_i, sorted_id_[index_i]); + mirrorInAxisDirection(index_i, body_domain_bounds_.second, axis_); + } + //=================================================================================================// + void MirrorBoundaryConditionInAxisDirection::UpdatingGhostStates + ::exec(Real dt) + { + for (size_t i = 0; i != ghost_particles_.size(); ++i) { + checking_bound_update_(ghost_particles_[i], dt); + } + } + //=================================================================================================// + void MirrorBoundaryConditionInAxisDirection::UpdatingGhostStates + ::parallel_exec(Real dt) + { + parallel_for(blocked_range(0, ghost_particles_.size()), + [&](const blocked_range& r) { + for (size_t i = r.begin(); i < r.end(); ++i) { + checking_bound_update_(ghost_particles_[i], dt); + } + }, ap); + } + //=================================================================================================// + VelocityBoundCheck:: + VelocityBoundCheck(SPHBody* body, Real velocity_bound) + : ParticleDynamicsReduce(body), + GeneralDataDelegateSimple(body), + vel_n_(particles_->vel_n_), velocity_bound_(velocity_bound) + { + initial_reference_ = false; + } + //=================================================================================================// + bool VelocityBoundCheck::ReduceFunction(size_t index_i, Real dt) + { + return vel_n_[index_i].norm() > velocity_bound_; + } + //=================================================================================================// + UpperFrontInXDirection:: + UpperFrontInXDirection(SPHBody* body) : + ParticleDynamicsReduce(body), + GeneralDataDelegateSimple(body), + pos_n_(particles_->pos_n_) + { + quantity_name_ = "UpperFrontInXDirection"; + initial_reference_ = 0.0; + } + //=================================================================================================// + Real UpperFrontInXDirection::ReduceFunction(size_t index_i, Real dt) + { + return pos_n_[index_i][0]; + } + //=================================================================================================// + MaximumSpeed:: + MaximumSpeed(SPHBody* body) : + ParticleDynamicsReduce(body), + GeneralDataDelegateSimple(body), + vel_n_(particles_->vel_n_) + { + quantity_name_ = "MaximumSpeed"; + initial_reference_ = 0.0; + } + //=================================================================================================// + Real MaximumSpeed::ReduceFunction(size_t index_i, Real dt) + { + return vel_n_[index_i].norm(); + } + //=================================================================================================// + BodyLowerBound::BodyLowerBound(SPHBody* body) + : ParticleDynamicsReduce(body), + GeneralDataDelegateSimple(body), + pos_n_(particles_->pos_n_) + { + constexpr double max_real_number = (std::numeric_limits::max)(); + initial_reference_ = Vecd(max_real_number); + } + //=================================================================================================// + Vecd BodyLowerBound::ReduceFunction(size_t index_i, Real dt) + { + return pos_n_[index_i]; + } + //=================================================================================================// + BodyUpperBound:: + BodyUpperBound(SPHBody* body) : + ParticleDynamicsReduce(body), + GeneralDataDelegateSimple(body), + pos_n_(particles_->pos_n_) + { + constexpr double min_real_number = (std::numeric_limits::min)(); + initial_reference_ = Vecd(min_real_number); + } + //=================================================================================================// + Vecd BodyUpperBound::ReduceFunction(size_t index_i, Real dt) + { + return pos_n_[index_i]; + } + //=================================================================================================// + TotalMechanicalEnergy::TotalMechanicalEnergy(SPHBody* body, Gravity* gravity) + : ParticleDynamicsReduce>(body), + GeneralDataDelegateSimple(body), mass_(particles_->mass_), + vel_n_(particles_->vel_n_), pos_n_(particles_->pos_n_), gravity_(gravity) + { + quantity_name_ = "TotalMechanicalEnergy"; + initial_reference_ = 0.0; + } + //=================================================================================================// + Real TotalMechanicalEnergy::ReduceFunction(size_t index_i, Real dt) + { + return 0.5 * mass_[index_i] * vel_n_[index_i].normSqr() + + mass_[index_i] * gravity_->getPotential(pos_n_[index_i]); + } + //=================================================================================================// +} +//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/general_dynamics/general_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/general_dynamics/general_dynamics.h new file mode 100644 index 0000000000..b143ff257c --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/general_dynamics/general_dynamics.h @@ -0,0 +1,547 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file general_dynamics.h +* @brief This is the particle dynamics aplliable for all type bodies +* @author Chi ZHang and Xiangyu Hu +*/ + + +#ifndef GENERAL_DYNAMICS_H +#define GENERAL_DYNAMICS_H + + + +#include "all_particle_dynamics.h" + +#include + +namespace SPH +{ + typedef DataDelegateSimple GeneralDataDelegateSimple; + typedef DataDelegateContact GeneralDataDelegateContact; + /** + * @class TimeStepInitialization + * @brief initialize a time step for a body. + * including initialize particle acceleration + * induced by viscous, gravity and other forces, + * set the number of ghost particles into zero. + */ + class TimeStepInitialization + : public ParticleDynamicsSimple, public GeneralDataDelegateSimple + { + public: + TimeStepInitialization(SPHBody* body, Gravity* gravity = new Gravity(Vecd(0))); + virtual ~TimeStepInitialization() {}; + protected: + StdLargeVec& pos_n_,& dvel_dt_prior_; + Gravity* gravity_; + virtual void setupDynamics(Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class RandomizePartilePosition + * @brief Randomize the initial particle position + */ + class RandomizePartilePosition + : public ParticleDynamicsSimple, public GeneralDataDelegateSimple + { + public: + RandomizePartilePosition(SPHBody* body); + virtual ~RandomizePartilePosition() {}; + protected: + StdLargeVec& pos_n_; + Real randomize_scale_; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class BoundingInAxisDirection + * @brief Bounding particle position in a axis direction. + * The axis_direction must be 0, 1 for 2d and 0, 1, 2 for 3d + */ + class BoundingInAxisDirection : + public ParticleDynamics, public GeneralDataDelegateSimple + { + protected: + const int axis_; /**< the axis directions for bounding*/ + BoundingBox body_domain_bounds_; /**< lower and upper bound for checking. */ + StdLargeVec& pos_n_; + BaseCellLinkedList* cell_linked_list_; + Real cut_off_radius_max_; /**< maximum cut off radius to avoid boundary particle depletion */ + public: + BoundingInAxisDirection(RealBody* real_body, int axis_direction); + virtual ~BoundingInAxisDirection() {}; + }; + + /** + * @class PeriodicConditionInAxisDirection + * @brief Base class for two different type periodic boundary conditions. + */ + class PeriodicConditionInAxisDirection + { + protected: + Vecd periodic_translation_; + StdVec bound_cells_; + void setPeriodicTranslation(BoundingBox& body_domain_bounds, int axis_direction); + + /** + * @class PeriodicBounding + * @brief Periodic bounding particle position in an axis direction + */ + class PeriodicBounding : public BoundingInAxisDirection + { + protected: + Vecd& periodic_translation_; + StdVec& bound_cells_; + + virtual void checkLowerBound(size_t index_i, Real dt = 0.0); + virtual void checkUpperBound(size_t index_i, Real dt = 0.0); + public: + PeriodicBounding(Vecd& periodic_translation, + StdVec& bound_cells, RealBody* real_body, int axis_direction) : + BoundingInAxisDirection(real_body, axis_direction), periodic_translation_(periodic_translation), + bound_cells_(bound_cells) {}; + virtual ~PeriodicBounding() {}; + + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override; + }; + + /** + * @class PeriodicCondition + * @brief implement periodic condition in an axis direction + */ + class PeriodicCondition : public BoundingInAxisDirection + { + protected: + Vecd& periodic_translation_; + StdVec& bound_cells_; + + virtual void checkLowerBound(ListData& list_data, Real dt = 0.0) = 0; + virtual void checkUpperBound(ListData& list_data, Real dt = 0.0) = 0; + public: + PeriodicCondition(Vecd& periodic_translation, + StdVec& bound_cells, RealBody* real_body, int axis_direction) : + BoundingInAxisDirection(real_body, axis_direction), periodic_translation_(periodic_translation), + bound_cells_(bound_cells) {}; + virtual ~PeriodicCondition() {}; + + /** This class is only implemented in sequential due to memory conflicts. + * Because the cell list data is not concurrent vector. + */ + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override { exec(); }; + }; + + public: + PeriodicConditionInAxisDirection(RealBody* real_body, int axis_direction); + virtual ~PeriodicConditionInAxisDirection() {}; + }; + + /** + * @class PeriodicConditionInAxisDirectionUsingCellLinkedList + * @brief The method imposing periodic boundary condition in an axis direction. + * It includes two different steps, i.e. imposing periodic bounding and condition. + * The first step is carried out before update cell linked list and + * the second after the updating. + * If the exec or parallel_exec is called directly, error message will be given. + */ + class PeriodicConditionInAxisDirectionUsingCellLinkedList : + public PeriodicConditionInAxisDirection + { + protected: + /** + * @class PeriodicCondition + * @brief Periodic boundary condition in an axis direction + */ + class PeriodicCellLinkedList : public PeriodicCondition + { + protected: + virtual void checkLowerBound(ListData& list_data, Real dt = 0.0) override; + virtual void checkUpperBound(ListData& list_data, Real dt = 0.0) override; + public: + + PeriodicCellLinkedList(Vecd& periodic_translation, + StdVec& bound_cells, RealBody* real_body, int axis_direction) + : PeriodicCondition(periodic_translation, bound_cells, real_body, axis_direction) {}; + virtual ~PeriodicCellLinkedList() {}; + }; + + public: + PeriodicConditionInAxisDirectionUsingCellLinkedList(RealBody* real_body, int axis_direction) : + PeriodicConditionInAxisDirection(real_body, axis_direction), + bounding_(this->periodic_translation_, this->bound_cells_, real_body, axis_direction), + update_cell_linked_list_(this->periodic_translation_, this->bound_cells_, real_body, axis_direction) {}; + virtual ~PeriodicConditionInAxisDirectionUsingCellLinkedList() {}; + + PeriodicBounding bounding_; + PeriodicCellLinkedList update_cell_linked_list_; + }; + + /** + * @class OpenBoundaryConditionInAxisDirection + * @brief In open boundary case, we transfer fluid particles to buffer paritcles at outlet + * @brief int axis_direction is used to choose direction in coordinate + * @brief bool positive is used to choose upper or lower bound in your choosed direction + */ + class OpenBoundaryConditionInAxisDirection + { + protected: + StdVec bound_cells_; + + class ParticleTypeTransfer : public BoundingInAxisDirection + { + protected: + StdVec&bound_cells_; + ParticleFunctor checking_bound_; + virtual void checkLowerBound(size_t index_i, Real dt = 0.0) ; + virtual void checkUpperBound(size_t index_i, Real dt = 0.0) ; + public: + ParticleTypeTransfer(StdVec& bound_cells, RealBody* real_body, int axis_direction, bool positive) : + BoundingInAxisDirection(real_body, axis_direction), + bound_cells_(bound_cells) + { + checking_bound_ = positive ? + std::bind(&OpenBoundaryConditionInAxisDirection::ParticleTypeTransfer::checkUpperBound, this, _1, _2) + : std::bind(&OpenBoundaryConditionInAxisDirection::ParticleTypeTransfer::checkLowerBound, this, _1, _2); + }; + virtual ~ParticleTypeTransfer() {}; + + /** This class is only implemented in sequential due to memory conflicts. + * Because the cell list data is not concurrent vector. + */ + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override { exec(); }; + }; + + public: + OpenBoundaryConditionInAxisDirection(RealBody* real_body, int axis_direction, bool positive); + virtual ~OpenBoundaryConditionInAxisDirection() {}; + + ParticleTypeTransfer particle_type_transfer; + }; + + /** + * @class PeriodicConditionInAxisDirectionUsingGhostParticles + * @brief The method imposing periodic boundary condition in an axis direction by using ghost particles. + * It includes three different steps, i.e. imposing periodic bounding, creating ghosts and update ghost state. + * The first step is carried out before update cell linked list and + * the second and third after the updating. + * If the exec or parallel_exec is called directly, error message will be given. + * Note that, currently, this class is not for periodic condition in combined directions, + * such as periodic condition in both x and y directions. + */ + class PeriodicConditionInAxisDirectionUsingGhostParticles : + public PeriodicConditionInAxisDirection + { + protected: + StdVec ghost_particles_; + + /** + * @class CreatPeriodicGhostParticles + * @brief create ghost particles in an axis direction + */ + class CreatPeriodicGhostParticles : public PeriodicBounding + { + protected: + StdVec& ghost_particles_; + virtual void setupDynamics(Real dt = 0.0) override; + virtual void checkLowerBound(size_t index_i, Real dt = 0.0) override; + virtual void checkUpperBound(size_t index_i, Real dt = 0.0) override; + public: + CreatPeriodicGhostParticles(Vecd& periodic_translation, StdVec& bound_cells, + StdVec& ghost_particles, RealBody* real_body, int axis_direction) : + PeriodicBounding(periodic_translation, bound_cells, real_body, axis_direction), + ghost_particles_(ghost_particles) {}; + virtual ~CreatPeriodicGhostParticles() {}; + + /** This class is only implemented in sequential due to memory conflicts. + * Because creating ghost particle allocate memory. + */ + virtual void parallel_exec(Real dt = 0.0) override { exec(); }; + }; + + /** + * @class UpdatePeriodicGhostParticles + * @brief update ghost particles in an axis direction + */ + class UpdatePeriodicGhostParticles : public PeriodicBounding + { + protected: + StdVec& ghost_particles_; + void checkLowerBound(size_t index_i, Real dt = 0.0) override; + void checkUpperBound(size_t index_i, Real dt = 0.0) override; + public: + UpdatePeriodicGhostParticles(Vecd& periodic_translation, StdVec& bound_cells, + StdVec& ghost_particles, RealBody* real_body, int axis_direction) : + PeriodicBounding(periodic_translation, bound_cells, real_body, axis_direction), + ghost_particles_(ghost_particles) {}; + virtual ~UpdatePeriodicGhostParticles() {}; + + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override; + }; + public: + PeriodicConditionInAxisDirectionUsingGhostParticles(RealBody* real_body, int axis_direction) : + PeriodicConditionInAxisDirection(real_body, axis_direction), + bounding_(this->periodic_translation_, this->bound_cells_, real_body, axis_direction), + ghost_creation_(this->periodic_translation_, this->bound_cells_, this->ghost_particles_, real_body, axis_direction), + ghost_update_(this->periodic_translation_, this->bound_cells_, this->ghost_particles_, real_body, axis_direction) + { + ghost_particles_.resize(2); + }; + + virtual ~PeriodicConditionInAxisDirectionUsingGhostParticles() {}; + + PeriodicBounding bounding_; + CreatPeriodicGhostParticles ghost_creation_; + UpdatePeriodicGhostParticles ghost_update_; + }; + + /** + * @class MirrorBoundaryConditionInAxisDirection + * @brief Mirror bounding particle position and velocity in an axis direction + * Note that, currently, this class is not for mirror condition in combined directions, + * such as mirror condition in both x and y directions. + */ + class MirrorBoundaryConditionInAxisDirection : public BoundingInAxisDirection + { + protected: + CellLists bound_cells_; + IndexVector ghost_particles_; + + class MirrorBounding : public BoundingInAxisDirection + { + protected: + CellLists& bound_cells_; + virtual void checkLowerBound(size_t index_i, Real dt = 0.0); + virtual void checkUpperBound(size_t index_i, Real dt = 0.0); + ParticleFunctor checking_bound_; + + StdLargeVec& vel_n_; + void mirrorInAxisDirection(size_t particle_index_i, Vecd body_bound, int axis_direction); + public: + MirrorBounding(CellLists& bound_cells, RealBody* real_body, int axis_direction, bool positive); + virtual ~MirrorBounding() {}; + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override; + }; + + /** + * @class CreatingGhostParticles + * @brief ghost particle created according to its corresponding real particle + */ + class CreatingGhostParticles : public MirrorBounding + { + protected: + IndexVector& ghost_particles_; + virtual void setupDynamics(Real dt = 0.0) override { ghost_particles_.clear(); }; + virtual void checkLowerBound(size_t index_i, Real dt = 0.0) override; + virtual void checkUpperBound(size_t index_i, Real dt = 0.0) override; + public: + CreatingGhostParticles(IndexVector& ghost_particles, CellLists& bound_cells, + RealBody* real_body, int axis_direction, bool positive); + virtual ~CreatingGhostParticles() {}; + /** This class is only implemented in sequential due to memory conflicts. */ + virtual void parallel_exec(Real dt = 0.0) override { exec(); }; + }; + + /** + * @class UpdatingGhostStates + * @brief the state of a ghost particle updated according to its corresponding real particle + */ + class UpdatingGhostStates : public MirrorBounding + { + protected: + IndexVector& ghost_particles_; + void checkLowerBound(size_t index_i, Real dt = 0.0) override; + void checkUpperBound(size_t index_i, Real dt = 0.0) override; + ParticleFunctor checking_bound_update_; + public: + UpdatingGhostStates(IndexVector& ghost_particles, CellLists& bound_cells, + RealBody* real_body, int axis_direction, bool positive); + virtual ~UpdatingGhostStates() {}; + + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override; + }; + + public: + MirrorBounding bounding_; + CreatingGhostParticles creating_ghost_particles_; + UpdatingGhostStates updating_ghost_states_; + + MirrorBoundaryConditionInAxisDirection(RealBody* real_body, int axis_direction, bool positive); + virtual ~MirrorBoundaryConditionInAxisDirection() {}; + + virtual void exec(Real dt = 0.0) override {}; + virtual void parallel_exec(Real dt = 0.0) override {}; + }; + + /** + * @class VelocityBoundCheck + * @brief check whether particle velocity within a given bound + */ + class VelocityBoundCheck : + public ParticleDynamicsReduce, + public GeneralDataDelegateSimple + { + public: + VelocityBoundCheck(SPHBody* body, Real velocity_bound); + virtual ~VelocityBoundCheck() {}; + protected: + StdLargeVec& vel_n_; + Real velocity_bound_; + bool ReduceFunction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class UpperFrontInXDirection + * @brief Get the upper front In X Direction for a SPH body + */ + class UpperFrontInXDirection : + public ParticleDynamicsReduce, + public GeneralDataDelegateSimple + { + public: + explicit UpperFrontInXDirection(SPHBody* body); + virtual ~UpperFrontInXDirection() {}; + protected: + StdLargeVec& pos_n_; + Real ReduceFunction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class MaximumSpeed + * @brief Get the maximum particle speed in a SPH body + */ + class MaximumSpeed : + public ParticleDynamicsReduce, + public GeneralDataDelegateSimple + { + public: + explicit MaximumSpeed(SPHBody* body); + virtual ~MaximumSpeed() {}; + protected: + StdLargeVec& vel_n_; + Real ReduceFunction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class BodyLowerBound + * @brief the lower bound of a body by reduced particle positions. + */ + class BodyLowerBound : + public ParticleDynamicsReduce, + public GeneralDataDelegateSimple + { + public: + explicit BodyLowerBound(SPHBody* body); + virtual ~BodyLowerBound() {}; + protected: + StdLargeVec& pos_n_; + Vecd ReduceFunction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class BodyUpperBound + * @brief the upper bound of a body by reduced particle positions. + */ + class BodyUpperBound : + public ParticleDynamicsReduce, + public GeneralDataDelegateSimple + { + public: + explicit BodyUpperBound(SPHBody* body); + virtual ~BodyUpperBound() {}; + protected: + StdLargeVec& pos_n_; + Vecd ReduceFunction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class BodySummation + * @brief Compute the summation of a particle variable in a body + */ + template + class BodySummation : + public ParticleDynamicsReduce>, + public GeneralDataDelegateSimple + { + public: + explicit BodySummation(SPHBody* body, std::string variable_name) : + ParticleDynamicsReduce>(body), + GeneralDataDelegateSimple(body), + variable_(*particles_->getVariableByName(variable_name)) + { + this->initial_reference_ = VariableType(0); + }; + virtual ~BodySummation() {}; + protected: + StdLargeVec& variable_; + VariableType ReduceFunction(size_t index_i, Real dt = 0.0) override + { + return variable_[index_i]; + }; + }; + + /** + * @class BodyMoment + * @brief Compute the moment of a body + */ + template + class BodyMoment : public BodySummation + { + public: + explicit BodyMoment(SPHBody* body, std::string variable_name) : + BodySummation(body, variable_name), + mass_(this->particles_->mass_) {}; + virtual ~BodyMoment() {}; + protected: + StdLargeVec& mass_; + VariableType ReduceFunction(size_t index_i, Real dt = 0.0) override + { + return mass_[index_i] * this->variable_[index_i]; + }; + }; + + /** + * @class TotalMechanicalEnergy + * @brief Compute the total mechanical (kinematic and potential) energy + */ + class TotalMechanicalEnergy + : public ParticleDynamicsReduce>, public GeneralDataDelegateSimple + { + public: + explicit TotalMechanicalEnergy(SPHBody* body, Gravity* gravity); + virtual ~TotalMechanicalEnergy() {}; + protected: + StdLargeVec& mass_; + StdLargeVec& vel_n_, & pos_n_; + Gravity* gravity_; + Real ReduceFunction(size_t index_i, Real dt = 0.0) override; + }; +} +#endif //GENERAL_DYNAMICS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/observer_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/observer_dynamics/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/observer_dynamics/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/observer_dynamics/observer_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/observer_dynamics/observer_dynamics.cpp new file mode 100644 index 0000000000..30837a056f --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/observer_dynamics/observer_dynamics.cpp @@ -0,0 +1,69 @@ +/** + * @file observer_dynamics.cpp + * @brief Here, Functions defined in observer_dyanmcis.h are detailed. + * @author Chi ZHang and Xiangyu Hu + */ + +#include "observer_dynamics.h" +//=================================================================================================// +using namespace SimTK; +//=================================================================================================// +namespace SPH +{ +//=================================================================================================// + namespace observer_dynamics + { + //=================================================================================================// + CorrectInterpolationKernelWeights:: + CorrectInterpolationKernelWeights(BaseBodyRelationContact* body_contact_relation) : + InteractionDynamics(body_contact_relation->sph_body_), + InterpolationContactData(body_contact_relation) + { + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); + } + } + //=================================================================================================// + void CorrectInterpolationKernelWeights::Interaction(size_t index_i, Real dt) + { + Vecd weight_correction(0.0); + Matd local_configuration(Eps); // small number added to diagonal to avoid divide zero + // Compute the first order consistent kernel weights + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + StdLargeVec& Vol_k = *(contact_Vol_[k]); + Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + Real weight_j = contact_neighborhood.W_ij_[n] * Vol_k[index_j]; + Vecd r_ji = -contact_neighborhood.r_ij_[n] * contact_neighborhood.e_ij_[n]; + Vecd gradw_ij = contact_neighborhood.dW_ij_[n] * contact_neighborhood.e_ij_[n]; + + weight_correction += r_ji * weight_j; + local_configuration += Vol_k[index_j] * SimTK::outer(r_ji, gradw_ij); + } + } + + // correction matrix for interacting configuration + Matd B_ = SimTK::inverse(local_configuration); + + // Add the kernel weight correction to W_ij_ of neighboring particles. + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + Vecd normalized_weight_correction = B_ * weight_correction; + contact_neighborhood.W_ij_[n] + -= dot(normalized_weight_correction, contact_neighborhood.e_ij_[n]) + * contact_neighborhood.dW_ij_[n]; + } + } + } + //=================================================================================================// + } +//=================================================================================================// +} + diff --git a/SPHINXsys/src/shared/particle_dynamics/observer_dynamics/observer_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/observer_dynamics/observer_dynamics.h new file mode 100644 index 0000000000..b0adccac46 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/observer_dynamics/observer_dynamics.h @@ -0,0 +1,123 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file observer_dynamics.h + * @brief There are the classes for observser bodies to record the state of the flow or + * solid in given locations. Mostly, this is done by an interpolation algorithm. + * @author Xiangyu Hu and Chi Zhang + */ + + +#ifndef OBSERVER_DYNAMICS_H +#define OBSERVER_DYNAMICS_H + + + +#include "all_particle_dynamics.h" + +namespace SPH +{ + namespace observer_dynamics + { + typedef DataDelegateContact InterpolationContactData; + + /** + * @class InterpolatingAQuantity + * @brief Interpolate a given member data in the particles of a general body + */ + template + class InterpolatingAQuantity : public InteractionDynamics, public InterpolationContactData + { + public: + explicit InterpolatingAQuantity(BaseBodyRelationContact* body_contact_relation, std::string variable_name) : + InteractionDynamics(body_contact_relation->sph_body_), InterpolationContactData(body_contact_relation), + interpolated_quantities_(*particles_->createAVariable(variable_name)) + { + prepareContactData(variable_name); + }; + explicit InterpolatingAQuantity(BaseBodyRelationContact* body_contact_relation, + std::string interpolated_variable, std::string target_variable) : + InteractionDynamics(body_contact_relation->sph_body_), InterpolationContactData(body_contact_relation), + interpolated_quantities_(*particles_->getVariableByName(interpolated_variable)) + { + prepareContactData(target_variable); + }; + virtual ~InterpolatingAQuantity() {}; + + protected: + StdLargeVec& interpolated_quantities_; + StdVec*> contact_Vol_; + StdVec*> contact_data_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override + { + VariableType observed_quantity(0); + Real ttl_weight(0); + + for (size_t k = 0; k < this->contact_configuration_.size(); ++k) + { + StdLargeVec& Vol_k = *(contact_Vol_[k]); + StdLargeVec& data_k = *(contact_data_[k]); + Neighborhood& contact_neighborhood = (*this->contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + Real weight_j = contact_neighborhood.W_ij_[n] * Vol_k[index_j]; + + observed_quantity += weight_j * data_k[index_j]; + ttl_weight += weight_j; + } + } + interpolated_quantities_[index_i] = observed_quantity / (ttl_weight + TinyReal); + }; + + void prepareContactData(const std::string& variable_name) + { + for (size_t k = 0; k != this->contact_particles_.size(); ++k) + { + contact_Vol_.push_back(&(this->contact_particles_[k]->Vol_)); + StdLargeVec* contact_data = + this->contact_particles_[k]->template getVariableByName(variable_name); + contact_data_.push_back(contact_data); + } + } + }; + + /** + * @class CorrectInterpolationKernelWeights + * @brief correct kernel weights for interpolation between general bodies + */ + class CorrectInterpolationKernelWeights : + public InteractionDynamics, public InterpolationContactData + { + public: + CorrectInterpolationKernelWeights(BaseBodyRelationContact* body_contact_relation); + virtual ~CorrectInterpolationKernelWeights() {}; + protected: + StdVec*> contact_Vol_; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + } +} +#endif //OBSERVER_DYNAMICS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_algorithms.cpp b/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_algorithms.cpp new file mode 100644 index 0000000000..c4f9bb31c2 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_algorithms.cpp @@ -0,0 +1,148 @@ +/** +* @file particle_dynamics_algorithms.cpp +* @brief This is the implementation of the template class particle dynamics algorithms +* @author Chi ZHang and Xiangyu Hu +*/ + +#include "particle_dynamics_algorithms.h" + +//=================================================================================================// +namespace SPH { + //=================================================================================================// + void ParticleDynamicsSimple::exec(Real dt) + { + setBodyUpdated(); + setupDynamics(dt); + size_t total_real_particles = base_particles_->total_real_particles_; + ParticleIterator(total_real_particles, functor_update_, dt); + } + //=================================================================================================// + void ParticleDynamicsSimple::parallel_exec(Real dt) + { + setBodyUpdated(); + setupDynamics(dt); + size_t total_real_particles = base_particles_->total_real_particles_; + ParticleIterator_parallel(total_real_particles, functor_update_, dt); + } + //=================================================================================================// + void InteractionDynamics::exec(Real dt) + { + setBodyUpdated(); + setupDynamics(dt); + for (size_t k = 0; k < pre_processes_.size(); ++k) pre_processes_[k]->exec(dt); + size_t total_real_particles = base_particles_->total_real_particles_; + ParticleIterator(total_real_particles, functor_interaction_, dt); + for (size_t k = 0; k < post_processes_.size(); ++k) post_processes_[k]->exec(dt); + } + //=================================================================================================// + void InteractionDynamics::parallel_exec(Real dt) + { + setBodyUpdated(); + setupDynamics(dt); + for (size_t k = 0; k < pre_processes_.size(); ++k) pre_processes_[k]->parallel_exec(dt); + size_t total_real_particles = base_particles_->total_real_particles_; + ParticleIterator_parallel(total_real_particles, functor_interaction_, dt); + for (size_t k = 0; k < post_processes_.size(); ++k) post_processes_[k]->parallel_exec(dt); + } + //=================================================================================================// + CombinedInteractionDynamics:: + CombinedInteractionDynamics(InteractionDynamics& dynamics_a, InteractionDynamics& dynamics_b) : + InteractionDynamics(dynamics_a.sph_body_), + dynamics_a_(dynamics_a), dynamics_b_(dynamics_b) + { + if (dynamics_a.sph_body_ != dynamics_b.sph_body_) + { + std::cout << "\n Error: CombinedInteractionDynamics does not have the same source body!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + + for (size_t k = 0; k < dynamics_a.pre_processes_.size(); ++k) + pre_processes_.push_back(dynamics_a.pre_processes_[k]); + for (size_t k = 0; k < dynamics_b.pre_processes_.size(); ++k) + pre_processes_.push_back(dynamics_b.pre_processes_[k]); + + for (size_t k = 0; k < dynamics_a.post_processes_.size(); ++k) + post_processes_.push_back(dynamics_a.post_processes_[k]); + for (size_t k = 0; k < dynamics_b.post_processes_.size(); ++k) + post_processes_.push_back(dynamics_b.post_processes_[k]); + } + //=================================================================================================// + void CombinedInteractionDynamics::setupDynamics(Real dt) + { + dynamics_a_.setupDynamics(dt); + dynamics_b_.setupDynamics(dt); + } + //=================================================================================================// + void CombinedInteractionDynamics::Interaction(size_t index_i, Real dt) + { + dynamics_a_.Interaction(index_i, dt); + dynamics_b_.Interaction(index_i, dt); + } + //=================================================================================================// + void InteractionDynamicsWithUpdate::exec(Real dt) + { + setBodyUpdated(); + setupDynamics(dt); + for (size_t k = 0; k < pre_processes_.size(); ++k) pre_processes_[k]->exec(dt); + size_t total_real_particles = base_particles_->total_real_particles_; + ParticleIterator(total_real_particles, functor_interaction_, dt); + for (size_t k = 0; k < post_processes_.size(); ++k) post_processes_[k]->exec(dt); + ParticleIterator(total_real_particles, functor_update_, dt); + } + //=================================================================================================// + void InteractionDynamicsWithUpdate::parallel_exec(Real dt) + { + setBodyUpdated(); + setupDynamics(dt); + for (size_t k = 0; k < pre_processes_.size(); ++k) pre_processes_[k]->parallel_exec(dt); + size_t total_real_particles = base_particles_->total_real_particles_; + ParticleIterator_parallel(total_real_particles, functor_interaction_, dt); + for (size_t k = 0; k < post_processes_.size(); ++k) post_processes_[k]->parallel_exec(dt); + ParticleIterator_parallel(total_real_particles, functor_update_, dt); + } + //=================================================================================================// + void ParticleDynamics1Level::exec(Real dt) + { + setBodyUpdated(); + setupDynamics(dt); + size_t total_real_particles = base_particles_->total_real_particles_; + ParticleIterator(total_real_particles, functor_initialization_, dt); + for (size_t k = 0; k < pre_processes_.size(); ++k) pre_processes_[k]->exec(dt); + ParticleIterator(total_real_particles, functor_interaction_, dt); + for (size_t k = 0; k < post_processes_.size(); ++k) post_processes_[k]->exec(dt); + ParticleIterator(total_real_particles, functor_update_, dt); + } + //=================================================================================================// + void ParticleDynamics1Level::parallel_exec(Real dt) + { + setBodyUpdated(); + setupDynamics(dt); + size_t total_real_particles = base_particles_->total_real_particles_; + ParticleIterator_parallel(total_real_particles, functor_initialization_, dt); + for (size_t k = 0; k < pre_processes_.size(); ++k) pre_processes_[k]->parallel_exec(dt); + ParticleIterator_parallel(total_real_particles, functor_interaction_, dt); + for (size_t k = 0; k < post_processes_.size(); ++k) post_processes_[k]->parallel_exec(dt); + ParticleIterator_parallel(total_real_particles, functor_update_, dt); + } + //=================================================================================================// + void InteractionDynamicsSplitting::exec(Real dt) + { + setBodyUpdated(); + setupDynamics(dt); + for (size_t k = 0; k < pre_processes_.size(); ++k) pre_processes_[k]->exec(dt); + ParticleIteratorSplittingSweep(split_cell_lists_, functor_interaction_, dt); + for (size_t k = 0; k < post_processes_.size(); ++k) post_processes_[k]->exec(dt); + } + //=================================================================================================// + void InteractionDynamicsSplitting::parallel_exec(Real dt) + { + setBodyUpdated(); + setupDynamics(dt); + for (size_t k = 0; k < pre_processes_.size(); ++k) pre_processes_[k]->parallel_exec(dt); + ParticleIteratorSplittingSweep_parallel(split_cell_lists_, functor_interaction_, dt); + for (size_t k = 0; k < post_processes_.size(); ++k) post_processes_[k]->parallel_exec(dt); + } + //=============================================================================================// +} +//=================================================================================================// \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_algorithms.h b/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_algorithms.h new file mode 100644 index 0000000000..947e0eca75 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_algorithms.h @@ -0,0 +1,213 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file particle_dynamics_algorithms.h +* @brief This is the classes for algorithms particle dynamics. +* @detail Generally, there are four types dynamics. One is without particle interaction. +* One is with particle interaction within a body. One is with particle interaction +* between a center body and other contacted bodies. +* Still another is the combination of the last two. +* For the first dynamics, there is also reduce dynamics +* which carries reduced operations through the particles of the body. + +* @author Chi ZHang and Xiangyu Hu +*/ + +#ifndef PARTICLE_DYNAMICS_ALGORITHMS_H +#define PARTICLE_DYNAMICS_ALGORITHMS_H + + + +#include "base_particle_dynamics.h" +#include "base_particle_dynamics.hpp" + +namespace SPH +{ + /** + * @class ParticleDynamicsSimple + * @brief Simple particle dynamics without considering particle interaction + */ + class ParticleDynamicsSimple : public ParticleDynamics + { + public: + explicit ParticleDynamicsSimple(SPHBody* sph_body) : + ParticleDynamics(sph_body), + functor_update_(std::bind(&ParticleDynamicsSimple::Update, this, _1, _2)) {}; + virtual ~ParticleDynamicsSimple() {}; + + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override; + protected: + virtual void Update(size_t index_i, Real dt = 0.0) = 0; + ParticleFunctor functor_update_; + }; + + /** + * @class ParticleDynamicsReduce + * @brief Base abstract class for reduce + */ + template + class ParticleDynamicsReduce : public ParticleDynamics + { + public: + explicit ParticleDynamicsReduce(SPHBody* sph_body) : + ParticleDynamics(sph_body), quantity_name_("ReducedQuantity"), initial_reference_(), + functor_reduce_function_(std::bind(&ParticleDynamicsReduce::ReduceFunction, this, _1, _2)) {}; + virtual ~ParticleDynamicsReduce() {}; + + ReturnType InitialReference() { return initial_reference_; }; + std::string QuantityName() { return quantity_name_; }; + + virtual ReturnType exec(Real dt = 0.0) override + { + size_t total_real_particles = this->base_particles_->total_real_particles_; + this->setBodyUpdated(); + SetupReduce(); + ReturnType temp = ReduceIterator(total_real_particles, + initial_reference_, functor_reduce_function_, reduce_operation_, dt); + return OutputResult(temp); + }; + virtual ReturnType parallel_exec(Real dt = 0.0) override + { + size_t total_real_particles = this->base_particles_->total_real_particles_; + this->setBodyUpdated(); + SetupReduce(); + ReturnType temp = ReduceIterator_parallel(total_real_particles, + initial_reference_, functor_reduce_function_, reduce_operation_, dt); + return this->OutputResult(temp); + }; + protected: + ReduceOperation reduce_operation_; + std::string quantity_name_; + + /** inital or reference value */ + ReturnType initial_reference_; + virtual void SetupReduce() {}; + virtual ReturnType ReduceFunction(size_t index_i, Real dt = 0.0) = 0; + virtual ReturnType OutputResult(ReturnType reduced_value) { return reduced_value; }; + ReduceFunctor functor_reduce_function_; + }; + + /** + * @class InteractionDynamics + * @brief This is the class for particle interaction with other particles + */ + class InteractionDynamics : public ParticleDynamics + { + public: + explicit InteractionDynamics(SPHBody* sph_body) + : ParticleDynamics(sph_body), + functor_interaction_(std::bind(&InteractionDynamics::Interaction, + this, _1, _2)) {}; + virtual ~InteractionDynamics() {}; + + /** pre process such as update ghost state */ + StdVec*> pre_processes_; + /** post process such as impose constraint */ + StdVec*> post_processes_; + + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override; + protected: + friend class CombinedInteractionDynamics; + virtual void Interaction(size_t index_i, Real dt = 0.0) = 0; + ParticleFunctor functor_interaction_; + }; + + /** + * @class CombinedInteractionDynamics + * @brief This is the class for combining several interactions dynamics, + * which share the particle loop but are independent from each other, + * aiming to increase computing intensity under the data caching environment + */ + class CombinedInteractionDynamics : public InteractionDynamics + { + public: + explicit CombinedInteractionDynamics(InteractionDynamics& dynamics_a, InteractionDynamics& dynamics_b); + virtual ~CombinedInteractionDynamics() {}; + + protected: + InteractionDynamics& dynamics_a_, & dynamics_b_; + virtual void setupDynamics(Real dt = 0.0) override; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class InteractionDynamicsWithUpdate + * @brief This class includes an interaction and a update steps + */ + class InteractionDynamicsWithUpdate : public InteractionDynamics + { + public: + InteractionDynamicsWithUpdate(SPHBody* sph_body) + : InteractionDynamics(sph_body), + functor_update_(std::bind(&InteractionDynamicsWithUpdate::Update, + this, _1, _2)) {} + virtual ~InteractionDynamicsWithUpdate() {}; + + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override; + protected: + virtual void Update(size_t index_i, Real dt = 0.0) = 0; + ParticleFunctor functor_update_; + }; + + /** + * @class ParticleDynamics1Level + * @brief This class includes an initialization, an interaction and a update steps + */ + class ParticleDynamics1Level : public InteractionDynamicsWithUpdate + { + public: + ParticleDynamics1Level(SPHBody* sph_body) + : InteractionDynamicsWithUpdate(sph_body), + functor_initialization_(std::bind(&ParticleDynamics1Level::Initialization, + this, _1, _2)) {} + virtual ~ParticleDynamics1Level() {}; + + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override; + protected: + virtual void Initialization(size_t index_i, Real dt = 0.0) = 0; + ParticleFunctor functor_initialization_; + }; + + /** + * @class InteractionDynamicsSplitting + * @brief This is for the splitting algorithm + */ + class InteractionDynamicsSplitting : public InteractionDynamics + { + public: + explicit InteractionDynamicsSplitting(SPHBody* sph_body) : + InteractionDynamics(sph_body), + split_cell_lists_(sph_body->split_cell_lists_) {}; + virtual ~InteractionDynamicsSplitting() {}; + + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override; + protected: + SplitCellLists& split_cell_lists_; + }; +} +#endif //PARTICLE_DYNAMICS_ALGORITHMS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_bodypart.cpp b/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_bodypart.cpp new file mode 100644 index 0000000000..e341c1a6a4 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_bodypart.cpp @@ -0,0 +1,161 @@ +/** +* @file particle_dynamics_bodypart.cpp +* @brief This is the implementation of the template class +* @author Chi ZHang and Xiangyu Hu +*/ + +#include "particle_dynamics_bodypart.h" + +namespace SPH { + //=================================================================================================// + void PartDynamicsByParticle::exec(Real dt) + { + setBodyUpdated(); + setupDynamics(dt); + for (size_t i = 0; i < body_part_particles_.size(); ++i) + { + particle_functor_(body_part_particles_[i], dt); + } + } + //=================================================================================================// + void PartDynamicsByParticle::parallel_exec(Real dt) + { + setBodyUpdated(); + setupDynamics(dt); + parallel_for(blocked_range(0, body_part_particles_.size()), + [&](const blocked_range& r) { + for (size_t i = r.begin(); i < r.end(); ++i) { + particle_functor_(body_part_particles_[i], dt); + } + }, ap); + } + //=================================================================================================// + PartSimpleDynamicsByParticle:: + PartSimpleDynamicsByParticle(SPHBody* sph_body, BodyPartByParticle *body_part) : + PartDynamicsByParticle(sph_body, body_part) + { + particle_functor_ = std::bind(&PartSimpleDynamicsByParticle::Update, this, _1, _2); + }; + //=================================================================================================// + PartInteractionDynamicsByParticle:: + PartInteractionDynamicsByParticle(SPHBody* sph_body, BodyPartByParticle* body_part) : + PartDynamicsByParticle(sph_body, body_part) + { + particle_functor_ = std::bind(&PartInteractionDynamicsByParticle::Interaction, this, _1, _2); + } + //=================================================================================================// + PartInteractionDynamicsByParticleWithUpdate:: + PartInteractionDynamicsByParticleWithUpdate(SPHBody* sph_body, BodyPartByParticle* body_part) : + PartInteractionDynamicsByParticle(sph_body, body_part) + { + functor_update_ = std::bind(&PartInteractionDynamicsByParticleWithUpdate::Update, this, _1, _2); + } + //=================================================================================================// + void PartInteractionDynamicsByParticleWithUpdate::exec(Real dt) + { + setBodyUpdated(); + setupDynamics(dt); + + for (size_t i = 0; i < body_part_particles_.size(); ++i) + { + particle_functor_(body_part_particles_[i], dt); + } + + for (size_t i = 0; i < body_part_particles_.size(); ++i) + { + functor_update_(body_part_particles_[i], dt); + } + } + //=================================================================================================// + void PartInteractionDynamicsByParticleWithUpdate::parallel_exec(Real dt) + { + setBodyUpdated(); + setupDynamics(dt); + + parallel_for(blocked_range(0, body_part_particles_.size()), + [&](const blocked_range& r) { + for (size_t i = r.begin(); i < r.end(); ++i) { + particle_functor_(body_part_particles_[i], dt); + } + }, ap); + + parallel_for(blocked_range(0, body_part_particles_.size()), + [&](const blocked_range& r) { + for (size_t i = r.begin(); i < r.end(); ++i) { + functor_update_(body_part_particles_[i], dt); + } + }, ap); + } + //=================================================================================================// + void PartInteractionDynamicsByParticle1Level::exec(Real dt) + { + setBodyUpdated(); + setupDynamics(dt); + + for (size_t i = 0; i < body_part_particles_.size(); ++i) + { + functor_initialization_(body_part_particles_[i], dt); + } + + for (size_t i = 0; i < body_part_particles_.size(); ++i) + { + particle_functor_(body_part_particles_[i], dt); + } + + for (size_t i = 0; i < body_part_particles_.size(); ++i) + { + functor_update_(body_part_particles_[i], dt); + } + } + //=================================================================================================// + void PartInteractionDynamicsByParticle1Level::parallel_exec(Real dt) + { + setBodyUpdated(); + setupDynamics(dt); + + parallel_for(blocked_range(0, body_part_particles_.size()), + [&](const blocked_range& r) { + for (size_t i = r.begin(); i < r.end(); ++i) { + functor_initialization_(body_part_particles_[i], dt); + } + }, ap); + + parallel_for(blocked_range(0, body_part_particles_.size()), + [&](const blocked_range& r) { + for (size_t i = r.begin(); i < r.end(); ++i) { + particle_functor_(body_part_particles_[i], dt); + } + }, ap); + + parallel_for(blocked_range(0, body_part_particles_.size()), + [&](const blocked_range& r) { + for (size_t i = r.begin(); i < r.end(); ++i) { + functor_update_(body_part_particles_[i], dt); + } + }, ap); + } + //=================================================================================================// + void PartDynamicsByCell::exec(Real dt) + { + setBodyUpdated(); + setupDynamics(dt); + for (size_t i = 0; i != body_part_cells_.size(); ++i) { + ListDataVector& list_data = body_part_cells_[i]->cell_list_data_; + for (size_t num = 0; num < list_data.size(); ++num) Update(list_data[num].first, dt); + } + } + //=================================================================================================// + void PartDynamicsByCell::parallel_exec(Real dt) + { + setBodyUpdated(); + setupDynamics(dt); + parallel_for(blocked_range(0, body_part_cells_.size()), + [&](const blocked_range& r) { + for (size_t i = r.begin(); i < r.end(); ++i) { + ListDataVector& list_data = body_part_cells_[i]->cell_list_data_; + for (size_t num = 0; num < list_data.size(); ++num) Update(list_data[num].first, dt); + } + }, ap); + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_bodypart.h b/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_bodypart.h new file mode 100644 index 0000000000..9a80322e1a --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/particle_dynamics_bodypart.h @@ -0,0 +1,264 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file particle_dynamics_bodypart.h + * @brief Dynamics for bodypart. + * The dynamics is constrained to a part of the body, + * such as in a subregion or on the surface of the body. + * The particles of a body part can be defined in an Eulerian or Lagrangian fashion. + * @author Chi ZHang and Xiangyu Hu + */ + +#ifndef PARTICLE_DYNAMICS_BODYPART_H +#define PARTICLE_DYNAMICS_BODYPART_H + + + +#include "base_particle_dynamics.h" +#include "base_particle_dynamics.hpp" + +namespace SPH { + + /** + * @class PartDynamicsByParticle + * @brief Abstract class for imposing body part dynamics by particles. + * That is the constrained particles will be the same + * during the simulation. + */ + class PartDynamicsByParticle : public ParticleDynamics + { + public: + PartDynamicsByParticle(SPHBody* sph_body, BodyPartByParticle *body_part) + : ParticleDynamics(sph_body), + body_part_particles_(body_part->body_part_particles_) {}; + virtual ~PartDynamicsByParticle() {}; + + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override; + protected: + IndexVector& body_part_particles_; + ParticleFunctor particle_functor_; + }; + + /** + * @class PartSimpleDynamicsByParticle + * @brief Abstract class for body part simple particle dynamics. + */ + class PartSimpleDynamicsByParticle : public PartDynamicsByParticle + { + public: + PartSimpleDynamicsByParticle(SPHBody* sph_body, BodyPartByParticle *body_part); + virtual ~PartSimpleDynamicsByParticle() {}; + protected: + virtual void Update(size_t index_i, Real dt = 0.0) = 0; + }; + + /** + * @class PartInteractionDynamicsByParticle + * @brief Abstract class for particle interaction involving in a body part. + */ + class PartInteractionDynamicsByParticle : public PartDynamicsByParticle + { + public: + PartInteractionDynamicsByParticle(SPHBody* sph_body, BodyPartByParticle* body_part); + virtual ~PartInteractionDynamicsByParticle() {}; + protected: + virtual void Interaction(size_t index_i, Real dt = 0.0) = 0; + }; + + /** + * @class PartInteractionDynamicsByParticleWithUpdate + * @brief Abstract class for particle interaction involving in a body part with an extra update step. + */ + class PartInteractionDynamicsByParticleWithUpdate : public PartInteractionDynamicsByParticle + { + public: + PartInteractionDynamicsByParticleWithUpdate(SPHBody* sph_body, BodyPartByParticle* body_part); + virtual ~PartInteractionDynamicsByParticleWithUpdate() {}; + + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override; + protected: + virtual void Update(size_t index_i, Real dt = 0.0) = 0; + ParticleFunctor functor_update_; + }; + + /** + * @class PartInteractionDynamicsByParticleWithUpdate + * @brief Abstract class for particle interaction involving in a body part with an extra update step. + */ + class PartInteractionDynamicsByParticle1Level : public PartInteractionDynamicsByParticleWithUpdate + { + public: + PartInteractionDynamicsByParticle1Level(SPHBody* sph_body, BodyPartByParticle* body_part) : + PartInteractionDynamicsByParticleWithUpdate(sph_body, body_part), + functor_initialization_(std::bind(&PartInteractionDynamicsByParticle1Level::Initialization, + this, _1, _2)) {}; + virtual ~PartInteractionDynamicsByParticle1Level() {}; + + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override; + protected: + virtual void Initialization(size_t index_i, Real dt = 0.0) = 0; + ParticleFunctor functor_initialization_; + }; + + /** + * @class PartDynamicsByCell + * @brief Abstract class for imposing Eulerian constrain to a body. + * The constrained particles are in the tagged cells . + */ + class PartDynamicsByCell : public ParticleDynamics + { + public: + PartDynamicsByCell(SPHBody* sph_body, BodyPartByCell *body_part) + : ParticleDynamics(sph_body), + body_part_cells_(body_part->body_part_cells_) {}; + virtual ~PartDynamicsByCell() {}; + + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override; + protected: + CellLists& body_part_cells_; + + virtual void Update(size_t index_i, Real dt = 0.0) = 0; + }; + /** + * @class PartDynamicsByCellReduce + * @brief Abstract class for reduce operation in a Eulerian constrain region. + */ + template + class PartDynamicsByCellReduce : public ParticleDynamics + { + public: + PartDynamicsByCellReduce(SPHBody* sph_body, BodyPartByCell* body_part) + : ParticleDynamics(sph_body), body_part_cells_(body_part->body_part_cells_), + quantity_name_("ReducedQuantity"), initial_reference_() {}; + virtual ~PartDynamicsByCellReduce() {}; + + ReturnType InitialReference() { return initial_reference_; }; + std::string QuantityName() { return quantity_name_; }; + + virtual ReturnType exec(Real dt = 0.0) override + { + ReturnType temp = initial_reference_; + this->SetupReduce(); + for (size_t i = 0; i != body_part_cells_.size(); ++i) + { + ListDataVector& list_data = body_part_cells_[i]->cell_list_data_; + for (size_t num = 0; num < list_data.size(); ++num) + { + temp = reduce_operation_(temp, ReduceFunction(list_data[num].first, dt)); + } + } + return OutputResult(temp); + }; + + virtual ReturnType parallel_exec(Real dt = 0.0) override + { + ReturnType temp = initial_reference_; + this->SetupReduce(); + temp = parallel_reduce(blocked_range(0, body_part_cells_.size()), + temp, + [&](const blocked_range& r, ReturnType temp0)->ReturnType + { + for (size_t i = r.begin(); i != r.end(); ++i) + { + ListDataVector& list_data = body_part_cells_[i]->cell_list_data_; + for (size_t num = 0; num < list_data.size(); ++num) + { + temp0 = reduce_operation_(temp0, ReduceFunction(list_data[num].first, dt)); + } + } + return temp0; + }, [this](ReturnType x, ReturnType y)->ReturnType { return reduce_operation_(x, y); } + ); + + return OutputResult(temp); + }; + protected: + ReduceOperation reduce_operation_; + CellLists& body_part_cells_; + std::string quantity_name_; + ReturnType initial_reference_; + virtual void SetupReduce() {}; + virtual ReturnType ReduceFunction(size_t index_i, Real dt = 0.0) = 0; + virtual ReturnType OutputResult(ReturnType reduced_value) { return reduced_value; }; + }; + /** + * @class PartDynamicsByParticleReduce + * @brief reduce operation in a Lagrangian contrained region. + */ + template + class PartDynamicsByParticleReduce : public ParticleDynamics + { + public: + PartDynamicsByParticleReduce(SPHBody* sph_body, BodyPartByParticle *body_part) + : ParticleDynamics(sph_body), + body_part_particles_(body_part->body_part_particles_), + quantity_name_("ReducedQuantity"), initial_reference_() {}; + virtual ~PartDynamicsByParticleReduce() {}; + + ReturnType InitialReference() { return initial_reference_; }; + std::string QuantityName() { return quantity_name_; }; + + virtual ReturnType exec(Real dt = 0.0) override + { + ReturnType temp = initial_reference_; + this->SetupReduce(); + for (size_t i = 0; i < body_part_particles_.size(); ++i) + { + temp = reduce_operation_(temp, ReduceFunction(body_part_particles_[i], dt)); + } + return OutputResult(temp); + }; + virtual ReturnType parallel_exec(Real dt = 0.0) override + { + ReturnType temp = initial_reference_; + this->SetupReduce(); + temp = parallel_reduce(blocked_range(0, body_part_particles_.size()), + temp, + [&](const blocked_range& r, ReturnType temp0)->ReturnType { + for (size_t n = r.begin(); n != r.end(); ++n) { + temp0 = reduce_operation_(temp0, ReduceFunction(body_part_particles_[n], dt)); + } + return temp0; + }, + [this](ReturnType x, ReturnType y)->ReturnType { + return reduce_operation_(x, y); + } + ); + + return OutputResult(temp); + }; + protected: + ReduceOperation reduce_operation_; + IndexVector& body_part_particles_; + std::string quantity_name_; + ReturnType initial_reference_; + virtual void SetupReduce() {}; + virtual ReturnType ReduceFunction(size_t index_i, Real dt = 0.0) = 0; + virtual ReturnType OutputResult(ReturnType reduced_value) { return reduced_value; }; + }; +} +#endif //PARTICLE_DYNAMICS_BODYPART_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/relax_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/relax_dynamics/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/relax_dynamics/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/relax_dynamics/relax_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/relax_dynamics/relax_dynamics.cpp new file mode 100644 index 0000000000..e922354ce7 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/relax_dynamics/relax_dynamics.cpp @@ -0,0 +1,239 @@ +/** + * @file relax_dynamics.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#include "relax_dynamics.h" +#include "particle_generator_lattice.h" +#include "geometry_level_set.h" + +using namespace SimTK; +//=================================================================================================// +namespace SPH +{ +//=================================================================================================// + namespace relax_dynamics + { + //=================================================================================================// + GetTimeStepSizeSquare::GetTimeStepSizeSquare(SPHBody* body) : + ParticleDynamicsReduce(body), + RelaxDataDelegateSimple(body), dvel_dt_(particles_->dvel_dt_) + { + h_ref_ = body->particle_adaptation_->ReferenceSmoothingLength(); + //given by sound criteria by assuming unit speed of sound and zero particle velocity + initial_reference_ = 1.0 / h_ref_; + } + //=================================================================================================// + Real GetTimeStepSizeSquare::ReduceFunction(size_t index_i, Real dt) + { + return dvel_dt_[index_i].norm(); + } + //=================================================================================================// + Real GetTimeStepSizeSquare::OutputResult(Real reduced_value) + { + return 0.0625 * h_ref_ / (reduced_value + TinyReal); + } + //=================================================================================================// + RelaxationAccelerationInner::RelaxationAccelerationInner(BaseBodyRelationInner* body_inner_relation) : + InteractionDynamics(body_inner_relation->sph_body_), + RelaxDataDelegateInner(body_inner_relation), + Vol_(particles_->Vol_), dvel_dt_(particles_->dvel_dt_), pos_n_(particles_->pos_n_) {} + //=================================================================================================// + void RelaxationAccelerationInner::Interaction(size_t index_i, Real dt) + { + Vecd acceleration(0); + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + acceleration -= 2.0 * inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n] * Vol_[index_j]; + } + dvel_dt_[index_i] = acceleration; + } + //=================================================================================================// + RelaxationAccelerationInnerWithLevelSetCorrection:: + RelaxationAccelerationInnerWithLevelSetCorrection(BaseBodyRelationInner* body_inner_relation) : + RelaxationAccelerationInner(body_inner_relation) + { + level_set_complex_shape_ = dynamic_cast(body_->body_shape_); + if (level_set_complex_shape_ == nullptr) + { + std::cout << "\n FAILURE: LevelSetComplexShape is undefined!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + } + //=================================================================================================// + void RelaxationAccelerationInnerWithLevelSetCorrection::Interaction(size_t index_i, Real dt) + { + RelaxationAccelerationInner::Interaction(index_i, dt); + dvel_dt_[index_i] -= 2.0 * level_set_complex_shape_-> + computeKernelGradientIntegral(pos_n_[index_i], particle_adaptation_->SmoothingLengthRatio(index_i)); + } + //=================================================================================================// + UpdateParticlePosition::UpdateParticlePosition(SPHBody* body) : + ParticleDynamicsSimple(body), RelaxDataDelegateSimple(body), + pos_n_(particles_->pos_n_), dvel_dt_(particles_->dvel_dt_) {} + //=================================================================================================// + void UpdateParticlePosition::Update(size_t index_i, Real dt_square) + { + pos_n_[index_i] += dvel_dt_[index_i] * dt_square * 0.5 / particle_adaptation_->SmoothingLengthRatio(index_i); + } + //=================================================================================================// + UpdateSolidParticlePosition::UpdateSolidParticlePosition(SPHBody* body) : + ParticleDynamicsSimple(body), solid_dynamics::SolidDataSimple(body), + pos_0_(particles_->pos_0_), pos_n_(particles_->pos_n_), dvel_dt_(particles_->dvel_dt_) {} + //=================================================================================================// + void UpdateSolidParticlePosition::Update(size_t index_i, Real dt_square) + { + pos_n_[index_i] += dvel_dt_[index_i] * dt_square * 0.5 / particle_adaptation_->SmoothingLengthRatio(index_i); + pos_0_[index_i] = pos_n_[index_i]; + } + //=================================================================================================// + UpdateSmoothingLengthRatioByBodyShape::UpdateSmoothingLengthRatioByBodyShape(SPHBody* body) : + ParticleDynamicsSimple(body), RelaxDataDelegateSimple(body), + h_ratio_(*particles_->getVariableByName("SmoothingLengthRatio")), + Vol_(particles_->Vol_), pos_n_(particles_->pos_n_), + body_shape_(*body->body_shape_), kernel_(*body->particle_adaptation_->getKernel()) + { + particle_spacing_by_body_shape_ = + dynamic_cast(body->particle_adaptation_); + } + //=================================================================================================// + void UpdateSmoothingLengthRatioByBodyShape::Update(size_t index_i, Real dt_square) + { + Real local_spacing = particle_spacing_by_body_shape_->getLocalSpacing(body_shape_, pos_n_[index_i]); + h_ratio_[index_i] = particle_adaptation_->ReferenceSpacing() / local_spacing; + Vol_[index_i] = powerN(local_spacing, Dimensions); + } + //=================================================================================================// + RelaxationAccelerationComplex:: + RelaxationAccelerationComplex(ComplexBodyRelation* body_complex_relation) : + InteractionDynamics(body_complex_relation->sph_body_), + RelaxDataDelegateComplex(body_complex_relation), + Vol_(particles_->Vol_), dvel_dt_(particles_->dvel_dt_) + { + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); + } + } + //=================================================================================================// + void RelaxationAccelerationComplex::Interaction(size_t index_i, Real dt) + { + Vecd acceleration(0); + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + acceleration -= 2.0 * inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]* Vol_[index_j]; + } + + /** Contact interaction. */ + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + StdLargeVec& Vol_k = *(contact_Vol_[k]); + Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + + acceleration -= 2.0 * Vol_k[index_j] + * contact_neighborhood.dW_ij_[n] * contact_neighborhood.e_ij_[n]; + } + } + + dvel_dt_[index_i] = acceleration; + } + //=================================================================================================// + ShapeSurfaceBounding:: + ShapeSurfaceBounding(SPHBody* body, NearShapeSurface* body_part) : + PartDynamicsByCell(body, body_part), RelaxDataDelegateSimple(body), + pos_n_(particles_->pos_n_), + constrained_distance_(0.5 * body->particle_adaptation_->MinimumSpacing()) {} + //=================================================================================================// + void ShapeSurfaceBounding::Update(size_t index_i, Real dt) + { + Real phi = body_->body_shape_->findSignedDistance(pos_n_[index_i]); + + if (phi > -constrained_distance_) + { + Vecd unit_normal = body_->body_shape_->findNormalDirection(pos_n_[index_i]); + unit_normal /= unit_normal.norm() + TinyReal; + pos_n_[index_i] -= (phi + constrained_distance_) * unit_normal; + } + } + //=================================================================================================// + ConstraintSurfaceParticles:: + ConstraintSurfaceParticles(SPHBody* body, ShapeSurface* body_part) + :PartSimpleDynamicsByParticle(body, body_part), RelaxDataDelegateSimple(body), + constrained_distance_(0.5 * body->particle_adaptation_->MinimumSpacing()), + pos_n_(particles_->pos_n_) + { + } + //=================================================================================================// + void ConstraintSurfaceParticles::Update(size_t index_i, Real dt) + { + Real phi = body_->body_shape_->findSignedDistance(pos_n_[index_i]); + Vecd unit_normal = body_->body_shape_->findNormalDirection(pos_n_[index_i]); + unit_normal /= unit_normal.norm() + TinyReal; + pos_n_[index_i] -= (phi + constrained_distance_) * unit_normal; + } + //=================================================================================================// + RelaxationStepInner:: + RelaxationStepInner(BaseBodyRelationInner* body_inner_relation, bool level_set_correction) : + ParticleDynamics(body_inner_relation->sph_body_), + real_body_(body_inner_relation->real_body_), inner_relation_(body_inner_relation), + relaxation_acceleration_inner_(nullptr), + get_time_step_square_(real_body_), update_particle_position_(real_body_), + surface_bounding_(real_body_, new NearShapeSurface(real_body_)) + { + relaxation_acceleration_inner_ = !level_set_correction ? + new RelaxationAccelerationInner(body_inner_relation) : + new RelaxationAccelerationInnerWithLevelSetCorrection(body_inner_relation); + } + //=================================================================================================// + void RelaxationStepInner::exec(Real dt) + { + real_body_->updateCellLinkedList(); + inner_relation_->updateConfiguration(); + relaxation_acceleration_inner_->exec(); + Real dt_square = get_time_step_square_.exec(); + update_particle_position_.exec(dt_square); + surface_bounding_.exec(); + } + //=================================================================================================// + void RelaxationStepInner::parallel_exec(Real dt) + { + real_body_->updateCellLinkedList(); + inner_relation_->updateConfiguration(); + relaxation_acceleration_inner_->parallel_exec(); + Real dt_square = get_time_step_square_.parallel_exec(); + update_particle_position_.parallel_exec(dt_square); + surface_bounding_.parallel_exec(); + } + //=================================================================================================// + void SolidRelaxationStepInner::exec(Real dt) + { + real_body_->updateCellLinkedList(); + inner_relation_->updateConfiguration(); + relaxation_acceleration_inner_->exec(); + Real dt_square = get_time_step_square_.exec(); + update_solid_particle_position_.exec(dt_square); + surface_bounding_.exec(); + } + //=================================================================================================// + void SolidRelaxationStepInner::parallel_exec(Real dt) + { + real_body_->updateCellLinkedList(); + inner_relation_->updateConfiguration(); + relaxation_acceleration_inner_->parallel_exec(); + Real dt_square = get_time_step_square_.parallel_exec(); + update_solid_particle_position_.parallel_exec(dt_square); + surface_bounding_.parallel_exec(); + } + //=================================================================================================// + } + //=================================================================================================// +} +//=================================================================================================// diff --git a/SPHINXsys/src/shared/particle_dynamics/relax_dynamics/relax_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/relax_dynamics/relax_dynamics.h new file mode 100644 index 0000000000..83db7f40e9 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/relax_dynamics/relax_dynamics.h @@ -0,0 +1,260 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file relax_dynamics.h +* @brief This is the classes of particle relaxation in order to produce body fitted +* initial particle distribution. +* @author Chi ZHang and Xiangyu Hu +*/ + + +#ifndef RELAX_DYNAMICS_H +#define RELAX_DYNAMICS_H + + + +#include "math.h" + +#include "all_particle_dynamics.h" +#include "neighbor_relation.h" +#include "base_kernel.h" +#include "cell_linked_list.h" +#include "solid_dynamics.h" + +namespace SPH +{ + class ComplexShape; + class LevelSetComplexShape; + + namespace relax_dynamics + { + typedef DataDelegateSimple RelaxDataDelegateSimple; + + typedef DataDelegateInner RelaxDataDelegateInner; + + typedef DataDelegateComplex RelaxDataDelegateComplex; + + /** + * @class GetTimeStepSizeSquare + * @brief relaxation dynamics for particle initialization + * computing the square of time step size + */ + class GetTimeStepSizeSquare : + public ParticleDynamicsReduce, + public RelaxDataDelegateSimple + { + public: + explicit GetTimeStepSizeSquare(SPHBody* body); + virtual ~GetTimeStepSizeSquare() {}; + protected: + StdLargeVec& dvel_dt_; + Real h_ref_; + Real ReduceFunction(size_t index_i, Real dt = 0.0) override; + Real OutputResult(Real reduced_value) override; + }; + + /** + * @class RelaxationAccelerationInner + * @brief simple algorithm for physics relaxation + * without considering contact interaction. + * this is usually used for solid like bodies + */ + class RelaxationAccelerationInner : + public InteractionDynamics, public RelaxDataDelegateInner + { + public: + RelaxationAccelerationInner(BaseBodyRelationInner* body_inner_relation); + virtual ~RelaxationAccelerationInner() {}; + protected: + StdLargeVec& Vol_; + StdLargeVec& dvel_dt_; + StdLargeVec& pos_n_; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class RelaxationAccelerationInnerWithLevelSetCorrection + * @brief we constrain particles to a level function representing the interafce. + */ + class RelaxationAccelerationInnerWithLevelSetCorrection : + public RelaxationAccelerationInner + { + public: + RelaxationAccelerationInnerWithLevelSetCorrection(BaseBodyRelationInner* body_inner_relation); + virtual ~RelaxationAccelerationInnerWithLevelSetCorrection() {}; + protected: + LevelSetComplexShape* level_set_complex_shape_; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class UpdateParticlePosition + * @brief update the particle position for a time step + */ + class UpdateParticlePosition : + public ParticleDynamicsSimple, public RelaxDataDelegateSimple + { + public: + explicit UpdateParticlePosition(SPHBody* body); + virtual ~UpdateParticlePosition() {}; + protected: + StdLargeVec& pos_n_, & dvel_dt_; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class UpdateSmoothingLengthRatioByBodyShape + * @brief update the particle smoothing length ratio + */ + class UpdateSmoothingLengthRatioByBodyShape : + public ParticleDynamicsSimple, public RelaxDataDelegateSimple + { + public: + explicit UpdateSmoothingLengthRatioByBodyShape(SPHBody* body); + virtual ~UpdateSmoothingLengthRatioByBodyShape() {}; + protected: + StdLargeVec& h_ratio_, & Vol_; + StdLargeVec& pos_n_; + ComplexShape& body_shape_; + Kernel& kernel_; + ParticleSpacingByBodyShape* particle_spacing_by_body_shape_; + + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class RelaxationAccelerationComplex + * @brief compute relaxation acceleration while consider the present of contact bodies + * with considering contact interaction + * this is usually used for fluid like bodies + */ + class RelaxationAccelerationComplex : + public InteractionDynamics, + public RelaxDataDelegateComplex + { + public: + RelaxationAccelerationComplex(ComplexBodyRelation* body_complex_relation); + virtual ~RelaxationAccelerationComplex() {}; + protected: + StdLargeVec& Vol_; + StdLargeVec& dvel_dt_; + StdVec*> contact_Vol_; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class ShapeSurfaceBounding + * @brief constrain surface particles by + * map contrained particles to geometry face and + * r = r + phi * norm (vector distance to face) + */ + class ShapeSurfaceBounding : + public PartDynamicsByCell, + public RelaxDataDelegateSimple + { + public: + ShapeSurfaceBounding(SPHBody *body, NearShapeSurface* body_part); + virtual ~ShapeSurfaceBounding() {}; + protected: + StdLargeVec& pos_n_; + Real constrained_distance_; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class ConstraintSurfaceParticles + * @brief constrain surface particles by + * map contrained particles to geometry face and + * r = r + phi * norm (vector distance to face) + */ + class ConstraintSurfaceParticles : + public PartSimpleDynamicsByParticle, + public RelaxDataDelegateSimple + { + public: + ConstraintSurfaceParticles(SPHBody* body, ShapeSurface* body_part); + virtual ~ConstraintSurfaceParticles() {}; + protected: + Real constrained_distance_; + StdLargeVec& pos_n_; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class RelaxationStepInner + * @brief carry out particle relaxation step of particles within the body + */ + class RelaxationStepInner : public ParticleDynamics + { + protected: + RealBody* real_body_; + BaseBodyRelationInner* inner_relation_; + public: + explicit RelaxationStepInner(BaseBodyRelationInner* body_inner_relation, bool level_set_correction = false); + virtual ~RelaxationStepInner() {}; + + RelaxationAccelerationInner* relaxation_acceleration_inner_; + GetTimeStepSizeSquare get_time_step_square_; + UpdateParticlePosition update_particle_position_; + ShapeSurfaceBounding surface_bounding_; + + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override; + }; + + /** + * @class UpdateParticlePosition + * @brief update the particle position for a time step + */ + class UpdateSolidParticlePosition : + public ParticleDynamicsSimple, public solid_dynamics::SolidDataSimple + { + public: + explicit UpdateSolidParticlePosition(SPHBody* body); + virtual ~UpdateSolidParticlePosition() {}; + protected: + StdLargeVec& pos_0_, & pos_n_, &dvel_dt_; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class SolidRelaxationStepInner + * @brief carry out particle relaxation step of particles within the body + */ + class SolidRelaxationStepInner : public RelaxationStepInner + { + public: + explicit SolidRelaxationStepInner(BaseBodyRelationInner* body_inner_relation, bool level_set_correction = false) : + RelaxationStepInner(body_inner_relation, level_set_correction), + update_solid_particle_position_(real_body_) {}; + virtual ~SolidRelaxationStepInner() {}; + + UpdateSolidParticlePosition update_solid_particle_position_; + + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override; + }; + + } +} +#endif //RELAX_DYNAMICS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/CMakeLists.txt b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/all_solid_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/all_solid_dynamics.h new file mode 100644 index 0000000000..5249b900eb --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/all_solid_dynamics.h @@ -0,0 +1,10 @@ +/** @file +This is the header file that user code should include to pick up all +solid dynamics used in SPHinXsys. **/ +#pragma once + +#include "solid_dynamics.h" +#include "thin_structure_dynamics.h" +#include "fluid_structure_interaction.h" +#include "thin_structure_math.h" +#include "inelastic_dynamics.h" diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/fluid_structure_interaction.cpp b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/fluid_structure_interaction.cpp new file mode 100644 index 0000000000..6cb99a0c04 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/fluid_structure_interaction.cpp @@ -0,0 +1,150 @@ +/** + * @file fluid_structure_interaction.cpp + * @author Luhui Han, Chi Zhang and Xiangyu Hu + */ + +#include "fluid_structure_interaction.h" + +namespace SPH +{ + namespace solid_dynamics + { + //=================================================================================================// + FluidViscousForceOnSolid:: + FluidViscousForceOnSolid(BaseBodyRelationContact* body_contact_relation) : + InteractionDynamics(body_contact_relation->sph_body_), + FSIContactData(body_contact_relation), + Vol_(particles_->Vol_), vel_ave_(particles_->vel_ave_), + viscous_force_from_fluid_(*particles_->createAVariable("ViscousForceFromFluid")) + { + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); + contact_rho_n_.push_back(&(contact_particles_[k]->rho_n_)); + contact_vel_n_.push_back(&(contact_particles_[k]->vel_n_)); + + mu_.push_back(contact_material_[k]->ReferenceViscosity()); + smoothing_length_.push_back(contact_bodies_[k]->particle_adaptation_->ReferenceSmoothingLength()); + } + } + //=================================================================================================// + void FluidViscousForceOnSolid::Interaction(size_t index_i, Real dt) + { + Real Vol_i = Vol_[index_i]; + Vecd& vel_ave_i = vel_ave_[index_i]; + + Vecd force(0); + /** Contact interaction. */ + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + Real mu_k = mu_[k]; + Real smoothing_length_k = smoothing_length_[k]; + StdLargeVec& Vol_k = *(contact_Vol_[k]); + StdLargeVec& vel_n_k = *(contact_vel_n_[k]); + Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + + Vecd vel_derivative = 2.0 * (vel_ave_i - vel_n_k[index_j]) + / (contact_neighborhood.r_ij_[n] + 0.01 * smoothing_length_k); + + force += 2.0 * mu_k * vel_derivative * Vol_i * Vol_k[index_j] + * contact_neighborhood.dW_ij_[n]; + } + } + + viscous_force_from_fluid_[index_i] = force; + } + //=================================================================================================// + void FluidAngularConservativeViscousForceOnSolid::Interaction(size_t index_i, Real dt) + { + Real Vol_i = Vol_[index_i]; + Vecd& vel_ave_i = vel_ave_[index_i]; + + Vecd force(0); + /** Contact interaction. */ + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + Real mu_k = mu_[k]; + Real smoothing_length_k = smoothing_length_[k]; + StdLargeVec& Vol_k = *(contact_Vol_[k]); + StdLargeVec& rho_n_k = *(contact_rho_n_[k]); + StdLargeVec& vel_n_k = *(contact_vel_n_[k]); + Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + + /** The following viscous force is given in Monaghan 2005 (Rep. Prog. Phys.) */ + Real v_r_ij = dot(vel_ave_i - vel_n_k[index_j], + contact_neighborhood.r_ij_[n] * contact_neighborhood.e_ij_[n]); + Real vel_difference = 0.0 * (vel_ave_i - vel_n_k[index_j]).norm() + * contact_neighborhood.r_ij_[n]; + Real eta_ij = 8.0 * SMAX(mu_k, rho_n_k[index_j] * vel_difference) * v_r_ij / + (contact_neighborhood.r_ij_[n] * contact_neighborhood.r_ij_[n] + 0.01 * smoothing_length_k); + force += eta_ij * Vol_i * Vol_k[index_j] + * contact_neighborhood.dW_ij_[n] * contact_neighborhood.e_ij_[n]; + } + } + + viscous_force_from_fluid_[index_i] = force; + } + //=================================================================================================// + TotalViscousForceOnSolid + ::TotalViscousForceOnSolid(SolidBody* body) : + ParticleDynamicsReduce>(body), + SolidDataSimple(body), + viscous_force_from_fluid_(*particles_->getVariableByName("ViscousForceFromFluid")) + { + quantity_name_ = "TotalViscousForceOnSolid"; + initial_reference_ = Vecd(0); + } + //=================================================================================================// + Vecd TotalViscousForceOnSolid::ReduceFunction(size_t index_i, Real dt) + { + return viscous_force_from_fluid_[index_i]; + } + //=================================================================================================// + TotalForceOnSolid::TotalForceOnSolid(SolidBody* body) : + ParticleDynamicsReduce>(body), + SolidDataSimple(body), + force_from_fluid_(particles_->force_from_fluid_) + { + quantity_name_ = "TotalForceOnSolid"; + initial_reference_ = Vecd(0); + } + //=================================================================================================// + Vecd TotalForceOnSolid::ReduceFunction(size_t index_i, Real dt) + { + return force_from_fluid_[index_i]; + } + //=================================================================================================// + InitializeDisplacement:: + InitializeDisplacement(SolidBody* body, StdLargeVec& pos_temp) : + ParticleDynamicsSimple(body), SolidDataSimple(body), + pos_temp_(pos_temp), pos_n_(particles_->pos_n_), + vel_ave_(particles_->vel_ave_), dvel_dt_ave_(particles_->dvel_dt_ave_) + { + } + //=================================================================================================// + void InitializeDisplacement::Update(size_t index_i, Real dt) + { + pos_temp_[index_i] = pos_n_[index_i]; + } + //=================================================================================================// + void UpdateAverageVelocityAndAcceleration::Update(size_t index_i, Real dt) + { + Vecd updated_vel_ave = (pos_n_[index_i] - pos_temp_[index_i]) / (dt + Eps); + dvel_dt_ave_[index_i] = (updated_vel_ave - vel_ave_[index_i]) / (dt + Eps); + vel_ave_[index_i] = updated_vel_ave; + } + //=================================================================================================// + AverageVelocityAndAcceleration:: + AverageVelocityAndAcceleration(SolidBody* body) : + pos_temp_(*body->base_particles_->createAVariable("TemporaryPosition")), + initialize_displacement_(body, pos_temp_), + update_averages_(body, pos_temp_) {} + //=================================================================================================// + } +} diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/fluid_structure_interaction.h b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/fluid_structure_interaction.h new file mode 100644 index 0000000000..3c9fee3c2e --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/fluid_structure_interaction.h @@ -0,0 +1,234 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file fluid_structure_interaction.h +* @brief Here, we define the algorithm classes for fluid structure interaction. +* @author Chi ZHang and Xiangyu Hu +*/ + +#ifndef FLUID_STRUCTURE_INTERACTION_H +#define FLUID_STRUCTURE_INTERACTION_H + + + +#include "all_particle_dynamics.h" +#include "base_material.h" +#include "fluid_dynamics_complex.h" +#include "riemann_solver.h" + +namespace SPH +{ + namespace solid_dynamics + { + typedef DataDelegateSimple SolidDataSimple; + typedef DataDelegateContact FSIContactData; + + /** + * @class FluidViscousForceOnSolid + * @brief Computing the viscous force from the fluid + */ + class FluidViscousForceOnSolid : + public InteractionDynamics, public FSIContactData + { + public: + FluidViscousForceOnSolid(BaseBodyRelationContact* body_contact_relation); + virtual ~FluidViscousForceOnSolid() {}; + protected: + StdLargeVec& Vol_; + StdLargeVec& vel_ave_, & viscous_force_from_fluid_; + StdVec*> contact_Vol_, contact_rho_n_; + StdVec*> contact_vel_n_; + StdVec mu_; + StdVec smoothing_length_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class FluidAngularConservativeViscousForceOnSolid + * @brief Computing the viscous force from the fluid + */ + class FluidAngularConservativeViscousForceOnSolid : public FluidViscousForceOnSolid + { + public: + FluidAngularConservativeViscousForceOnSolid(BaseBodyRelationContact* body_contact_relation) + : FluidViscousForceOnSolid(body_contact_relation) {}; + virtual ~FluidAngularConservativeViscousForceOnSolid() {}; + protected: + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class BaseFluidPressureForceOnSolidRiemann + * @brief Template class fro computing the pressure force from the fluid with different Riemann solvers. + * The pressrue force is added on the viscous force of the latter is computed. + * This class is for FSI applications to achieve smaller solid dynamics + * time step size compared to the fluid dynamics + */ + template + class BaseFluidPressureForceOnSolid : public FluidViscousForceOnSolid + { + public: + BaseFluidPressureForceOnSolid(BaseBodyRelationContact* body_contact_relation) : + FluidViscousForceOnSolid(body_contact_relation), + force_from_fluid_(particles_->force_from_fluid_), + dvel_dt_ave_(particles_->dvel_dt_ave_), n_(particles_->n_) + { + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + contact_p_.push_back(&(contact_particles_[k]->p_)); + contact_dvel_dt_prior_.push_back(&(contact_particles_[k]->dvel_dt_prior_)); + riemann_solvers_.push_back(new RiemannSolverType(*contact_material_[k], *contact_material_[k])); + } + }; + virtual ~BaseFluidPressureForceOnSolid() {}; + protected: + StdLargeVec& force_from_fluid_, & dvel_dt_ave_, & n_; + StdVec*> contact_p_; + StdVec*> contact_dvel_dt_prior_; + StdVec riemann_solvers_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override + { + Vecd& dvel_dt_ave_i = dvel_dt_ave_[index_i]; + Real Vol_i = Vol_[index_i]; + Vecd& vel_ave_i = vel_ave_[index_i]; + Vecd& n_i = n_[index_i]; + + Vecd force(0); + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + StdLargeVec& Vol_k = *(contact_Vol_[k]); + StdLargeVec& rho_n_k = *(contact_rho_n_[k]); + StdLargeVec& p_k = *(contact_p_[k]); + StdLargeVec& vel_n_k = *(contact_vel_n_[k]); + StdLargeVec& dvel_dt_prior_k = *(contact_dvel_dt_prior_[k]); + Fluid* fluid_k = contact_material_[k]; + RiemannSolverType* riemann_solver_k = riemann_solvers_[k]; + Neighborhood& contact_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + Vecd e_ij = contact_neighborhood.e_ij_[n]; + Real r_ij = contact_neighborhood.r_ij_[n]; + Real face_wall_external_acceleration + = dot((dvel_dt_prior_k[index_j] - dvel_dt_ave_i), e_ij); + Real p_in_wall = p_k[index_j] + rho_n_k[index_j] * r_ij * SMAX(0.0, face_wall_external_acceleration); + Real rho_in_wall = fluid_k->DensityFromPressure(p_in_wall); + Vecd vel_in_wall = 2.0 * vel_ave_i - vel_n_k[index_j]; + + FluidState state_l(rho_n_k[index_j], vel_n_k[index_j], p_k[index_j]); + FluidState state_r(rho_in_wall, vel_in_wall, p_in_wall); + Real p_star = riemann_solver_k->getPStar(state_l, state_r, n_i); + force -= 2.0 * p_star * e_ij * Vol_i * Vol_k[index_j] * contact_neighborhood.dW_ij_[n]; + } + } + force_from_fluid_[index_i] = viscous_force_from_fluid_[index_i] + force; + }; + }; + using FluidPressureForceOnSolid = BaseFluidPressureForceOnSolid; + using FluidPressureForceOnSolidRiemann = BaseFluidPressureForceOnSolid; + + /** + * @class TotalViscousForceOnSolid + * @brief Computing the total viscous force from fluid + */ + class TotalViscousForceOnSolid : + public ParticleDynamicsReduce>, public SolidDataSimple + { + public: + explicit TotalViscousForceOnSolid(SolidBody* body); + virtual ~TotalViscousForceOnSolid() {}; + protected: + StdLargeVec& viscous_force_from_fluid_; + Vecd ReduceFunction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class TotalForceOnSolid + * @brief Computing total force from fluid. + */ + class TotalForceOnSolid : + public ParticleDynamicsReduce>, public SolidDataSimple + { + public: + explicit TotalForceOnSolid(SolidBody* body); + virtual ~TotalForceOnSolid() {}; + protected: + StdLargeVec& force_from_fluid_; + Vecd ReduceFunction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class InitializeDisplacement + * @brief initialize the displacement for computing average velocity. + * This class is for FSI applications to achieve smaller solid dynamics + * time step size compared to the fluid dynamics + */ + class InitializeDisplacement : + public ParticleDynamicsSimple, public SolidDataSimple + { + public: + explicit InitializeDisplacement(SolidBody* body, StdLargeVec& pos_temp); + virtual ~InitializeDisplacement() {}; + protected: + StdLargeVec& pos_temp_, & pos_n_, & vel_ave_, & dvel_dt_ave_; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class UpdateAverageVelocityAndAcceleration + * @brief Computing average velocity. + * This class is for FSI applications to achieve smaller solid dynamics + * time step size compared to the fluid dynamics + */ + class UpdateAverageVelocityAndAcceleration : public InitializeDisplacement + { + public: + explicit UpdateAverageVelocityAndAcceleration(SolidBody* body, StdLargeVec& pos_temp) + : InitializeDisplacement(body, pos_temp) {}; + virtual ~UpdateAverageVelocityAndAcceleration() {}; + protected: + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class AverageVelocityAndAcceleration + * @brief Impose force matching between fluid and solid dynamics. + * Note that the fluid time step should be larger than that of solid time step. + * Otherwise numerical instability may occur. + */ + class AverageVelocityAndAcceleration + { + protected: + StdLargeVec& pos_temp_; + public: + InitializeDisplacement initialize_displacement_; + UpdateAverageVelocityAndAcceleration update_averages_; + + AverageVelocityAndAcceleration(SolidBody* body); + ~AverageVelocityAndAcceleration() {}; + }; + } +} +#endif //FLUID_STRUCTURE_INTERACTION_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.cpp new file mode 100644 index 0000000000..575dad9691 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.cpp @@ -0,0 +1,33 @@ +/** + * @file inelastic_dynamics.cpp + * @author Xiaojing Tang, Chi Zhang and Xiangyu Hu + */ + +#include "inelastic_dynamics.h" + +using namespace SimTK; + +namespace SPH +{ + namespace solid_dynamics + { + //=================================================================================================// + PlasticStressRelaxationFirstHalf:: + PlasticStressRelaxationFirstHalf(BaseBodyRelationInner* body_inner_relation) : + StressRelaxationFirstHalf(body_inner_relation), + plastic_solid_(dynamic_cast(material_)) + { + numerical_dissipation_factor_ = 0.25; + } + //=================================================================================================// + void PlasticStressRelaxationFirstHalf::Initialization(size_t index_i, Real dt) + { + pos_n_[index_i] += vel_n_[index_i] * dt * 0.5; + F_[index_i] += dF_dt_[index_i] * dt * 0.5; + rho_n_[index_i] = rho0_ / SimTK::det(F_[index_i]); + + stress_PK1_[index_i] = plastic_solid_->PlasticConstitutiveRelation(F_[index_i], index_i, dt) * B_[index_i]; + } + //=================================================================================================// + } +} diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.h new file mode 100644 index 0000000000..5b3207f4ba --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.h @@ -0,0 +1,56 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file inelastic_solid_dynamics.h +* @brief Here, we define the algorithm classes for inelastic_solid dynamics. +* @details We consider here a weakly compressible solids. +* @author Xiaojing Tang, Chi Zhang and Xiangyu Hu +*/ +#pragma once + +#include "solid_dynamics.h" + +namespace SPH +{ + namespace solid_dynamics + { + /** + * @class PlasticStressRelaxationFirstHalf + * @brief computing stress relaxation process by verlet time stepping + * This is the first step + */ + class PlasticStressRelaxationFirstHalf + : public StressRelaxationFirstHalf + { + public: + PlasticStressRelaxationFirstHalf(BaseBodyRelationInner* body_inner_relation); + virtual ~PlasticStressRelaxationFirstHalf() {}; + protected: + PlasticSolid* plastic_solid_; + + virtual void Initialization(size_t index_i, Real dt = 0.0) override; + }; + } +} + + diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.cpp new file mode 100644 index 0000000000..5a49032de5 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.cpp @@ -0,0 +1,820 @@ +/** + * @file solid_dynamics.cpp + * @author Luhui Han, Chi Zhang and Xiangyu Hu + */ + +#include "solid_dynamics.h" +#include "general_dynamics.h" + +using namespace SimTK; + +namespace SPH +{ + namespace solid_dynamics + { + //=================================================================================================// + ContactDensitySummation:: + ContactDensitySummation(SolidBodyRelationContact *solid_body_contact_relation) + : PartInteractionDynamicsByParticle(solid_body_contact_relation->sph_body_, + &solid_body_contact_relation->body_surface_layer_), + ContactDynamicsData(solid_body_contact_relation), + mass_(particles_->mass_), contact_density_(particles_->contact_density_) + { + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + contact_mass_.push_back(&(contact_particles_[k]->mass_)); + } + } + //=================================================================================================// + void ContactDensitySummation::Interaction(size_t index_i, Real dt) + { + /** Contact interaction. */ + Real sigma = 0.0; + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + StdLargeVec &contact_mass_k = *(contact_mass_[k]); + Neighborhood &contact_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + sigma += contact_neighborhood.W_ij_[n] * contact_mass_k[contact_neighborhood.j_[n]]; + } + } + contact_density_[index_i] = sigma; + } + //=================================================================================================// + ContactForce::ContactForce(SolidBodyRelationContact *solid_body_contact_relation) + : PartInteractionDynamicsByParticle(solid_body_contact_relation->sph_body_, + &solid_body_contact_relation->body_surface_layer_), + ContactDynamicsData(solid_body_contact_relation), + contact_density_(particles_->contact_density_), + Vol_(particles_->Vol_), mass_(particles_->mass_), + dvel_dt_prior_(particles_->dvel_dt_prior_), + contact_force_(particles_->contact_force_) + { + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); + contact_contact_density_.push_back(&(contact_particles_[k]->contact_density_)); + } + } + //=================================================================================================// + void ContactForce::Interaction(size_t index_i, Real dt) + { + Real Vol_i = Vol_[index_i]; + Real p_i = contact_density_[index_i] * material_->ContactStiffness(); + /** Contact interaction. */ + Vecd force(0.0); + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + StdLargeVec &contact_density_k = *(contact_contact_density_[k]); + StdLargeVec &Vol_k = *(contact_Vol_[k]); + Solid *solid_k = contact_material_[k]; + + Neighborhood &contact_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + Vecd e_ij = contact_neighborhood.e_ij_[n]; + + Real p_star = 0.5 * (p_i + contact_density_k[index_j] * solid_k->ContactStiffness()); + //force due to pressure + force -= 2.0 * p_star * e_ij * Vol_i * Vol_k[index_j] * contact_neighborhood.dW_ij_[n]; + } + } + contact_force_[index_i] = force; + dvel_dt_prior_[index_i] += force / mass_[index_i]; + } + //=================================================================================================// + DynamicContactForce:: + DynamicContactForce(SolidBodyRelationContact *solid_body_contact_relation, Real penalty_strength) + : PartInteractionDynamicsByParticle(solid_body_contact_relation->sph_body_, + &solid_body_contact_relation->body_surface_layer_), + ContactDynamicsData(solid_body_contact_relation), + Vol_(particles_->Vol_), mass_(particles_->mass_), + vel_n_(particles_->vel_n_), dvel_dt_prior_(particles_->dvel_dt_prior_), + contact_force_(particles_->contact_force_), penalty_strength_(penalty_strength) + { + Real impedence = material_->ReferenceDensity() * sqrt(material_->ContactStiffness()); + Real reference_pressure = material_->ReferenceDensity() * material_->ContactStiffness(); + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); + contact_vel_n_.push_back(&(contact_particles_[k]->vel_n_)); + Real contact_impedence = + contact_material_[k]->ReferenceDensity() * sqrt(contact_material_[k]->ContactStiffness()); + contact_impedence_.push_back(2.0 * impedence * contact_impedence / (impedence + contact_impedence)); + Real contact_reference_pressure = + contact_material_[k]->ReferenceDensity() * contact_material_[k]->ContactStiffness(); + contact_reference_pressure_.push_back(2.0 * reference_pressure * contact_reference_pressure / + (reference_pressure + contact_reference_pressure)); + } + } + //=================================================================================================// + void DynamicContactForce::Interaction(size_t index_i, Real dt) + { + Real Vol_i = Vol_[index_i]; + Vecd vel_i = vel_n_[index_i]; + + /** Contact interaction. */ + Vecd force(0.0); + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + Real particle_spacing_j1 = 1.0 / contact_bodies_[k]->particle_adaptation_->ReferenceSpacing(); + Real particle_spacing_ratio2 = + 1.0 / (this->body_->particle_adaptation_->ReferenceSpacing() * particle_spacing_j1); + particle_spacing_ratio2 *= 0.1 * particle_spacing_ratio2; + + StdLargeVec &Vol_k = *(contact_Vol_[k]); + StdLargeVec &vel_n_k = *(contact_vel_n_[k]); + + Neighborhood &contact_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + Vecd e_ij = contact_neighborhood.e_ij_[n]; + + Real impedence_p = 0.5 * contact_impedence_[k] * (SimTK::dot(vel_i - vel_n_k[index_j], -e_ij)); + Real overlap = contact_neighborhood.r_ij_[n]; + Real delta = 2.0 * overlap * particle_spacing_j1; + Real beta = delta < 1.0 ? (1.0 - delta) * (1.0 - delta) * particle_spacing_ratio2 : 0.0; + Real penalty_p = penalty_strength_ * beta * overlap * contact_reference_pressure_[k]; + + //force due to pressure + force -= 2.0 * (impedence_p + penalty_p) * e_ij * + Vol_i * Vol_k[index_j] * contact_neighborhood.dW_ij_[n]; + } + } + + contact_force_[index_i] = force; + dvel_dt_prior_[index_i] += force / mass_[index_i]; + } + //=================================================================================================// + ContactForceWithWall:: + ContactForceWithWall(SolidBodyRelationContact *solid_body_contact_relation, Real penalty_strength) + : PartInteractionDynamicsByParticle(solid_body_contact_relation->sph_body_, + &solid_body_contact_relation->body_surface_layer_), + ContactDynamicsData(solid_body_contact_relation), + Vol_(particles_->Vol_), mass_(particles_->mass_), + vel_n_(particles_->vel_n_), dvel_dt_prior_(particles_->dvel_dt_prior_), + contact_force_(particles_->contact_force_), penalty_strength_(penalty_strength) + { + impedence_ = material_->ReferenceDensity() * sqrt(material_->ContactStiffness()); + reference_pressure_ = material_->ReferenceDensity() * material_->ContactStiffness(); + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); + contact_vel_n_.push_back(&(contact_particles_[k]->vel_n_)); + contact_n_.push_back(&(contact_particles_[k]->n_)); + } + } + //=================================================================================================// + void ContactForceWithWall::Interaction(size_t index_i, Real dt) + { + Real Vol_i = Vol_[index_i]; + Vecd vel_i = vel_n_[index_i]; + + /** Contact interaction. */ + Vecd force(0.0); + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + Real particle_spacing_j1 = 1.0 / contact_bodies_[k]->particle_adaptation_->ReferenceSpacing(); + Real particle_spacing_ratio2 = + 1.0 / (body_->particle_adaptation_->ReferenceSpacing() * particle_spacing_j1); + particle_spacing_ratio2 *= 0.1 * particle_spacing_ratio2; + + StdLargeVec &Vol_k = *(contact_Vol_[k]); + StdLargeVec &n_k = *(contact_n_[k]); + StdLargeVec &vel_n_k = *(contact_vel_n_[k]); + + Neighborhood &contact_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + Vecd e_ij = contact_neighborhood.e_ij_[n]; + Vecd n_k_j = n_k[index_j]; + + Real impedence_p = 0.5 * impedence_ * (SimTK::dot(vel_i - vel_n_k[index_j], -n_k_j)); + Real overlap = contact_neighborhood.r_ij_[n] * SimTK::dot(n_k_j, e_ij); + Real delta = 2.0 * overlap * particle_spacing_j1; + Real beta = delta < 1.0 ? (1.0 - delta) * (1.0 - delta) * particle_spacing_ratio2 : 0.0; + Real penalty_p = penalty_strength_ * beta * overlap * reference_pressure_; + + //force due to pressure + force -= 2.0 * (impedence_p + penalty_p) * dot(e_ij, n_k_j) * + n_k_j * Vol_i * Vol_k[index_j] * contact_neighborhood.dW_ij_[n]; + } + } + + contact_force_[index_i] = force; + dvel_dt_prior_[index_i] += force / mass_[index_i]; + } + //=================================================================================================// + AcousticTimeStepSize::AcousticTimeStepSize(SolidBody *body, Real CFL) + : ParticleDynamicsReduce(body), + ElasticSolidDataSimple(body), CFL_(CFL), + vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_) + { + smoothing_length_ = particle_adaptation_->ReferenceSmoothingLength(); + initial_reference_ = DBL_MAX; + } + //=================================================================================================// + Real AcousticTimeStepSize::ReduceFunction(size_t index_i, Real dt) + { + //since the particle does not change its configuration in pressure relaxation step + //I chose a time-step size according to Eulerian method + Real sound_speed = material_->ReferenceSoundSpeed(); + return CFL_ * SMIN(sqrt(smoothing_length_ / (dvel_dt_[index_i].norm() + TinyReal)), + smoothing_length_ / (sound_speed + vel_n_[index_i].norm())); + } + //=================================================================================================// + CorrectConfiguration:: + CorrectConfiguration(BaseBodyRelationInner *body_inner_relation) + : InteractionDynamics(body_inner_relation->sph_body_), + SolidDataInner(body_inner_relation), + Vol_(particles_->Vol_), B_(particles_->B_) + { + } + //=================================================================================================// + void CorrectConfiguration::Interaction(size_t index_i, Real dt) + { + Matd local_configuration(Eps); // a small number added to diagonal to avoid divide zero + Neighborhood &inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + + Vecd gradw_ij = inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]; + Vecd r_ji = inner_neighborhood.r_ij_[n] * inner_neighborhood.e_ij_[n]; + local_configuration -= Vol_[index_j] * SimTK::outer(r_ji, gradw_ij); + } + B_[index_i] = SimTK::inverse(local_configuration); + } + //=================================================================================================// + ConstrainSolidBodyRegion:: + ConstrainSolidBodyRegion(SPHBody *body, BodyPartByParticle *body_part) + : PartSimpleDynamicsByParticle(body, body_part), SolidDataSimple(body), + pos_n_(particles_->pos_n_), pos_0_(particles_->pos_0_), + n_(particles_->n_), n_0_(particles_->n_0_), + vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), + vel_ave_(particles_->vel_ave_), dvel_dt_ave_(particles_->dvel_dt_ave_) + { + } + //=================================================================================================// + void ConstrainSolidBodyRegion::Update(size_t index_i, Real dt) + { + Vecd pos_0 = pos_0_[index_i]; + Vecd pos_n = pos_n_[index_i]; + Vecd vel_n = vel_n_[index_i]; + Vecd dvel_dt = dvel_dt_[index_i]; + + pos_n_[index_i] = getDisplacement(pos_0, pos_n); + vel_n_[index_i] = getVelocity(pos_0, pos_n, vel_n); + dvel_dt_[index_i] = getAcceleration(pos_0, pos_n, dvel_dt); + /** the average values are prescirbed also. */ + vel_ave_[index_i] = vel_n_[index_i]; + dvel_dt_ave_[index_i] = dvel_dt_[index_i]; + } + //=================================================================================================// + PositionSolidBody:: + PositionSolidBody(SPHBody *body, BodyPartByParticle *body_part, + Real start_time, Real end_time, Vecd pos_end_center) + : PartSimpleDynamicsByParticle(body, body_part), SolidDataSimple(body), + pos_n_(particles_->pos_n_), pos_0_(particles_->pos_0_), + vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), + vel_ave_(particles_->vel_ave_), dvel_dt_ave_(particles_->dvel_dt_ave_), + start_time_(start_time), end_time_(end_time), pos_end_center_(pos_end_center) + { + BoundingBox bounds = body->getBodyDomainBounds(); + pos_0_center_ = (bounds.first + bounds.second) * 0.5; + translation_ = pos_end_center_ - pos_0_center_; + } + //=================================================================================================// + Vecd PositionSolidBody::getDisplacement(size_t index_i, Real dt) + { + Vecd displacement; + try + { + // displacement from the initial position + Vecd pos_final = pos_0_[index_i] + translation_; + displacement = (pos_final - pos_n_[index_i]) * dt / + (end_time_ - GlobalStaticVariables::physical_time_); + } + catch (out_of_range &e) + { + throw runtime_error(string("PositionSolidBody::getDisplacement: particle index out of bounds") + to_string(index_i)); + } + return displacement; + } + //=================================================================================================// + void PositionSolidBody::Update(size_t index_i, Real dt) + { + try + { + // only apply in the defined time period + if (GlobalStaticVariables::physical_time_ >= start_time_ && + GlobalStaticVariables::physical_time_ <= end_time_) + { + pos_n_[index_i] = pos_n_[index_i] + getDisplacement(index_i, dt); // displacement from the initial position + vel_n_[index_i] = getVelocity(); + dvel_dt_[index_i] = getAcceleration(); + /** the average values are prescirbed also. */ + vel_ave_[index_i] = vel_n_[index_i]; + dvel_dt_ave_[index_i] = dvel_dt_[index_i]; + } + } + catch (out_of_range &e) + { + throw runtime_error(string("PositionSolidBody::Update: particle index out of bounds") + to_string(index_i)); + } + } + //=================================================================================================// + PositionScaleSolidBody:: + PositionScaleSolidBody(SPHBody *body, BodyPartByParticle *body_part, + Real start_time, Real end_time, Real end_scale) + : PartSimpleDynamicsByParticle(body, body_part), SolidDataSimple(body), + pos_n_(particles_->pos_n_), pos_0_(particles_->pos_0_), + vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), + vel_ave_(particles_->vel_ave_), dvel_dt_ave_(particles_->dvel_dt_ave_), + start_time_(start_time), end_time_(end_time), end_scale_(end_scale) + { + BoundingBox bounds = body->getBodyDomainBounds(); + pos_0_center_ = (bounds.first + bounds.second) * 0.5; + } + //=================================================================================================// + Vecd PositionScaleSolidBody::getDisplacement(size_t index_i, Real dt) + { + Vecd displacement; + try + { + // displacement from the initial position + Vecd pos_final = pos_0_center_ + end_scale_ * (pos_0_[index_i] - pos_0_center_); + displacement = (pos_final - pos_n_[index_i]) * dt / + (end_time_ - GlobalStaticVariables::physical_time_); + } + catch (out_of_range &e) + { + throw runtime_error(string("PositionScaleSolidBody::getDisplacement: particle index out of bounds") + to_string(index_i)); + } + return displacement; + } + //=================================================================================================// + void PositionScaleSolidBody::Update(size_t index_i, Real dt) + { + try + { + // only apply in the defined time period + if (GlobalStaticVariables::physical_time_ >= start_time_ && + GlobalStaticVariables::physical_time_ <= end_time_) + { + pos_n_[index_i] = pos_n_[index_i] + getDisplacement(index_i, dt); // displacement from the initial position + vel_n_[index_i] = getVelocity(); + dvel_dt_[index_i] = getAcceleration(); + /** the average values are prescirbed also. */ + vel_ave_[index_i] = vel_n_[index_i]; + dvel_dt_ave_[index_i] = dvel_dt_[index_i]; + } + } + catch (out_of_range &e) + { + throw runtime_error(string("PositionScaleSolidBody::Update: particle index out of bounds") + to_string(index_i)); + } + } + //=================================================================================================// + TranslateSolidBody:: + TranslateSolidBody(SPHBody* body, BodyPartByParticle* body_part, Real start_time, Real end_time, Vecd translation): + PartSimpleDynamicsByParticle(body, body_part), SolidDataSimple(body), + pos_n_(particles_->pos_n_), pos_0_(particles_->pos_0_), + vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), + vel_ave_(particles_->vel_ave_), dvel_dt_ave_(particles_->dvel_dt_ave_), + start_time_(start_time), end_time_(end_time), translation_(translation) + {} + //=================================================================================================// + Vecd TranslateSolidBody::getDisplacement(size_t index_i, Real dt) + { + Vecd displacement(0); + // if we are out of the time interval, return 0 + if (GlobalStaticVariables::physical_time_ < start_time_ || GlobalStaticVariables::physical_time_ > end_time_) return displacement; + try { + // distance left to reach the final position + Vecd translation_left = translation_ * (end_time_ - GlobalStaticVariables::physical_time_) / (end_time_ - start_time_); + // displacement is a portion of distance left, scaled by dt and remaining time + displacement = 0.5 * translation_left * dt / (end_time_ - GlobalStaticVariables::physical_time_); + } + catch(out_of_range& e){ + throw runtime_error(string("TranslateSolidBody::getDisplacement: particle index out of bounds") + to_string(index_i)); + } + return displacement; + } + //=================================================================================================// + void TranslateSolidBody::Update(size_t index_i, Real dt) + { + try + { + // only apply in the defined time period + if (GlobalStaticVariables::physical_time_ >= start_time_ && + GlobalStaticVariables::physical_time_ <= end_time_) + { + pos_n_[index_i] = pos_n_[index_i] + getDisplacement(index_i, dt); // displacement from the initial position + vel_n_[index_i] = getVelocity(); + dvel_dt_[index_i] = getAcceleration(); + /** the average values are prescirbed also. */ + vel_ave_[index_i] = vel_n_[index_i]; + dvel_dt_ave_[index_i] = dvel_dt_[index_i]; + } + } + catch (out_of_range &e) + { + throw runtime_error(string("TranslateSolidBody::Update: particle index out of bounds") + to_string(index_i)); + } + } + //=================================================================================================// + SoftConstrainSolidBodyRegion:: + SoftConstrainSolidBodyRegion(BaseBodyRelationInner *body_inner_relation, BodyPartByParticle *body_part) + : PartInteractionDynamicsByParticleWithUpdate(body_inner_relation->sph_body_, body_part), + SolidDataInner(body_inner_relation), + Vol_(particles_->Vol_), + vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), + vel_ave_(particles_->vel_ave_), dvel_dt_ave_(particles_->dvel_dt_ave_), + vel_temp_(*particles_->createAVariable("TemporaryVelocity")), + dvel_dt_temp_(*particles_->createAVariable("TemporaryAcceleration")) {} + //=================================================================================================// + void SoftConstrainSolidBodyRegion::Interaction(size_t index_i, Real dt) + { + Real ttl_weight(Eps); + Vecd vel_i = vel_n_[index_i]; + Vecd dvel_dt_i = dvel_dt_[index_i]; + + Neighborhood &inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + Real weight_j = inner_neighborhood.W_ij_[n] * Vol_[index_j]; + + ttl_weight += weight_j; + vel_i += vel_n_[index_j] * weight_j; + dvel_dt_i += dvel_dt_[index_j] * weight_j; + } + + vel_temp_[index_i] = vel_i / ttl_weight; + dvel_dt_temp_[index_i] = dvel_dt_i / ttl_weight; + } + //=================================================================================================// + void SoftConstrainSolidBodyRegion::Update(size_t index_i, Real dt) + { + vel_n_[index_i] = vel_temp_[index_i]; + dvel_dt_[index_i] = dvel_dt_temp_[index_i]; + /** the average values are prescirbed also. */ + vel_ave_[index_i] = vel_n_[index_i]; + dvel_dt_ave_[index_i] = dvel_dt_[index_i]; + } + //=================================================================================================// + ClampConstrainSolidBodyRegion:: + ClampConstrainSolidBodyRegion(BaseBodyRelationInner *body_inner_relation, BodyPartByParticle *body_part) + : ParticleDynamics(body_inner_relation->sph_body_), + constrianing_(new ConstrainSolidBodyRegion(body_inner_relation->sph_body_, body_part)), + softing_(new SoftConstrainSolidBodyRegion(body_inner_relation, body_part)) {} + //=================================================================================================// + void ClampConstrainSolidBodyRegion::exec(Real dt) + { + constrianing_->exec(); + softing_->exec(); + } + //=================================================================================================// + void ClampConstrainSolidBodyRegion::parallel_exec(Real dt) + { + constrianing_->parallel_exec(); + softing_->parallel_exec(); + } + //=================================================================================================// + ConstrainSolidBodyMassCenter:: + ConstrainSolidBodyMassCenter(SPHBody *body, Vecd constrain_direction) + : ParticleDynamicsSimple(body), SolidDataSimple(body), + correction_matrix_(Matd(1.0)), vel_n_(particles_->vel_n_) + { + for (int i = 0; i != Dimensions; ++i) + correction_matrix_[i][i] = constrain_direction[i]; + BodySummation compute_total_mass_(body, "Mass"); + total_mass_ = compute_total_mass_.parallel_exec(); + compute_total_momentum_ = new BodyMoment(body, "Velocity"); + } + //=================================================================================================// + void ConstrainSolidBodyMassCenter::setupDynamics(Real dt) + { + velocity_correction_ = + correction_matrix_ * compute_total_momentum_->parallel_exec(dt) / total_mass_; + } + //=================================================================================================// + void ConstrainSolidBodyMassCenter::Update(size_t index_i, Real dt) + { + vel_n_[index_i] -= velocity_correction_; + } + //=================================================================================================// + ImposeExternalForce:: + ImposeExternalForce(SolidBody *body, SolidBodyPartForSimbody *body_part) + : PartSimpleDynamicsByParticle(body, body_part), SolidDataSimple(body), + pos_0_(particles_->pos_0_), vel_n_(particles_->vel_n_), + vel_ave_(particles_->vel_ave_) {} + //=================================================================================================// + void ImposeExternalForce::Update(size_t index_i, Real dt) + { + Vecd induced_acceleration = getAcceleration(pos_0_[index_i]); + vel_n_[index_i] += induced_acceleration * dt; + vel_ave_[index_i] = vel_n_[index_i]; + } + //=================================================================================================// + SpringDamperConstraintParticleWise:: + SpringDamperConstraintParticleWise(SolidBody *body, Vecd stiffness, Real damping_ratio) + : ParticleDynamicsSimple(body), SolidDataSimple(body), + pos_n_(particles_->pos_n_), + pos_0_(particles_->pos_0_), + vel_n_(particles_->vel_n_), + dvel_dt_prior_(particles_->dvel_dt_prior_) + { + // calculate total mass + total_mass_ = 0.0; + for (size_t i = 0; i < particles_->mass_.size(); i++) + { + total_mass_ += particles_->mass_[i]; + } + // scale stiffness and damping by mass here, so it's not necessary in each iteration + stiffness_ = stiffness / total_mass_; + damping_coeff_ = stiffness * damping_ratio / total_mass_; + } + //=================================================================================================// + SpringDamperConstraintParticleWise::~SpringDamperConstraintParticleWise() + { + } + //=================================================================================================// + void SpringDamperConstraintParticleWise::setupDynamics(Real dt) + { + particles_->total_ghost_particles_ = 0; + } + //=================================================================================================// + Vecd SpringDamperConstraintParticleWise::getSpringForce(size_t index_i, Vecd &disp) + { + Vecd spring_force(0); + for (int i = 0; i < disp.size(); i++) + { + spring_force[i] = -stiffness_[i] * disp[i]; + } + return spring_force; + } + //=================================================================================================// + Vecd SpringDamperConstraintParticleWise::getDampingForce(size_t index_i) + { + Vecd damping_force(0); + for (int i = 0; i < vel_n_[index_i].size(); i++) + { + damping_force[i] = -damping_coeff_[i] * vel_n_[index_i][i]; + } + return damping_force; + } + //=================================================================================================// + void SpringDamperConstraintParticleWise::Update(size_t index_i, Real dt) + { + Vecd delta_x = pos_n_[index_i] - pos_0_[index_i]; + dvel_dt_prior_[index_i] += getSpringForce(index_i, delta_x); + dvel_dt_prior_[index_i] += getDampingForce(index_i); + } + //=================================================================================================// + AccelerationForBodyPartInBoundingBox:: + AccelerationForBodyPartInBoundingBox(SolidBody *body, BoundingBox *bounding_box, Vecd acceleration) + : ParticleDynamicsSimple(body), SolidDataSimple(body), + pos_n_(particles_->pos_n_), + dvel_dt_prior_(particles_->dvel_dt_prior_), + bounding_box_(bounding_box), + acceleration_(acceleration) {} + //=================================================================================================// + void AccelerationForBodyPartInBoundingBox::setupDynamics(Real dt) + { + particles_->total_ghost_particles_ = 0; + } + //=================================================================================================// + void AccelerationForBodyPartInBoundingBox::Update(size_t index_i, Real dt) + { + if (pos_n_.size() > index_i) + { + Vecd point = pos_n_[index_i]; + if (point.size() >= 3 && bounding_box_ != nullptr && bounding_box_->first.size() >= 3 && + bounding_box_->second.size() >= 3 && point[0] >= bounding_box_->first[0] && + point[0] <= bounding_box_->second[0] && + point[1] >= bounding_box_->first[1] && point[1] <= bounding_box_->second[1] && + point[2] >= bounding_box_->first[2] && point[2] <= bounding_box_->second[2]) + { + dvel_dt_prior_[index_i] += acceleration_; + } + } + } + //=================================================================================================// + ElasticDynamicsInitialCondition:: + ElasticDynamicsInitialCondition(SolidBody *body) + : ParticleDynamicsSimple(body), + ElasticSolidDataSimple(body), + pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_) + { + } + //=================================================================================================// + UpdateElasticNormalDirection:: + UpdateElasticNormalDirection(SolidBody *elastic_body) + : ParticleDynamicsSimple(elastic_body), + ElasticSolidDataSimple(elastic_body), + n_(particles_->n_), n_0_(particles_->n_0_), F_(particles_->F_) + { + } + //=================================================================================================// + DeformationGradientTensorBySummation:: + DeformationGradientTensorBySummation(BaseBodyRelationInner *body_inner_relation) + : InteractionDynamics(body_inner_relation->sph_body_), + ElasticSolidDataInner(body_inner_relation), + Vol_(particles_->Vol_), pos_n_(particles_->pos_n_), + B_(particles_->B_), F_(particles_->F_) + { + } + //=================================================================================================// + void DeformationGradientTensorBySummation::Interaction(size_t index_i, Real dt) + { + Vecd &pos_n_i = pos_n_[index_i]; + + Matd deformation(0.0); + Neighborhood &inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + + Vecd gradw_ij = inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]; + deformation -= Vol_[index_j] * SimTK::outer((pos_n_i - pos_n_[index_j]), gradw_ij); + } + + F_[index_i] = B_[index_i] * deformation; + } + //=================================================================================================// + BaseElasticRelaxation:: + BaseElasticRelaxation(BaseBodyRelationInner *body_inner_relation) + : ParticleDynamics1Level(body_inner_relation->sph_body_), + ElasticSolidDataInner(body_inner_relation), Vol_(particles_->Vol_), + rho_n_(particles_->rho_n_), mass_(particles_->mass_), + pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), + B_(particles_->B_), F_(particles_->F_), dF_dt_(particles_->dF_dt_) {} + //=================================================================================================// + StressRelaxationFirstHalf:: + StressRelaxationFirstHalf(BaseBodyRelationInner *body_inner_relation) + : BaseElasticRelaxation(body_inner_relation), + dvel_dt_prior_(particles_->dvel_dt_prior_), force_from_fluid_(particles_->force_from_fluid_), + stress_PK1_(particles_->stress_PK1_) + { + rho0_ = material_->ReferenceDensity(); + inv_rho0_ = 1.0 / rho0_; + smoothing_length_ = particle_adaptation_->ReferenceSmoothingLength(); + numerical_dissipation_factor_ = 0.25; + } + //=================================================================================================// + void StressRelaxationFirstHalf::Initialization(size_t index_i, Real dt) + { + pos_n_[index_i] += vel_n_[index_i] * dt * 0.5; + F_[index_i] += dF_dt_[index_i] * dt * 0.5; + rho_n_[index_i] = rho0_ / det(F_[index_i]); + //obtain the first Piola-Kirchhoff stress from the second Piola-Kirchhoff stress + //it seems using reproducing correction here increases convergence rate + //near the free surface + stress_PK1_[index_i] = F_[index_i] * (material_->ConstitutiveRelation(F_[index_i], index_i) + + material_->NumericalDampingRightCauchy(F_[index_i], dF_dt_[index_i], smoothing_length_, index_i)) * B_[index_i]; + } + //=================================================================================================// + void StressRelaxationFirstHalf::Interaction(size_t index_i, Real dt) + { + //including gravity and force from fluid + Vecd acceleration = dvel_dt_prior_[index_i] + force_from_fluid_[index_i] / mass_[index_i]; + Neighborhood &inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + Vecd e_ij = inner_neighborhood.e_ij_[n]; + acceleration += (stress_PK1_[index_i] + stress_PK1_[index_j]) * inner_neighborhood.dW_ij_[n] * + e_ij * Vol_[index_j] * inv_rho0_; + } + + dvel_dt_[index_i] = acceleration; + } + //=================================================================================================// + void StressRelaxationFirstHalf::Update(size_t index_i, Real dt) + { + vel_n_[index_i] += dvel_dt_[index_i] * dt; + } + //=================================================================================================// + KirchhoffStressRelaxationFirstHalf:: + KirchhoffStressRelaxationFirstHalf(BaseBodyRelationInner *body_inner_relation) + : StressRelaxationFirstHalf(body_inner_relation), + J_to_minus_2_over_dimension_(*particles_->createAVariable("DeterminantTerm")), + stress_on_particle_(*particles_->createAVariable("StressOnParticle")), + inverse_F_T_(*particles_->createAVariable("InverseTransposedDeformation")){}; + //=================================================================================================// + void KirchhoffStressRelaxationFirstHalf::Initialization(size_t index_i, Real dt) + { + pos_n_[index_i] += vel_n_[index_i] * dt * 0.5; + F_[index_i] += dF_dt_[index_i] * dt * 0.5; + Real J = det(F_[index_i]); + Real one_over_J = 1.0 / J; + rho_n_[index_i] = rho0_ * one_over_J; + J_to_minus_2_over_dimension_[index_i] = B_[index_i] * pow(one_over_J * one_over_J, one_over_dimensions_); + inverse_F_T_[index_i] = ~SimTK::inverse(F_[index_i]); + stress_on_particle_[index_i] = (Matd(1.0) * material_->VolumetricKirchhoff(J) + + material_->NumericalDampingLeftCauchy(F_[index_i], dF_dt_[index_i], smoothing_length_, index_i)) * B_[index_i] - + Matd(1.0) * material_->ShearModulus() * J_to_minus_2_over_dimension_[index_i] * + (F_[index_i] * ~F_[index_i]).trace() * one_over_dimensions_; + stress_PK1_[index_i] = F_[index_i] * material_->ConstitutiveRelation(F_[index_i], index_i); + } + //=================================================================================================// + void KirchhoffStressRelaxationFirstHalf::Interaction(size_t index_i, Real dt) + { + //including gravity and force from fluid + Vecd acceleration = dvel_dt_prior_[index_i] + force_from_fluid_[index_i] / mass_[index_i]; + Neighborhood &inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + Vecd extension = (pos_n_[index_i] - pos_n_[index_j]) / inner_neighborhood.r_ij_[n]; + Matd stress_ij = material_->ShearModulus() * + (J_to_minus_2_over_dimension_[index_i] + J_to_minus_2_over_dimension_[index_j]) * + SimTK::outer(extension, extension); + acceleration += ((stress_on_particle_[index_i] + stress_on_particle_[index_j] + stress_ij) * + (inverse_F_T_[index_i] + inverse_F_T_[index_j]) * 0.5) * + inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n] * Vol_[index_j] * inv_rho0_; + } + dvel_dt_[index_i] = acceleration; + } + //=================================================================================================// + void StressRelaxationSecondHalf::Initialization(size_t index_i, Real dt) + { + pos_n_[index_i] += vel_n_[index_i] * dt * 0.5; + } + //=================================================================================================// + void StressRelaxationSecondHalf::Interaction(size_t index_i, Real dt) + { + Vecd &vel_n_i = vel_n_[index_i]; + + Matd deformation_gradient_change_rate(0); + Neighborhood &inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + + Vecd gradw_ij = inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]; + deformation_gradient_change_rate -= + Vol_[index_j] * SimTK::outer((vel_n_i - vel_n_[index_j]), gradw_ij); + } + + dF_dt_[index_i] = deformation_gradient_change_rate * B_[index_i]; + } + //=================================================================================================// + void StressRelaxationSecondHalf::Update(size_t index_i, Real dt) + { + F_[index_i] += dF_dt_[index_i] * dt * 0.5; + } + //=================================================================================================// + ConstrainSolidBodyPartBySimBody:: + ConstrainSolidBodyPartBySimBody(SolidBody *body, + SolidBodyPartForSimbody *body_part, + SimTK::MultibodySystem &MBsystem, + SimTK::MobilizedBody &mobod, + SimTK::Force::DiscreteForces &force_on_bodies, + SimTK::RungeKuttaMersonIntegrator &integ) + : ConstrainSolidBodyRegion(body, body_part), + MBsystem_(MBsystem), mobod_(mobod), force_on_bodies_(force_on_bodies), integ_(integ) + { + simbody_state_ = &integ_.getState(); + MBsystem_.realize(*simbody_state_, Stage::Acceleration); + initial_mobod_origin_location_ = mobod_.getBodyOriginLocation(*simbody_state_); + } + //=================================================================================================// + void ConstrainSolidBodyPartBySimBody::setupDynamics(Real dt) + { + body_->setNewlyUpdated(); + simbody_state_ = &integ_.getState(); + MBsystem_.realize(*simbody_state_, Stage::Acceleration); + } + //=================================================================================================// + TotalForceOnSolidBodyPartForSimBody:: + TotalForceOnSolidBodyPartForSimBody(SolidBody *body, + SolidBodyPartForSimbody *body_part, + SimTK::MultibodySystem &MBsystem, + SimTK::MobilizedBody &mobod, + SimTK::Force::DiscreteForces &force_on_bodies, + SimTK::RungeKuttaMersonIntegrator &integ) + : PartDynamicsByParticleReduce>(body, body_part), + SolidDataSimple(body), + force_from_fluid_(particles_->force_from_fluid_), contact_force_(particles_->contact_force_), + pos_n_(particles_->pos_n_), + MBsystem_(MBsystem), mobod_(mobod), force_on_bodies_(force_on_bodies), integ_(integ) + { + initial_reference_ = SpatialVec(Vec3(0), Vec3(0)); + } + //=================================================================================================// + void TotalForceOnSolidBodyPartForSimBody::SetupReduce() + { + simbody_state_ = &integ_.getState(); + MBsystem_.realize(*simbody_state_, Stage::Acceleration); + current_mobod_origin_location_ = mobod_.getBodyOriginLocation(*simbody_state_); + } + //=================================================================================================// + } +} diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.h new file mode 100644 index 0000000000..dbbd58597b --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.h @@ -0,0 +1,618 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file solid_dynamics.h +* @brief Here, we define the algorithm classes for solid dynamics. +* @details We consider here a weakly compressible solids. +* @author Luhui Han, Chi ZHang and Xiangyu Hu +*/ + +#ifndef SOLID_DYNAMICS_H +#define SOLID_DYNAMICS_H + + +#include "all_particle_dynamics.h" +#include "elastic_solid.h" +#include "weakly_compressible_fluid.h" +#include "base_kernel.h" +#include "all_fluid_dynamics.h" + +namespace SPH +{ + template + class BodySummation; + template + class BodyMoment; + + namespace solid_dynamics + { + //---------------------------------------------------------------------- + // for general solid dynamics + //---------------------------------------------------------------------- + typedef DataDelegateSimple SolidDataSimple; + typedef DataDelegateInner SolidDataInner; + typedef DataDelegateContact ContactDynamicsData; + + /** + * @class SolidDynamicsInitialCondition + * @brief set initial condition for solid fluid body + * This is a abstract class to be override for case specific initial conditions. + */ + class SolidDynamicsInitialCondition : + public ParticleDynamicsSimple, public SolidDataSimple + { + public: + SolidDynamicsInitialCondition(SolidBody* body) : + ParticleDynamicsSimple(body), SolidDataSimple(body) {}; + virtual ~SolidDynamicsInitialCondition() {}; + }; + + /** + * @class ContactDensitySummation + * @brief Computing the summation density due to solid-solid contact model. + */ + class ContactDensitySummation : + public PartInteractionDynamicsByParticle, public ContactDynamicsData + { + public: + ContactDensitySummation(SolidBodyRelationContact* solid_body_contact_relation); + virtual ~ContactDensitySummation() {}; + protected: + StdLargeVec& mass_, & contact_density_; + StdVec*> contact_mass_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class ContactForce + * @brief Computing the contact force. + */ + class ContactForce : + public PartInteractionDynamicsByParticle, public ContactDynamicsData + { + public: + ContactForce(SolidBodyRelationContact* solid_body_contact_relation); + virtual ~ContactForce() {}; + protected: + StdLargeVec& contact_density_, & Vol_, & mass_; + StdLargeVec& dvel_dt_prior_, & contact_force_; + StdVec*> contact_contact_density_, contact_Vol_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class DynamicContactForce + * @brief Computing the contact force for problems dominainted by the contact dynamic process itself. + * For example, the high speed impact problems in which the detailed contact behavior is crutial for + * physical sound solutions. Therefore, for simple low speed problem in which contact force is + * used merely prevent penetration. We can still use the simple formualation in the class ContactForce. + * The idea is to introduce conact force based on Riemann problem like formulation, + * in which the artficial dissipation is the main interaction force to prevent + * penetration. Furthermore, a panelty type force is used as supplementrary to prevent penetration + * when the contact velocity is small. + */ + class DynamicContactForce : + public PartInteractionDynamicsByParticle, public ContactDynamicsData + { + public: + DynamicContactForce(SolidBodyRelationContact* solid_body_contact_relation, Real penalty_strength = 1.0); + virtual ~DynamicContactForce() {}; + protected: + StdLargeVec& Vol_, & mass_; + StdLargeVec& vel_n_, & dvel_dt_prior_, & contact_force_; + StdVec*> contact_Vol_; + StdVec*>contact_vel_n_; + Real penalty_strength_; + StdVec contact_impedence_, contact_reference_pressure_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class ContactForceWithWall + * @brief Computing the contact force with a rigid wall. + * Note that the body surface of the wall should be + * updated beforce computing the contact force. + */ + class ContactForceWithWall : + public PartInteractionDynamicsByParticle, public ContactDynamicsData + { + public: + ContactForceWithWall(SolidBodyRelationContact* solid_body_contact_relation, Real penalty_strength = 1.0); + virtual ~ContactForceWithWall() {}; + protected: + StdLargeVec& Vol_, & mass_; + StdLargeVec& vel_n_, & dvel_dt_prior_, & contact_force_; + StdVec*> contact_Vol_; + StdVec*>contact_vel_n_, contact_n_; + Real penalty_strength_; + Real impedence_, reference_pressure_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class CorrectConfiguration + * @brief obtain the corrected initial configuration in strong form + */ + class CorrectConfiguration : + public InteractionDynamics, public SolidDataInner + { + public: + CorrectConfiguration(BaseBodyRelationInner* body_inner_relation); + virtual ~CorrectConfiguration() {}; + protected: + StdLargeVec& Vol_; + StdLargeVec& B_; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class ConstrainSolidBodyRegion + * @brief Constrain a solid body part with prescribed motion. + * Note the average values for FSI are prescirbed also. + */ + class ConstrainSolidBodyRegion : + public PartSimpleDynamicsByParticle, public SolidDataSimple + { + public: + ConstrainSolidBodyRegion(SPHBody* body, BodyPartByParticle* body_part); + virtual ~ConstrainSolidBodyRegion() {}; + protected: + StdLargeVec& pos_n_, & pos_0_; + StdLargeVec& n_, & n_0_; + StdLargeVec& vel_n_, & dvel_dt_, & vel_ave_, & dvel_dt_ave_; + virtual Vecd getDisplacement(Vecd& pos_0, Vecd& pos_n) { return pos_n; }; + virtual Vecd getVelocity(Vecd& pos_0, Vecd& pos_n, Vecd& vel_n) { return Vecd(0); }; + virtual Vecd getAcceleration(Vecd& pos_0, Vecd& pos_n, Vecd& dvel_dt) { return Vecd(0); }; + virtual SimTK::Rotation getBodyRotation(Vecd& pos_0, Vecd& pos_n, Vecd& dvel_dt) { return SimTK::Rotation(); } + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class PositionSolidBody + * @brief Moves the body into a defined position in a given time interval - position driven boundary condition + * Note the average values for FSI are prescirbed also. + */ + class PositionSolidBody: + public PartSimpleDynamicsByParticle, public SolidDataSimple + { + public: + PositionSolidBody(SPHBody* body, BodyPartByParticle* body_part, Real start_time, Real end_time, Vecd pos_end_center); + virtual ~PositionSolidBody() {}; + StdLargeVec& GetParticlePos0(){ return pos_0_; }; + StdLargeVec& GetParticlePosN(){ return pos_n_; }; + protected: + StdLargeVec& pos_n_, &pos_0_; + StdLargeVec& vel_n_, &dvel_dt_, &vel_ave_, &dvel_dt_ave_; + Real start_time_, end_time_; + Vecd pos_0_center_, pos_end_center_, translation_; + Vecd getDisplacement(size_t index_i, Real dt); + virtual Vecd getVelocity() { return Vecd(0); }; + virtual Vecd getAcceleration() { return Vecd(0); }; + virtual SimTK::Rotation getBodyRotation() { return SimTK::Rotation(); } + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class PositionScaleSolidBody + * @brief Scales the body in a given time interval - position driven boundary condition + * Note the average values for FSI are prescirbed also. + */ + class PositionScaleSolidBody: + public PartSimpleDynamicsByParticle, public SolidDataSimple + { + public: + PositionScaleSolidBody(SPHBody* body, BodyPartByParticle* body_part, Real start_time, Real end_time, Real end_scale); + virtual ~PositionScaleSolidBody() {}; + StdLargeVec& GetParticlePos0(){ return pos_0_; }; + StdLargeVec& GetParticlePosN(){ return pos_n_; }; + protected: + StdLargeVec& pos_n_, &pos_0_; + StdLargeVec& vel_n_, &dvel_dt_, &vel_ave_, &dvel_dt_ave_; + Real start_time_, end_time_, end_scale_; + Vecd pos_0_center_; + Vecd getDisplacement(size_t index_i, Real dt); + virtual Vecd getVelocity() { return Vecd(0); }; + virtual Vecd getAcceleration() { return Vecd(0); }; + virtual SimTK::Rotation getBodyRotation() { return SimTK::Rotation(); } + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class TranslateSolidBody + * @brief Translates the body in a given time interval -translation driven boundary condition; only moving the body; end position irrelevant; + * Note the average values for FSI are prescirbed also. + */ + class TranslateSolidBody: + public PartSimpleDynamicsByParticle, public SolidDataSimple + { + public: + TranslateSolidBody(SPHBody* body, BodyPartByParticle* body_part, Real start_time, Real end_time, Vecd translation); + virtual ~TranslateSolidBody() {}; + StdLargeVec& GetParticlePos0(){ return pos_0_; }; + StdLargeVec& GetParticlePosN(){ return pos_n_; }; + protected: + StdLargeVec& pos_n_, &pos_0_; + StdLargeVec& vel_n_, &dvel_dt_, &vel_ave_, &dvel_dt_ave_; + Real start_time_, end_time_; + Vecd translation_; + Vecd getDisplacement(size_t index_i, Real dt); + virtual Vecd getVelocity() { return Vecd(0); }; + virtual Vecd getAcceleration() { return Vecd(0); }; + virtual SimTK::Rotation getBodyRotation() { return SimTK::Rotation(); } + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class ConstrainSolidBodyRegionVelocity + * @brief Constrain the velocity of a solid body part. + */ + class ConstrainSolidBodyRegionVelocity : public ConstrainSolidBodyRegion + { + public: + ConstrainSolidBodyRegionVelocity(SPHBody* body, BodyPartByParticle* body_part, + Vecd constrained_direction = Vecd(0)) : + solid_dynamics::ConstrainSolidBodyRegion(body, body_part), + constrain_matrix_(Matd(1.0)) + { + for (int k = 0; k != Dimensions; ++k) + constrain_matrix_[k][k] = constrained_direction[k]; + }; + virtual ~ConstrainSolidBodyRegionVelocity() {}; + protected: + Matd constrain_matrix_; + virtual Vecd getVelocity(Vecd& pos_0, Vecd& pos_n, Vecd& vel_n) + { + return constrain_matrix_ * vel_n; + }; + }; + + /** + * @class SoftConstrainSolidBodyRegion + * @brief Soft the constrain of a solid body part + */ + class SoftConstrainSolidBodyRegion : + public PartInteractionDynamicsByParticleWithUpdate, + public SolidDataInner + { + public: + SoftConstrainSolidBodyRegion(BaseBodyRelationInner* body_inner_relation, BodyPartByParticle* body_part); + virtual ~SoftConstrainSolidBodyRegion() {}; + protected: + StdLargeVec& Vol_; + StdLargeVec& vel_n_, & dvel_dt_, & vel_ave_, & dvel_dt_ave_; + StdLargeVec& vel_temp_, & dvel_dt_temp_; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class ClampConstrainSolidBodyRegion + * @brief Constrain a solid body part with prescribed motion and smoothing to mimic the clamping effect. + */ + class ClampConstrainSolidBodyRegion : public ParticleDynamics + { + public: + ConstrainSolidBodyRegion* constrianing_; + SoftConstrainSolidBodyRegion* softing_; + + ClampConstrainSolidBodyRegion(BaseBodyRelationInner* body_inner_relation, BodyPartByParticle* body_part); + virtual ~ClampConstrainSolidBodyRegion() {}; + + virtual void exec(Real dt = 0.0) override; + virtual void parallel_exec(Real dt = 0.0) override; + }; + + /** + * @class ConstrainSolidBodyMassCenter + * @brief Constrain the mass center of a solid body. + */ + class ConstrainSolidBodyMassCenter : + public ParticleDynamicsSimple, public SolidDataSimple + { + public: + ConstrainSolidBodyMassCenter(SPHBody* body, Vecd constrain_direction = Vecd(1.0)); + virtual ~ConstrainSolidBodyMassCenter() {}; + protected: + virtual void setupDynamics(Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + private: + Real total_mass_; + Matd correction_matrix_; + Vecd velocity_correction_; + StdLargeVec& vel_n_; + BodyMoment* compute_total_momentum_; + }; + + /**@class ImposeExternalForce + * @brief impose external force on a solid body part + * by add extra acceleration + */ + class ImposeExternalForce : + public PartSimpleDynamicsByParticle, public SolidDataSimple + { + public: + ImposeExternalForce(SolidBody* body, SolidBodyPartForSimbody* body_part); + virtual ~ImposeExternalForce() {}; + protected: + StdLargeVec& pos_0_, & vel_n_, & vel_ave_; + /** + * @brief acceleration will be specified by the application + */ + virtual Vecd getAcceleration(Vecd& pos) = 0; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + /** + * @class SpringDamperConstraintParticleWise + * @brief Exerts spring force and damping force in the form of acceleration to each particle. + * The spring force is calculated based on the difference from the particle's initial position. + * The damping force is calculated based on the particle's current velocity. + * Only for 3D applications + */ + class SpringDamperConstraintParticleWise + : public ParticleDynamicsSimple, public SolidDataSimple + { + public: + SpringDamperConstraintParticleWise(SolidBody* body, Vecd stiffness, Real damping_ratio = 0.05); + ~SpringDamperConstraintParticleWise(); + protected: + Real total_mass_; + StdLargeVec& pos_n_,& pos_0_,& vel_n_,& dvel_dt_prior_; + Vecd stiffness_; + Vecd damping_coeff_; // damping component parallel to the spring force component + + virtual void setupDynamics(Real dt = 0.0) override; + virtual Vecd getSpringForce(size_t index_i, Vecd& disp); + virtual Vecd getDampingForce(size_t index_i); + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + /** + * @class AccelerationForBodyPartInBoundingBox + * @brief Adds acceleration to the part of the body that's inside a bounding box + */ + class AccelerationForBodyPartInBoundingBox + : public ParticleDynamicsSimple, public SolidDataSimple + { + public: + AccelerationForBodyPartInBoundingBox(SolidBody* body, BoundingBox* bounding_box, Vecd acceleration); + virtual ~AccelerationForBodyPartInBoundingBox() {}; + protected: + StdLargeVec& pos_n_,& dvel_dt_prior_; + BoundingBox* bounding_box_; + Vecd acceleration_; + virtual void setupDynamics(Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + //---------------------------------------------------------------------- + // for elastic solid dynamics + //---------------------------------------------------------------------- + typedef DataDelegateSimple ElasticSolidDataSimple; + typedef DataDelegateInner ElasticSolidDataInner; + + /** + * @class ElasticDynamicsInitialCondition + * @brief set initial condition for a solid body with different material + * This is a abstract class to be override for case specific initial conditions. + */ + class ElasticDynamicsInitialCondition : + public ParticleDynamicsSimple, public ElasticSolidDataSimple + { + public: + ElasticDynamicsInitialCondition(SolidBody *body); + virtual ~ElasticDynamicsInitialCondition() {}; + protected: + StdLargeVec& pos_n_, & vel_n_; + }; + + /** + * @class UpdateElasticNormalDirection + * @brief update particle normal directions for elastic solid + */ + class UpdateElasticNormalDirection : + public ParticleDynamicsSimple, public ElasticSolidDataSimple + { + public: + explicit UpdateElasticNormalDirection(SolidBody *elastic_body); + virtual ~UpdateElasticNormalDirection() {}; + protected: + StdLargeVec& n_, & n_0_; + StdLargeVec& F_; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class AcousticTimeStepSize + * @brief Computing the acoustic time step size + * computing time step size + */ + class AcousticTimeStepSize : + public ParticleDynamicsReduce, + public ElasticSolidDataSimple + { + public: + explicit AcousticTimeStepSize(SolidBody* body, Real CFL = 0.6); + virtual ~AcousticTimeStepSize() {}; + protected: + Real CFL_; + StdLargeVec& vel_n_, & dvel_dt_; + Real smoothing_length_; + Real ReduceFunction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class DeformationGradientTensorBySummation + * @brief computing deformation gradient tensor by summation + */ + class DeformationGradientTensorBySummation : + public InteractionDynamics, public ElasticSolidDataInner + { + public: + DeformationGradientTensorBySummation(BaseBodyRelationInner* body_inner_relation); + virtual ~DeformationGradientTensorBySummation() {}; + protected: + StdLargeVec& Vol_; + StdLargeVec& pos_n_; + StdLargeVec& B_, & F_; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class BaseElasticRelaxation + * @brief base class for elastic relaxation + */ + class BaseElasticRelaxation + : public ParticleDynamics1Level, public ElasticSolidDataInner + { + public: + BaseElasticRelaxation(BaseBodyRelationInner* body_inner_relation); + virtual ~BaseElasticRelaxation() {}; + protected: + StdLargeVec& Vol_, & rho_n_, & mass_; + StdLargeVec& pos_n_, & vel_n_, & dvel_dt_; + StdLargeVec& B_, & F_, & dF_dt_; + }; + + /** + * @class StressRelaxationFirstHalf + * @brief computing stress relaxation process by verlet time stepping + * This is the first step + */ + class StressRelaxationFirstHalf : public BaseElasticRelaxation + { + public: + StressRelaxationFirstHalf(BaseBodyRelationInner* body_inner_relation); + virtual ~StressRelaxationFirstHalf() {}; + protected: + Real rho0_, inv_rho0_; + StdLargeVec& dvel_dt_prior_, & force_from_fluid_; + StdLargeVec& stress_PK1_; + Real numerical_dissipation_factor_; + Real smoothing_length_; + Real inv_W0_ = 1.0 / body_->particle_adaptation_->getKernel()->W0(Vecd(0)); + + virtual void Initialization(size_t index_i, Real dt = 0.0) override; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class KirchhoffStressRelaxationFirstHalf + * @brief Decompose the stress into particle stress includes isetropic stress + * and the stress due to non-homogeneous material properties. + * The preliminary shear stress is introduced by particle pair to avoid + * sprurious stress and deforamtion. + */ + class KirchhoffStressRelaxationFirstHalf : public StressRelaxationFirstHalf + { + public: + KirchhoffStressRelaxationFirstHalf(BaseBodyRelationInner* body_inner_relation); + virtual ~KirchhoffStressRelaxationFirstHalf() {}; + protected: + StdLargeVec& J_to_minus_2_over_dimension_; + StdLargeVec& stress_on_particle_, & inverse_F_T_; + const Real one_over_dimensions_ = 1.0 / (Real)Dimensions; + + virtual void Initialization(size_t index_i, Real dt = 0.0) override; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class StressRelaxationSecondHalf + * @brief computing stress relaxation process by verlet time stepping + * This is the second step + */ + class StressRelaxationSecondHalf : public BaseElasticRelaxation + { + public: + StressRelaxationSecondHalf(BaseBodyRelationInner* body_inner_relation) : + BaseElasticRelaxation(body_inner_relation) {}; + virtual ~StressRelaxationSecondHalf() {}; + protected: + virtual void Initialization(size_t index_i, Real dt = 0.0) override; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class ConstrainSolidBodyPartBySimBody + * @brief Constrain a solid body part from the motion + * computed from Simbody. + */ + class ConstrainSolidBodyPartBySimBody : public ConstrainSolidBodyRegion + { + public: + ConstrainSolidBodyPartBySimBody(SolidBody* body, + SolidBodyPartForSimbody* body_part, + SimTK::MultibodySystem& MBsystem, + SimTK::MobilizedBody& mobod, + SimTK::Force::DiscreteForces& force_on_bodies, + SimTK::RungeKuttaMersonIntegrator& integ); + virtual ~ConstrainSolidBodyPartBySimBody() {}; + protected: + SimTK::MultibodySystem& MBsystem_; + SimTK::MobilizedBody& mobod_; + SimTK::Force::DiscreteForces& force_on_bodies_; + SimTK::RungeKuttaMersonIntegrator& integ_; + const SimTK::State* simbody_state_; + Vec3d initial_mobod_origin_location_; + + virtual void setupDynamics(Real dt = 0.0) override; + void virtual Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class TotalForceOnSolidBodyPartForSimBody + * @brief Compute the force acting on the solid body part + * for applying to simbody forces latter + */ + class TotalForceOnSolidBodyPartForSimBody : + public PartDynamicsByParticleReduce>, + public SolidDataSimple + { + public: + TotalForceOnSolidBodyPartForSimBody(SolidBody* body, + SolidBodyPartForSimbody* body_part, + SimTK::MultibodySystem& MBsystem, + SimTK::MobilizedBody& mobod, + SimTK::Force::DiscreteForces& force_on_bodies, + SimTK::RungeKuttaMersonIntegrator& integ); + virtual ~TotalForceOnSolidBodyPartForSimBody() {}; + protected: + StdLargeVec& force_from_fluid_, & contact_force_, & pos_n_; + SimTK::MultibodySystem& MBsystem_; + SimTK::MobilizedBody& mobod_; + SimTK::Force::DiscreteForces& force_on_bodies_; + SimTK::RungeKuttaMersonIntegrator& integ_; + const SimTK::State* simbody_state_; + Vec3d current_mobod_origin_location_; + + virtual void SetupReduce() override; + virtual SimTK::SpatialVec ReduceFunction(size_t index_i, Real dt = 0.0) override; + }; + } +} +#endif //SOLID_DYNAMICS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.cpp new file mode 100644 index 0000000000..df022e3ab2 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.cpp @@ -0,0 +1,521 @@ +/** + * @file thin_structure_dynamics.cpp + * @author Dong Wu and Xiangyu Hu + */ + +#include "thin_structure_dynamics.h" +#include "thin_structure_math.h" + +using namespace SimTK; + +namespace SPH +{ + namespace thin_structure_dynamics + { + //=================================================================================================// + ShellDynamicsInitialCondition:: + ShellDynamicsInitialCondition(SolidBody* body) : + ParticleDynamicsSimple(body), + ShellDataSimple(body), + n_0_(particles_->n_0_), n_(particles_->n_), pseudo_n_(particles_->pseudo_n_), + pos_0_(particles_->pos_0_), + transformation_matrix_(particles_->transformation_matrix_) {} + //=================================================================================================// + ShellAcousticTimeStepSize::ShellAcousticTimeStepSize(SolidBody* body) : + ParticleDynamicsReduce(body), + ShellDataSimple(body), + vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), + angular_vel_(particles_->angular_vel_), dangular_vel_dt_(particles_->dangular_vel_dt_), + shell_thickness_(particles_->shell_thickness_) + { + smoothing_length_ = particle_adaptation_->ReferenceSmoothingLength(); + initial_reference_ = DBL_MAX; + rho0_ = material_->ReferenceDensity(); + E0_ = material_->YoungsModulus(); + nu_ = material_->PoissonRatio(); + } + //=================================================================================================// + Real ShellAcousticTimeStepSize::ReduceFunction(size_t index_i, Real dt) + { + // Since the particle does not change its configuration in pressure relaxation step, + // I chose a time-step size according to Eulerian method. + Real sound_speed = material_->ReferenceSoundSpeed(); + Real time_setp_0 = 0.6 * SMIN(sqrt(smoothing_length_ / (dvel_dt_[index_i].norm() + TinyReal)), + smoothing_length_ / (sound_speed + vel_n_[index_i].norm())); + Real time_setp_1 = 0.6 * SMIN(sqrt(1.0 / (dangular_vel_dt_[index_i].norm() + TinyReal)), + 1.0 / (angular_vel_[index_i].norm() + TinyReal)); + Real time_setp_2 = smoothing_length_ * sqrt(rho0_ * (1.0 - nu_ * nu_) / E0_ / + (2.0 + (Pi * Pi / 12.0) * (1.0 - nu_) * (1.0 + 1.5 * powerN(smoothing_length_ / shell_thickness_[index_i], 2))) + ); + return SMIN(time_setp_0, time_setp_1, time_setp_2); + } + //=================================================================================================// + ShellCorrectConfiguration:: + ShellCorrectConfiguration(BaseBodyRelationInner* body_inner_relation) : + InteractionDynamics(body_inner_relation->sph_body_), + ShellDataInner(body_inner_relation), + Vol_(particles_->Vol_), B_(particles_->B_), + n_0_(particles_->n_0_), transformation_matrix_(particles_->transformation_matrix_) {} + //=================================================================================================// + void ShellCorrectConfiguration::Interaction(size_t index_i, Real dt) + { + /** A small number is added to diagonal to avoid dividing by zero. */ + Matd global_configuration(Eps); + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + + Vecd gradw_ij = inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]; + Vecd r_ji = -inner_neighborhood.r_ij_[n] * inner_neighborhood.e_ij_[n]; + global_configuration += Vol_[index_j] * SimTK::outer(r_ji, gradw_ij); + } + Matd local_configuration = + transformation_matrix_[index_i] * global_configuration * (~transformation_matrix_[index_i]); + /** correction matrix is obtained from local configuration. */ + B_[index_i] = SimTK::inverse(local_configuration) * reduced_unit_matrix; + } + //=================================================================================================// + ShellDeformationGradientTensor:: + ShellDeformationGradientTensor(BaseBodyRelationInner* body_inner_relation) : + InteractionDynamics(body_inner_relation->sph_body_), + ShellDataInner(body_inner_relation), + Vol_(particles_->Vol_), pos_n_(particles_->pos_n_), + pseudo_n_(particles_->pseudo_n_), n_0_(particles_->n_0_), + B_(particles_->B_), F_(particles_->F_), F_bending_(particles_->F_bending_), + transformation_matrix_(particles_->transformation_matrix_) {} + //=================================================================================================// + void ShellDeformationGradientTensor::Interaction(size_t index_i, Real dt) + { + Vecd& pseudo_n_i = pseudo_n_[index_i]; + Vecd& pos_n_i = pos_n_[index_i]; + Matd& transformation_matrix_i = transformation_matrix_[index_i]; + + Matd deformation_part_one(0.0); + Matd deformation_part_two(0.0); + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + Vecd gradw_ij = inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]; + deformation_part_one -= Vol_[index_j] * SimTK::outer((pos_n_i - pos_n_[index_j]), gradw_ij); + deformation_part_two -= Vol_[index_j] * SimTK::outer( + ((pseudo_n_i - n_0_[index_i]) - (pseudo_n_[index_j] - n_0_[index_j])), gradw_ij); + } + F_[index_i] = transformation_matrix_i * deformation_part_one * (~transformation_matrix_i) * B_[index_i]; + F_[index_i].col(Dimensions - 1) = transformation_matrix_i * pseudo_n_[index_i]; + F_bending_[index_i] = transformation_matrix_i * deformation_part_two * (~transformation_matrix_i) * B_[index_i]; + } + //=================================================================================================// + BaseShellRelaxation::BaseShellRelaxation(BaseBodyRelationInner* body_inner_relation) : + ParticleDynamics1Level(body_inner_relation->sph_body_), + ShellDataInner(body_inner_relation), Vol_(particles_->Vol_), + rho_n_(particles_->rho_n_), mass_(particles_->mass_), + shell_thickness_(particles_->shell_thickness_), + pos_n_(particles_->pos_n_), vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), + dvel_dt_prior_(particles_->dvel_dt_prior_), force_from_fluid_(particles_->force_from_fluid_), + n_0_(particles_->n_0_), pseudo_n_(particles_->pseudo_n_), + dpseudo_n_dt_(particles_->dpseudo_n_dt_), dpseudo_n_d2t_(particles_->dpseudo_n_d2t_), + rotation_(particles_->rotation_), angular_vel_(particles_->angular_vel_), + dangular_vel_dt_(particles_->dangular_vel_dt_), + B_(particles_->B_), F_(particles_->F_), dF_dt_(particles_->dF_dt_), + F_bending_(particles_->F_bending_), dF_bending_dt_(particles_->dF_bending_dt_), + transformation_matrix_(particles_->transformation_matrix_) {} + //=================================================================================================// + ShellStressRelaxationFirstHalf:: + ShellStressRelaxationFirstHalf(BaseBodyRelationInner* body_inner_relation, + int number_of_gaussian_points) : BaseShellRelaxation(body_inner_relation), + stress_PK1_(particles_->stress_PK1_), + global_stress_(particles_->global_stress_), + global_moment_(particles_->global_moment_), + global_shear_stress_(particles_->global_shear_stress_), + n_(particles_->n_), + number_of_gaussian_points_(number_of_gaussian_points) + { + rho0_ = material_->ReferenceDensity(); + inv_rho0_ = 1.0 / rho0_; + smoothing_length_ = particle_adaptation_->ReferenceSmoothingLength(); + + /** Note that, only three-point and five-point Gaussian quadrature rules are defined. */ + switch (number_of_gaussian_points) + { + case 5: + gaussian_point_ = five_gaussian_points_; + gaussian_weight_ = five_gaussian_weights_; + break; + default: + gaussian_point_ = three_gaussian_points_; + gaussian_weight_ = three_gaussian_weights_; + } + } + //=================================================================================================// + void ShellStressRelaxationFirstHalf::Initialization(size_t index_i, Real dt) + { + // Note that F_[index_i], F_bending_[index_i], dF_dt_[index_i], dF_bending_dt_[index_i] + // and rotation_[index_i], angular_vel_[index_i], dangular_vel_dt_[index_i] + // are defined in local coordinates, while others in global coordinates. + pos_n_[index_i] += vel_n_[index_i] * dt * 0.5; + rotation_[index_i] += angular_vel_[index_i] * dt * 0.5; + pseudo_n_[index_i] += dpseudo_n_dt_[index_i] * dt * 0.5; + + F_[index_i] += dF_dt_[index_i] * dt * 0.5; + F_bending_[index_i] += dF_bending_dt_[index_i] * dt * 0.5; + rho_n_[index_i] = rho0_ / det(F_[index_i]); + + /** Calculate the current normal direction of mid-surface. */ + Matd F_i = F_[index_i]; + F_i.col(Dimensions - 1) = Vecd(0.0); + n_[index_i] = (~transformation_matrix_[index_i]) * getNormalFromDeformationGradientTensor(F_i); + /** Get transformation matrix from global coordinates to current local coordinates. */ + Matd current_transformation_matrix = getTransformationMatrix(n_[index_i]); + + /** Initialize the local stress to 0. */ + Matd resultant_stress(0); + Matd resultant_moment(0); + Vecd resultant_shear_stress(0); + for (int i = 0; i != number_of_gaussian_points_; ++i) + { + Matd F_gaussian_point = F_[index_i] + gaussian_point_[i] * F_bending_[index_i] * shell_thickness_[index_i] * 0.5; + Matd dF_gaussian_point_dt = dF_dt_[index_i] + gaussian_point_[i] * dF_bending_dt_[index_i] * shell_thickness_[index_i] * 0.5; + Matd stress_PK2_gaussian_point = material_->ConstitutiveRelation(F_gaussian_point, index_i) + + material_->NumericalDampingRightCauchy(F_gaussian_point, dF_gaussian_point_dt, smoothing_length_, index_i); + + /** Get the mid-surface stress to output the von-Mises equivalent stress. */ + if (i == 0) stress_PK1_[index_i] = F_gaussian_point * stress_PK2_gaussian_point; + + /** Get Cauchy stress. */ + Matd cauchy_stress = current_transformation_matrix * (~transformation_matrix_[index_i]) + * F_gaussian_point * stress_PK2_gaussian_point * (~F_gaussian_point) + * transformation_matrix_[index_i] * (~current_transformation_matrix) / det(F_gaussian_point); + + /** Impose modeling assumptions. */ + cauchy_stress.col(Dimensions - 1) *= shear_correction_factor_; + cauchy_stress.row(Dimensions - 1) *= shear_correction_factor_; + cauchy_stress[Dimensions - 1][Dimensions - 1] = 0.0; + + stress_PK2_gaussian_point = det(F_gaussian_point) * SimTK::inverse(F_gaussian_point) + * transformation_matrix_[index_i] * (~current_transformation_matrix) * cauchy_stress + * current_transformation_matrix * (~transformation_matrix_[index_i]) + * (~SimTK::inverse(F_gaussian_point)); + Vecd shear_stress_PK2_gaussian_point = -stress_PK2_gaussian_point.col(Dimensions - 1); + Matd moment_PK2_gaussian_point = stress_PK2_gaussian_point * gaussian_point_[i] * shell_thickness_[index_i] * 0.5; + + resultant_stress += + 0.5 * shell_thickness_[index_i] * gaussian_weight_[i] * F_gaussian_point * stress_PK2_gaussian_point; + resultant_moment += + 0.5 * shell_thickness_[index_i] * gaussian_weight_[i] * F_gaussian_point * moment_PK2_gaussian_point; + resultant_shear_stress += + 0.5 * shell_thickness_[index_i] * gaussian_weight_[i] * F_gaussian_point * shear_stress_PK2_gaussian_point; + } + /** Only one (for 2D) or two (for 3D) angular momentum equations left. */ + resultant_moment.col(Dimensions - 1) = Vecd(0); + resultant_moment.row(Dimensions - 1) = ~Vecd(0); + resultant_shear_stress[Dimensions - 1] = 0.0; + + /** stress and moment in global coordinates for pair interaction */ + global_stress_[index_i] = + (~transformation_matrix_[index_i]) * resultant_stress * transformation_matrix_[index_i]; + global_moment_[index_i] = + (~transformation_matrix_[index_i]) * resultant_moment * transformation_matrix_[index_i]; + global_shear_stress_[index_i] = (~transformation_matrix_[index_i]) * resultant_shear_stress; + } + //=================================================================================================// + void ShellStressRelaxationFirstHalf::Interaction(size_t index_i, Real dt) + { + Vecd& global_shear_stress_i = global_shear_stress_[index_i]; + Matd& global_stress_i = global_stress_[index_i]; + Matd& global_moment_i = global_moment_[index_i]; + + Vecd acceleration(0.0); + Vecd pseudo_normal_acceleration = global_shear_stress_i; + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + acceleration += (global_stress_i + global_stress_[index_j]) + * inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n] * Vol_[index_j]; + pseudo_normal_acceleration += (global_moment_i + global_moment_[index_j]) + * inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n] * Vol_[index_j]; + } + /** including external force (body force) and force from fluid */ + dvel_dt_[index_i] = acceleration * inv_rho0_ / shell_thickness_[index_i] + + dvel_dt_prior_[index_i] + force_from_fluid_[index_i] / mass_[index_i]; + dpseudo_n_d2t_[index_i] = pseudo_normal_acceleration * inv_rho0_ + * 12.0 / powerN(shell_thickness_[index_i], 3); + + /** the relation between pseudo-normal and rotations */ + Vecd local_dpseudo_n_d2t = transformation_matrix_[index_i] * dpseudo_n_d2t_[index_i]; + dangular_vel_dt_[index_i] = getRotationFromPseudoNormalForSmallDeformation + (local_dpseudo_n_d2t, rotation_[index_i], angular_vel_[index_i], dt); + } + //=================================================================================================// + void ShellStressRelaxationFirstHalf::Update(size_t index_i, Real dt) + { + vel_n_[index_i] += dvel_dt_[index_i] * dt; + angular_vel_[index_i] += dangular_vel_dt_[index_i] * dt; + } + //=================================================================================================// + void ShellStressRelaxationSecondHalf::Initialization(size_t index_i, Real dt) + { + pos_n_[index_i] += vel_n_[index_i] * dt * 0.5; + rotation_[index_i] += angular_vel_[index_i] * dt * 0.5; + dpseudo_n_dt_[index_i] = (~transformation_matrix_[index_i]) + * getVectorChangeRateAfterThinStructureRotation(local_pseudo_n_0, rotation_[index_i], angular_vel_[index_i]); + pseudo_n_[index_i] += dpseudo_n_dt_[index_i] * dt * 0.5; + } + //=================================================================================================// + void ShellStressRelaxationSecondHalf::Interaction(size_t index_i, Real dt) + { + Vecd& vel_n_i = vel_n_[index_i]; + Vecd& dpseudo_n_dt_i = dpseudo_n_dt_[index_i]; + Matd& transformation_matrix_i = transformation_matrix_[index_i]; + + Matd deformation_gradient_change_rate_part_one(0.0); + Matd deformation_gradient_change_rate_part_two(0.0); + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + + Vecd gradw_ij = inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n]; + deformation_gradient_change_rate_part_one -= Vol_[index_j] * SimTK::outer( + (vel_n_i - vel_n_[index_j]), gradw_ij); + deformation_gradient_change_rate_part_two -= Vol_[index_j] * SimTK::outer( + (dpseudo_n_dt_i - dpseudo_n_dt_[index_j]), gradw_ij); + } + dF_dt_[index_i] = transformation_matrix_i * deformation_gradient_change_rate_part_one + * (~transformation_matrix_i) * B_[index_i]; + dF_dt_[index_i].col(Dimensions - 1) = transformation_matrix_i * dpseudo_n_dt_[index_i]; + dF_bending_dt_[index_i] = transformation_matrix_i * deformation_gradient_change_rate_part_two + * (~transformation_matrix_i) * B_[index_i]; + } + //=================================================================================================// + void ShellStressRelaxationSecondHalf::Update(size_t index_i, Real dt) + { + F_[index_i] += dF_dt_[index_i] * dt * 0.5; + F_bending_[index_i] += dF_bending_dt_[index_i] * dt * 0.5; + } + //=================================================================================================// + ConstrainShellBodyRegion:: + ConstrainShellBodyRegion(SolidBody* body, BodyPartByParticle* body_part) : + PartSimpleDynamicsByParticle(body, body_part), ShellDataSimple(body), + pos_n_(particles_->pos_n_), pos_0_(particles_->pos_0_), n_(particles_->n_), + vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), + vel_ave_(particles_->vel_ave_), dvel_dt_ave_(particles_->dvel_dt_ave_), + rotation_(particles_->rotation_), angular_vel_(particles_->angular_vel_), + dangular_vel_dt_(particles_->dangular_vel_dt_), + pseudo_n_(particles_->pseudo_n_), dpseudo_n_dt_(particles_->dpseudo_n_dt_) + { + } + //=================================================================================================// + void ConstrainShellBodyRegion::Update(size_t index_i, Real dt) + { + Vecd pos_0 = pos_0_[index_i]; + Vecd pos_n = pos_n_[index_i]; + Vecd vel_n = vel_n_[index_i]; + Vecd dvel_dt = dvel_dt_[index_i]; + Vecd rotation_0(0.0); + Vecd angular_vel(0.0); + Vecd dangular_vel_dt(0.0); + Vecd dpseudo_normal_dt(0.0); + + pos_n_[index_i] = getDisplacement(pos_0, pos_n); + vel_n_[index_i] = getVelocity(pos_0, pos_n, vel_n); + dvel_dt_[index_i] = GetAcceleration(pos_0, pos_n, dvel_dt); + rotation_[index_i] = GetRotationAngle(pos_0, pos_n, rotation_0); + angular_vel_[index_i] = GetAngularVelocity(pos_0, pos_n, angular_vel); + dangular_vel_dt_[index_i] = GetAngularAcceleration(pos_0, pos_n, dangular_vel_dt); + pseudo_n_[index_i] = GetPseudoNormal(pos_0, pos_n, local_pseudo_n_0); + dpseudo_n_dt_[index_i] = GetPseudoNormalChangeRate(pos_0, pos_n, dpseudo_normal_dt); + + /** the average values are prescirbed also. */ + vel_ave_[index_i] = vel_n_[index_i]; + dvel_dt_ave_[index_i] = dvel_dt_[index_i]; + } + //=================================================================================================// + FixedFreeRotateShellBoundary:: + FixedFreeRotateShellBoundary(BaseBodyRelationInner* body_inner_relation, + BodyPartByParticle* body_part, Vecd constrained_direction) : + PartInteractionDynamicsByParticle1Level(body_inner_relation->sph_body_, body_part), + ShellDataInner(body_inner_relation), + W0_(particle_adaptation_->getKernel()->W0(Vecd(0))), + constrain_matrix_(Matd(0)), recover_matrix_(Matd(1.0)), + Vol_(particles_->Vol_), vel_n_(particles_->vel_n_), + angular_vel_(particles_->angular_vel_), + vel_n_temp_(*particles_->createAVariable("TemporaryVelocity")), + angular_vel_temp_(*particles_->createAVariable("TemporaryAngularVelocity")) + { + for (int k = 0; k != Dimensions; ++k) + { + constrain_matrix_[k][k] = constrained_direction[k]; + recover_matrix_[k][k] = 1.0 - constrain_matrix_[k][k]; + } + } + //=================================================================================================// + void FixedFreeRotateShellBoundary::Initialization(size_t index_i, Real dt) + { + vel_n_[index_i] = constrain_matrix_ * vel_n_[index_i]; + angular_vel_[index_i] = Vecd(0); + } + //=================================================================================================// + void FixedFreeRotateShellBoundary::Interaction(size_t index_i, Real dt) + { + Real ttl_weight = W0_ * Vol_[index_i]; + Vecd vel_i = recover_matrix_ * vel_n_[index_i] * ttl_weight; + Real ttl_weight_angular = W0_* Vol_[index_i]; + Vecd angular_vel_i = angular_vel_[index_i] * ttl_weight_angular; + + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + Real weight_j = inner_neighborhood.W_ij_[n] * Vol_[index_j]; + + ttl_weight += weight_j; + vel_i += recover_matrix_ * vel_n_[index_j] * weight_j; + //exclude boundary particles to achieve extrapolation + if (angular_vel_[index_j].norm() >= Eps) { + angular_vel_i += angular_vel_[index_j] * weight_j; + ttl_weight_angular += weight_j; + } + } + + vel_n_temp_[index_i] = vel_i / ttl_weight; + angular_vel_temp_[index_i] = angular_vel_i / ttl_weight_angular; + } + //=================================================================================================// + void FixedFreeRotateShellBoundary::Update(size_t index_i, Real dt) + { + vel_n_[index_i] += vel_n_temp_[index_i]; + angular_vel_[index_i] = angular_vel_temp_[index_i]; + } + //=================================================================================================// + ClampConstrainShellBodyRegion:: + ClampConstrainShellBodyRegion(BaseBodyRelationInner* body_inner_relation, BodyPartByParticle* body_part) : + PartInteractionDynamicsByParticle1Level(body_inner_relation->sph_body_, body_part), + ShellDataInner(body_inner_relation), + Vol_(particles_->Vol_), vel_n_(particles_->vel_n_), + angular_vel_(particles_->angular_vel_), + vel_n_temp_(*particles_->createAVariable("TemporaryVelocity")), + angular_vel_temp_(*particles_->createAVariable("TemporaryAngularVelocity")) {} + //=================================================================================================// + void ClampConstrainShellBodyRegion::Initialization(size_t index_i, Real dt) + { + vel_n_[index_i] = Vecd(0); + angular_vel_[index_i] = Vecd(0); + } + //=================================================================================================// + void ClampConstrainShellBodyRegion::Interaction(size_t index_i, Real dt) + { + Real ttl_weight(Eps); + Vecd vel_i = vel_n_[index_i]; + Vecd angular_vel_i = angular_vel_[index_i]; + + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + Real weight_j = inner_neighborhood.W_ij_[n] * Vol_[index_j]; + + ttl_weight += weight_j; + vel_i += vel_n_[index_j] * weight_j; + angular_vel_i += angular_vel_[index_j] * weight_j; + } + + vel_n_temp_[index_i] = vel_i / ttl_weight; + angular_vel_temp_[index_i] = angular_vel_i / ttl_weight; + } + //=================================================================================================// + void ClampConstrainShellBodyRegion::Update(size_t index_i, Real dt) + { + vel_n_[index_i] = vel_n_temp_[index_i]; + angular_vel_[index_i] = angular_vel_temp_[index_i]; + } + //=================================================================================================// + ConstrainShellBodyRegionInAxisDirection:: + ConstrainShellBodyRegionInAxisDirection(SolidBody* body, BodyPartByParticle* body_part, int axis_direction) : + PartSimpleDynamicsByParticle(body, body_part), ShellDataSimple(body), + axis_(axis_direction), pos_n_(particles_->pos_n_), pos_0_(particles_->pos_0_), + vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), + vel_ave_(particles_->vel_ave_), dvel_dt_ave_(particles_->dvel_dt_ave_), + rotation_(particles_->rotation_), angular_vel_(particles_->angular_vel_), + dangular_vel_dt_(particles_->dangular_vel_dt_) {} + //=================================================================================================// + void ConstrainShellBodyRegionInAxisDirection::Update(size_t index_i, Real dt) + { + vel_n_[index_i][axis_] = 0.0; + vel_n_[index_i][2] = 0.0; + dvel_dt_[index_i][axis_] = 0.0; + dvel_dt_[index_i][2] = 0.0; + + angular_vel_[index_i][1 - axis_] = 0.0; + dangular_vel_dt_[index_i][1 - axis_] = 0.0; + + /** the average values are prescirbed also. */ + vel_ave_[index_i] = vel_n_[index_i]; + dvel_dt_ave_[index_i] = dvel_dt_[index_i]; + } + //=================================================================================================// + DistributingAPointForceToShell:: + DistributingAPointForceToShell(SolidBody* body, BodyPartByParticle* body_part, + Vecd point_force, Vecd reference_position, + Real time_to_smallest_h_ratio, Real time_to_full_external_force, + Real particle_spacing_ref, Real h_spacing_ratio) + : PartSimpleDynamicsByParticle(body, body_part), ShellDataSimple(body), + point_force_(point_force), reference_position_(reference_position), + time_to_smallest_h_ratio_(time_to_smallest_h_ratio), + time_to_full_external_force_(time_to_full_external_force), + particle_spacing_ref_(particle_spacing_ref), h_spacing_ratio_(h_spacing_ratio), + pos_0_(particles_->pos_0_), dvel_dt_prior_(particles_->dvel_dt_prior_), + Vol_(particles_->Vol_), mass_(particles_->mass_), shell_thickness_(particles_->shell_thickness_), + weight_(*particles_->createAVariable("Weight")) + { + } + //=================================================================================================// + void DistributingAPointForceToShell::getWeight() + { + sum_of_weight_ = 0.0; + Real current_time = GlobalStaticVariables::physical_time_; + Real h_spacing_ratio_time = current_time * (0.6 - h_spacing_ratio_) + / time_to_smallest_h_ratio_ + h_spacing_ratio_; + Real h_spacing_ratio = current_time < time_to_smallest_h_ratio_ ? h_spacing_ratio_time : 0.6; + + Real smooth_length = h_spacing_ratio * particle_spacing_ref_; + Real cutoff_radius_sqr = powerN(2.0 * smooth_length, 2); + for (size_t i = 0; i < particles_->total_real_particles_; ++i) + { + weight_[i] = 0.0; + Vecd displacement = reference_position_ - pos_0_[i]; + if (displacement.normSqr() <= cutoff_radius_sqr) + { + Kernel* kernel_ = body_->particle_adaptation_->getKernel(); + if (Dimensions == 2) + { + weight_[i] = 3.0 / 4.0 / smooth_length + * kernel_->W_2D(displacement.norm() / smooth_length) * Vol_[i]; + } + else + { + weight_[i] = 7.0 / (4.0 * Pi) / smooth_length / smooth_length + * kernel_->W_3D(displacement.norm() / smooth_length) * Vol_[i]; + } + sum_of_weight_ += weight_[i]; + } + } + } + //=================================================================================================// + void DistributingAPointForceToShell::getForce() + { + Real current_time = GlobalStaticVariables::physical_time_; + point_force_time_ = current_time < time_to_full_external_force_ ? + current_time * point_force_ / time_to_full_external_force_ : point_force_; + } + //=================================================================================================// + void DistributingAPointForceToShell::Update(size_t index_i, Real dt) + { + Vecd force = weight_[index_i] / (sum_of_weight_ + TinyReal) * point_force_time_; + dvel_dt_prior_[index_i] = force / mass_[index_i] / shell_thickness_[index_i]; + } + //=================================================================================================// + } +} diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.h new file mode 100644 index 0000000000..b3c28322a1 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.h @@ -0,0 +1,302 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file thin_structure_dynamics.h +* @brief Here, we define the algorithm classes for thin structure dynamics. +* @details We consider here a weakly compressible solids. +* @author Dong Wu, Chi Zhang and Xiangyu Hu +*/ + +#ifndef THIN_STRUCTURE_DYNAMICS_H +#define THIN_STRUCTURE_DYNAMICS_H + + +#include "all_particle_dynamics.h" +#include "elastic_solid.h" +#include "weakly_compressible_fluid.h" +#include "base_kernel.h" +#include "all_fluid_dynamics.h" + +namespace SPH +{ + namespace thin_structure_dynamics + { + typedef DataDelegateSimple ShellDataSimple; + typedef DataDelegateInner ShellDataInner; + + /** + * @class ShellDynamicsInitialCondition + * @brief set initial condition for shell particles + * This is a abstract class to be override for case specific initial conditions. + */ + class ShellDynamicsInitialCondition : + public ParticleDynamicsSimple, public ShellDataSimple + { + public: + ShellDynamicsInitialCondition(SolidBody *body); + virtual ~ShellDynamicsInitialCondition() {}; + protected: + StdLargeVec& n_0_, &n_, &pseudo_n_, &pos_0_; + StdLargeVec& transformation_matrix_; + }; + + /** + * @class ShellAcousticTimeStepSize + * @brief Computing the acoustic time step size for shell + */ + class ShellAcousticTimeStepSize : + public ParticleDynamicsReduce, + public ShellDataSimple + { + public: + explicit ShellAcousticTimeStepSize(SolidBody* body); + virtual ~ShellAcousticTimeStepSize() {}; + protected: + StdLargeVec& vel_n_, &dvel_dt_, &angular_vel_, &dangular_vel_dt_; + StdLargeVec& shell_thickness_; + Real rho0_, physical_viscosity_, E0_, nu_; + Real smoothing_length_; + Real ReduceFunction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class ShellCorrectConfiguration + * @brief obtain the corrected initial configuration in strong form + */ + class ShellCorrectConfiguration : + public InteractionDynamics, public ShellDataInner + { + public: + ShellCorrectConfiguration(BaseBodyRelationInner* body_inner_relation); + virtual ~ShellCorrectConfiguration() {}; + protected: + StdLargeVec& Vol_; + StdLargeVec& B_; + StdLargeVec& n_0_; + StdLargeVec& transformation_matrix_; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class ShellDeformationGradientTensor + * @brief computing deformation gradient tensor for shell + */ + class ShellDeformationGradientTensor : + public InteractionDynamics, public ShellDataInner + { + public: + ShellDeformationGradientTensor(BaseBodyRelationInner* body_inner_relation); + virtual ~ShellDeformationGradientTensor() {}; + protected: + StdLargeVec& Vol_; + StdLargeVec& pos_n_, &pseudo_n_, &n_0_; + StdLargeVec& B_, &F_, &F_bending_; + StdLargeVec& transformation_matrix_; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class BaseShellRelaxation + * @brief abstract class for preparing shell relaxation + */ + class BaseShellRelaxation : public ParticleDynamics1Level, public ShellDataInner + { + public: + BaseShellRelaxation(BaseBodyRelationInner* body_inner_relation); + virtual ~BaseShellRelaxation() {}; + protected: + StdLargeVec& Vol_, & rho_n_, & mass_, & shell_thickness_; + StdLargeVec& pos_n_, & vel_n_, & dvel_dt_, & dvel_dt_prior_, & force_from_fluid_; + StdLargeVec& n_0_, & pseudo_n_, & dpseudo_n_dt_, & dpseudo_n_d2t_, & rotation_, + & angular_vel_, dangular_vel_dt_; + StdLargeVec& B_, & F_, & dF_dt_, & F_bending_, & dF_bending_dt_; + StdLargeVec& transformation_matrix_; + }; + + /** + * @class ShellStressRelaxationFirstHalf + * @brief computing stress relaxation process by verlet time stepping + * This is the first step + */ + class ShellStressRelaxationFirstHalf : public BaseShellRelaxation + { + public: + ShellStressRelaxationFirstHalf(BaseBodyRelationInner* body_inner_relation, + int number_of_gaussian_points = 3); + virtual ~ShellStressRelaxationFirstHalf() {}; + protected: + Real rho0_, inv_rho0_; + StdLargeVec& stress_PK1_, & global_stress_, & global_moment_; + StdLargeVec& global_shear_stress_, & n_; + Real smoothing_length_; + + const Real shear_correction_factor_ = 5.0/6.0; + const StdVec three_gaussian_points_ = { 0.0, 0.77459667, -0.77459667 }; + const StdVec three_gaussian_weights_ = { 8.0 / 9.0, 5.0 / 9.0, 5.0 / 9.0 }; + const StdVec five_gaussian_points_ = { 0.0, 0.538469, -0.538469, 0.90618, -0.90618 }; + const StdVec five_gaussian_weights_ = { 0.568889, 0.478629, 0.478629, 0.236927, 0.236927 }; + int number_of_gaussian_points_; + StdVec gaussian_point_; + StdVec gaussian_weight_; + + virtual void Initialization(size_t index_i, Real dt = 0.0) override; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class ShellStressRelaxationSecondHalf + * @brief computing stress relaxation process by verlet time stepping + * This is the second step + */ + class ShellStressRelaxationSecondHalf : public BaseShellRelaxation + { + public: + ShellStressRelaxationSecondHalf(BaseBodyRelationInner* body_inner_relation) + : BaseShellRelaxation(body_inner_relation) {}; + virtual ~ShellStressRelaxationSecondHalf() {}; + protected: + virtual void Initialization(size_t index_i, Real dt = 0.0) override; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /**@class ConstrainShellBodyRegion + * @brief Fix the position and angle of a shell body part. + * Note that the average values for FSI are prescirbed also. + */ + class ConstrainShellBodyRegion : + public PartSimpleDynamicsByParticle, public ShellDataSimple + { + public: + ConstrainShellBodyRegion(SolidBody* body, BodyPartByParticle* body_part); + virtual ~ConstrainShellBodyRegion() {}; + protected: + StdLargeVec& pos_n_, &pos_0_; + StdLargeVec& n_; + StdLargeVec& vel_n_, &dvel_dt_, &vel_ave_, &dvel_dt_ave_; + StdLargeVec& rotation_, &angular_vel_, &dangular_vel_dt_; + StdLargeVec& pseudo_n_, &dpseudo_n_dt_; + virtual Vecd getDisplacement(const Vecd& pos_0, const Vecd& pos_n) { return pos_0; }; + virtual Vecd getVelocity(const Vecd& pos_0, const Vecd& pos_n, const Vecd& vel_n) { return Vecd(0); }; + virtual Vecd GetAcceleration(const Vecd& pos_0, const Vecd& pos_n, const Vecd& dvel_dt) { return Vecd(0); }; + virtual Vecd GetRotationAngle(const Vecd& pos_0, const Vecd& pos_n, const Vecd& rotation_angles_0_) { return rotation_angles_0_; }; + virtual Vecd GetAngularVelocity(const Vecd& pos_0, const Vecd& pos_n, const Vecd& angular_vel_) { return Vecd(0); }; + virtual Vecd GetAngularAcceleration(const Vecd& pos_0, const Vecd& pos_n, const Vecd& dangular_vel_dt_) { return Vecd(0); }; + virtual Vecd GetPseudoNormal(const Vecd& pos_0, const Vecd& pos_n, const Vecd& n_0) { return n_0; }; + virtual Vecd GetPseudoNormalChangeRate(const Vecd& pos_0, const Vecd& pos_n, const Vecd& dpseudo_normal_dt_) { return Vecd(0); }; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class FixedFreeRotateShellBoundary + * @brief Soft the constraint of a solid body part + */ + class FixedFreeRotateShellBoundary : + public PartInteractionDynamicsByParticle1Level, + public ShellDataInner + { + public: + FixedFreeRotateShellBoundary(BaseBodyRelationInner* body_inner_relation, + BodyPartByParticle* body_part, Vecd constrained_direction = Vecd(0)); + virtual ~FixedFreeRotateShellBoundary() {}; + protected: + Real W0_; + Matd constrain_matrix_, recover_matrix_; + StdLargeVec& Vol_; + StdLargeVec& vel_n_, & angular_vel_, &vel_n_temp_, &angular_vel_temp_; + + virtual void Initialization(size_t index_i, Real dt = 0.0) override; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class ClampConstrainShellBodyRegion + * @brief The clamped constrain of a shell body part + */ + class ClampConstrainShellBodyRegion : + public PartInteractionDynamicsByParticle1Level, + public ShellDataInner + { + public: + ClampConstrainShellBodyRegion(BaseBodyRelationInner* body_inner_relation, BodyPartByParticle* body_part); + virtual ~ClampConstrainShellBodyRegion() {}; + protected: + StdLargeVec& Vol_; + StdLargeVec& vel_n_, &angular_vel_, & vel_n_temp_, & angular_vel_temp_; + + virtual void Initialization(size_t index_i, Real dt = 0.0) override; + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /**@class ConstrainShellBodyRegionInAxisDirection + * @brief The boundary conditions are denoted by SS1 according to the references. + * The axis_direction must be 0 or 1. + * Note that the average values for FSI are prescirbed also. + */ + class ConstrainShellBodyRegionInAxisDirection : + public PartSimpleDynamicsByParticle, public ShellDataSimple + { + public: + ConstrainShellBodyRegionInAxisDirection(SolidBody* body, BodyPartByParticle* body_part, int axis_direction); + virtual ~ConstrainShellBodyRegionInAxisDirection() {}; + protected: + const int axis_; /**< the axis direction for bounding*/ + StdLargeVec& pos_n_, &pos_0_; + StdLargeVec& vel_n_, &dvel_dt_, &vel_ave_, &dvel_dt_ave_; + StdLargeVec& rotation_, &angular_vel_, &dangular_vel_dt_; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class DistributingAPointForceToShell + * @brief Distribute a point force to its contact shell bodies. + */ + class DistributingAPointForceToShell : + public PartSimpleDynamicsByParticle, public ShellDataSimple + { + protected: + Vecd point_force_, point_force_time_; + Vecd reference_position_; + Real time_to_smallest_h_ratio_, time_to_full_external_force_; + Real particle_spacing_ref_, h_spacing_ratio_; + Real sum_of_weight_; + StdLargeVec& pos_0_, &dvel_dt_prior_; + StdLargeVec& Vol_, &mass_, &shell_thickness_; + StdLargeVec& weight_; + public: + DistributingAPointForceToShell(SolidBody* body, BodyPartByParticle* body_part, + Vecd point_force, Vecd reference_position, + Real time_to_smallest_h_ratio, Real time_to_full_external_force, + Real particle_spacing_ref, Real h_spacing_ratio = 1.3); + virtual ~DistributingAPointForceToShell() {}; + + void getWeight(); + void getForce(); + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + } +} +#endif //THIN_STRUCTURE_DYNAMICS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.cpp b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.cpp new file mode 100644 index 0000000000..4381f9037e --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.cpp @@ -0,0 +1,148 @@ +/** + * @file thin_structure_math.cpp + * @author Dong Wu, Chi ZHang and Xiangyu Hu + * @version 0.1 + */ + +#include "thin_structure_math.h" + +using namespace SimTK; + +namespace SPH +{ + namespace thin_structure_dynamics + { + //=================================================================================================// + Vec2d getVectorAfterThinStructureRotation(const Vec2d &initial_vector, const Vec2d &rotation_angles) + { + /**The rotation matrix. */ + Mat2d rotation_matrix(0.0); + rotation_matrix[0][0] = cos(rotation_angles[0]); + rotation_matrix[0][1] = sin(rotation_angles[0]); + rotation_matrix[1][0] = -rotation_matrix[0][1]; + rotation_matrix[1][1] = rotation_matrix[0][0]; + + return rotation_matrix * initial_vector; + } + //=================================================================================================// + Vec3d getVectorAfterThinStructureRotation(const Vec3d &initial_vector, const Vec3d &rotation_angles) + { + /**The rotation matrix about the X-axis. */ + Mat3d rotation_matrix_x(0.0); + rotation_matrix_x[0][0] = 1.0; + rotation_matrix_x[1][1] = cos(rotation_angles[0]); + rotation_matrix_x[1][2] = -sin(rotation_angles[0]); + rotation_matrix_x[2][1] = -rotation_matrix_x[1][2]; + rotation_matrix_x[2][2] = rotation_matrix_x[1][1]; + /**The rotation matrix about the Y-axis. */ + Mat3d rotation_matrix_y(0.0); + rotation_matrix_y[0][0] = cos(rotation_angles[1]); + rotation_matrix_y[0][2] = sin(rotation_angles[1]); + rotation_matrix_y[1][1] = 1.0; + rotation_matrix_y[2][0] = -rotation_matrix_y[0][2]; + rotation_matrix_y[2][2] = rotation_matrix_y[0][0]; + + return rotation_matrix_y * rotation_matrix_x * initial_vector; + } + //=================================================================================================// + Vec2d getVectorChangeRateAfterThinStructureRotation(const Vec2d &initial_vector, const Vec2d &rotation_angles, const Vec2d &angular_vel) + { + /**The derivative of the rotation matrix. */ + Mat2d drotation_matrix_dt(0.0); + drotation_matrix_dt[0][0] = -sin(rotation_angles[0]) * angular_vel[0]; + drotation_matrix_dt[0][1] = cos(rotation_angles[0]) * angular_vel[0]; + drotation_matrix_dt[1][0] = -drotation_matrix_dt[0][1]; + drotation_matrix_dt[1][1] = drotation_matrix_dt[0][0]; + + return drotation_matrix_dt * initial_vector; + } + //=================================================================================================// + Vec3d getVectorChangeRateAfterThinStructureRotation(const Vec3d& initial_vector, const Vec3d& rotation_angles, const Vec3d& angular_vel) + { + /**The rotation matrix about the X-axis. */ + Mat3d rotation_matrix_x(0.0); + rotation_matrix_x[0][0] = 1.0; + rotation_matrix_x[1][1] = cos(rotation_angles[0]); + rotation_matrix_x[1][2] = -sin(rotation_angles[0]); + rotation_matrix_x[2][1] = -rotation_matrix_x[1][2]; + rotation_matrix_x[2][2] = rotation_matrix_x[1][1]; + /**The rotation matrix about the Y-axis. */ + Mat3d rotation_matrix_y(0.0); + rotation_matrix_y[0][0] = cos(rotation_angles[1]); + rotation_matrix_y[0][2] = sin(rotation_angles[1]); + rotation_matrix_y[1][1] = 1.0; + rotation_matrix_y[2][0] = -rotation_matrix_y[0][2]; + rotation_matrix_y[2][2] = rotation_matrix_y[0][0]; + + /**The derivative of the rotation matrix of the X-axis. */ + Mat3d drotation_matrix_x_dt(0.0); + drotation_matrix_x_dt[1][1] = -sin(rotation_angles[0]) * angular_vel[0]; + drotation_matrix_x_dt[1][2] = -cos(rotation_angles[0]) * angular_vel[0]; + drotation_matrix_x_dt[2][1] = -drotation_matrix_x_dt[1][2]; + drotation_matrix_x_dt[2][2] = drotation_matrix_x_dt[1][1]; + /**The derivative of the rotation matrix of the Y-axis. */ + Mat3d drotation_matrix_y_dt(0.0); + drotation_matrix_y_dt[0][0] = -sin(rotation_angles[1]) * angular_vel[1]; + drotation_matrix_y_dt[0][2] = cos(rotation_angles[1]) * angular_vel[1]; + drotation_matrix_y_dt[2][0] = -drotation_matrix_y_dt[0][2]; + drotation_matrix_y_dt[2][2] = drotation_matrix_y_dt[0][0]; + + return (drotation_matrix_y_dt * rotation_matrix_x + rotation_matrix_y * drotation_matrix_x_dt)* initial_vector; + } + //=================================================================================================// + Vec2d getRotationFromPseudoNormalForFiniteDeformation(const Vec2d& dpseudo_n_d2t, const Vec2d& rotation, const Vec2d& angular_vel, Real dt) + { + Vec2d dangular_vel_dt(0.0); + dangular_vel_dt[0] = -(dpseudo_n_d2t[0] + sin(rotation[0]) * powerN(angular_vel[0], 2)) + / (2 * sin(rotation[0]) * angular_vel[0] * dt - cos(rotation[0])); + return dangular_vel_dt; + } + //=================================================================================================// + Vec3d getRotationFromPseudoNormalForFiniteDeformation(const Vec3d& dpseudo_n_d2t, const Vec3d& rotation, const Vec3d& angular_vel, Real dt) + { + Vec3d dangular_vel_dt(0.0); + dangular_vel_dt[0] = (dpseudo_n_d2t[1] - sin(rotation[0]) * powerN(angular_vel[0], 2)) + / (2 * sin(rotation[0]) * angular_vel[0] * dt - cos(rotation[0])); + dangular_vel_dt[1] = (dpseudo_n_d2t[0] + cos(rotation[0]) * sin(rotation[1]) + * (powerN(angular_vel[0], 2) + powerN(angular_vel[1], 2)) + + 2 * sin(rotation[0]) * cos(rotation[1]) * angular_vel[0] * angular_vel[1] + + (2 * cos(rotation[0]) * sin(rotation[1]) * angular_vel[0] * dt + + 2 * sin(rotation[0]) * cos(rotation[1]) * angular_vel[1] * dt + + sin(rotation[0]) * cos(rotation[1])) * dangular_vel_dt[0]) + / (-2 * sin(rotation[0]) * cos(rotation[1]) * angular_vel[0] * dt + - 2 * cos(rotation[0]) * sin(rotation[1]) * angular_vel[1] * dt + + cos(rotation[0]) * cos(rotation[1])); + return dangular_vel_dt; + } + //=================================================================================================// + Vec2d getRotationFromPseudoNormalForSmallDeformation(const Vec2d& dpseudo_n_d2t, const Vec2d& rotation, const Vec2d& angular_vel, Real dt) + { + Vec2d dangular_vel_dt(0.0); + dangular_vel_dt[0] = dpseudo_n_d2t[0]; + return dangular_vel_dt; + } + //=================================================================================================// + Vec3d getRotationFromPseudoNormalForSmallDeformation(const Vec3d& dpseudo_n_d2t, const Vec3d& rotation, const Vec3d& angular_vel, Real dt) + { + Vec3d dangular_vel_dt(0.0); + dangular_vel_dt[0] = -dpseudo_n_d2t[1]; + dangular_vel_dt[1] = dpseudo_n_d2t[0]; + return dangular_vel_dt; + } + //=================================================================================================// + Vec2d getNormalFromDeformationGradientTensor(const Mat2d& F) + { + Vec2d n = Vec2d(-F.col(0)[1], F.col(0)[0]); + n = n / (n.norm() + Eps); + return n; + } + //=================================================================================================// + Vec3d getNormalFromDeformationGradientTensor(const Mat3d& F) + { + Vec3d n = F.col(0) % F.col(1); + n = n / (n.norm() + Eps); + return n; + } + //=================================================================================================// + } +} diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.h b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.h new file mode 100644 index 0000000000..8af2c07ae6 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.h @@ -0,0 +1,69 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file thin_structure_math.h +* @brief Here, we define the math operation for thin structure dynamics. +* @author Dong Wu and Xiangyu Hu +* @version 0.1 +*/ + +#ifndef THIN_STRUCTURE_MATH_H +#define THIN_STRUCTURE_MATH_H + + +#include "all_particle_dynamics.h" +#include "elastic_solid.h" +#include "weakly_compressible_fluid.h" +#include "base_kernel.h" + +namespace SPH +{ + namespace thin_structure_dynamics + { + /** + * @function getVectorAfterThinStructureRotation + * @brief Each of these basic vector rotations appears counterclockwise + * @brief when the axis about which they occur points toward the observer, + * @brief and the coordinate system is right-handed. + */ + Vec2d getVectorAfterThinStructureRotation(const Vec2d &initial_vector, const Vec2d &rotation_angles); + Vec3d getVectorAfterThinStructureRotation(const Vec3d &initial_vector, const Vec3d &rotation_angles); + + /** Vector change rate after rotation. */ + Vec2d getVectorChangeRateAfterThinStructureRotation(const Vec2d &initial_vector, const Vec2d &rotation_angles, const Vec2d &angular_vel); + Vec3d getVectorChangeRateAfterThinStructureRotation(const Vec3d &initial_vector, const Vec3d &rotation_angles, const Vec3d &angular_vel); + + /** get the rotation from pseudo-normal for finite deformation. */ + Vec2d getRotationFromPseudoNormalForFiniteDeformation(const Vec2d& dpseudo_n_d2t, const Vec2d& rotation, const Vec2d& angular_vel, Real dt); + Vec3d getRotationFromPseudoNormalForFiniteDeformation(const Vec3d& dpseudo_n_d2t, const Vec3d& rotation, const Vec3d& angular_vel, Real dt); + + /** get the rotation from pseudo-normal for small deformation. */ + Vec2d getRotationFromPseudoNormalForSmallDeformation(const Vec2d& dpseudo_n_d2t, const Vec2d& rotation, const Vec2d& angular_vel, Real dt); + Vec3d getRotationFromPseudoNormalForSmallDeformation(const Vec3d& dpseudo_n_d2t, const Vec3d& rotation, const Vec3d& angular_vel, Real dt); + + /** get the current normal direction from deformation gradient tensor. */ + Vec2d getNormalFromDeformationGradientTensor(const Mat2d& F); + Vec3d getNormalFromDeformationGradientTensor(const Mat3d& F); + } +} +#endif //THIN_STRUCTURE_MATH_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_generator/CMakeLists.txt b/SPHINXsys/src/shared/particle_generator/CMakeLists.txt new file mode 100644 index 0000000000..6e136f3c33 --- /dev/null +++ b/SPHINXsys/src/shared/particle_generator/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) diff --git a/SPHINXsys/src/shared/particle_generator/base_particle_generator.cpp b/SPHINXsys/src/shared/particle_generator/base_particle_generator.cpp new file mode 100644 index 0000000000..8e969473f9 --- /dev/null +++ b/SPHINXsys/src/shared/particle_generator/base_particle_generator.cpp @@ -0,0 +1,60 @@ +/** + * @file base_particle_generator.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#include "base_particle_generator.h" + +#include "base_body.h" +#include "base_particles.h" +#include "in_output.h" + + +namespace SPH { + //=================================================================================================// + void ParticleGenerator::initialize(SPHBody* sph_body) + { + sph_body_ = sph_body; + } + //=================================================================================================// + void ParticleGeneratorDirect + ::createBaseParticles(BaseParticles* base_particles) + { + auto& body_input_points_volumes = sph_body_->body_input_points_volumes_; + for (size_t i = 0; i < body_input_points_volumes.size(); ++i) + { + base_particles->initializeABaseParticle(body_input_points_volumes[i].first, + body_input_points_volumes[i].second); + } + } + //=================================================================================================// + ParticleGeneratorReload:: + ParticleGeneratorReload(In_Output* in_output, std::string reload_body_name) + : ParticleGenerator() + { + if (!fs::exists(in_output->reload_folder_)) + { + std::cout << "\n Error: the particle reload folder:" << in_output->reload_folder_ << " is not exists" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + + file_path_ = in_output->reload_folder_ + "/SPHBody_" + reload_body_name + "_rld.xml"; + } + //=================================================================================================// + void ParticleGeneratorReload::createBaseParticles(BaseParticles* base_particles) + { + XmlEngine* reload_xml_engine = base_particles->getReloadXmlEngine(); + reload_xml_engine->loadXmlFile(file_path_); + SimTK::Xml::element_iterator ele_ite_ = reload_xml_engine->root_element_.element_begin(); + for (; ele_ite_ != reload_xml_engine->root_element_.element_end(); ++ele_ite_) + { + Vecd position(0); + reload_xml_engine->getRequiredAttributeValue(ele_ite_, "Position", position); + Real volume(0); + reload_xml_engine->getRequiredAttributeValue(ele_ite_, "Volume", volume); + base_particles->initializeABaseParticle(position, volume); + } + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/particle_generator/base_particle_generator.h b/SPHINXsys/src/shared/particle_generator/base_particle_generator.h new file mode 100644 index 0000000000..d60cd2a27a --- /dev/null +++ b/SPHINXsys/src/shared/particle_generator/base_particle_generator.h @@ -0,0 +1,86 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file base_particle_generator.h + * @brief This is the base class of particle generator, which generates particles + * with given positions and volumes. The direct generator simply generate + * particle with given position and volume. + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ + +#ifndef BASE_PARTICLE_GENERATOR_H +#define BASE_PARTICLE_GENERATOR_H + + + +#include "base_data_package.h" +#include "sph_data_conainers.h" + +namespace SPH { + + class SPHBody; + class BaseParticles; + class In_Output; + + /** + * @class ParticleGenerator. + * @brief Base abstract class for particle generation. + */ + class ParticleGenerator + { + public: + ParticleGenerator() : sph_body_(nullptr) {}; + virtual ~ParticleGenerator() {}; + + virtual void initialize(SPHBody* sph_body); + virtual void createBaseParticles(BaseParticles* base_particles) = 0; + protected: + SPHBody* sph_body_; + }; + + /** + * @class ParticleGeneratorDirect + * @brief Generate particle directly from position-and-volume data. + */ + class ParticleGeneratorDirect : public ParticleGenerator + { + public: + ParticleGeneratorDirect() : ParticleGenerator() {}; + virtual ~ParticleGeneratorDirect() {}; + virtual void createBaseParticles(BaseParticles* base_particles) override; + }; + + /** + * @class ParticleGeneratorReload + * @brief Generate particle by reloading particle position and volume. + */ + class ParticleGeneratorReload : public ParticleGenerator + { + std::string file_path_; + public: + ParticleGeneratorReload(In_Output* in_output, std::string reload_body_name); + virtual ~ParticleGeneratorReload() {}; + virtual void createBaseParticles(BaseParticles* base_particles) override; + }; +} +#endif //BASE_PARTICLE_GENERATOR_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_generator/particle_generator_lattice.cpp b/SPHINXsys/src/shared/particle_generator/particle_generator_lattice.cpp new file mode 100644 index 0000000000..b2d1735e9a --- /dev/null +++ b/SPHINXsys/src/shared/particle_generator/particle_generator_lattice.cpp @@ -0,0 +1,59 @@ +/** + * @file particle_generator_lattice.cpp + * @author Luhui Han, Chi ZHang and Xiangyu Hu + */ +#include "particle_generator_lattice.h" + +#include "geometry.h" +#include "base_body.h" +#include "base_particles.h" +#include "particle_adaptation.h" + +namespace SPH { + //=================================================================================================// + ParticleGeneratorLattice::ParticleGeneratorLattice() + : ParticleGenerator(), lattice_spacing_(0), + domain_bounds_(0, 0), body_shape_(nullptr) + { + } + //=================================================================================================// + void ParticleGeneratorLattice::initialize(SPHBody* sph_body) + { + sph_body_ = sph_body; + lattice_spacing_ = sph_body_->particle_adaptation_->ReferenceSpacing(); + domain_bounds_ = sph_body_->getSPHSystemBounds(); + body_shape_ = sph_body_->body_shape_; + } + //=================================================================================================// + void ParticleGeneratorLattice:: + createABaseParticle(BaseParticles* base_particles, Vecd& particle_position, Real particle_volume) + { + base_particles->initializeABaseParticle(particle_position, particle_volume); + } + //=================================================================================================// + ParticleGeneratorMultiResolution::ParticleGeneratorMultiResolution() + : ParticleGeneratorLattice(), particle_adapation_(nullptr) {} + //=================================================================================================// + void ParticleGeneratorMultiResolution::initialize(SPHBody* sph_body) + { + sph_body_ = sph_body; + particle_adapation_ = + dynamic_cast(sph_body->particle_adaptation_); + lattice_spacing_ = particle_adapation_->MinimumSpacing(); + domain_bounds_ = sph_body_->getSPHSystemBounds(); + body_shape_ = sph_body_->body_shape_; + } + //=================================================================================================// + void ParticleGeneratorMultiResolution:: + createABaseParticle(BaseParticles* base_particles, Vecd& particle_position, Real particle_volume) + { + Real local_particle_spacing + = particle_adapation_->getLocalSpacing(*body_shape_, particle_position); + Real local_particle_volume_ratio = powerN(lattice_spacing_ / local_particle_spacing, Dimensions); + if ((double)rand() / (RAND_MAX) < local_particle_volume_ratio) + { + base_particles->initializeABaseParticle(particle_position, particle_volume / local_particle_volume_ratio); + } + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/particle_generator/particle_generator_lattice.h b/SPHINXsys/src/shared/particle_generator/particle_generator_lattice.h new file mode 100644 index 0000000000..02866a606f --- /dev/null +++ b/SPHINXsys/src/shared/particle_generator/particle_generator_lattice.h @@ -0,0 +1,82 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file particle_generator_lattice.h + * @brief This is the base class of particle generator, which generates particles + * with given positions and volumes. The direct generator simply generate + * particle with given position and volume. The lattice generator generate + * at lattice position by check whether the poision is contained by a SPH body. + * @author Xiangyu Hu, Chi Zhang, Yongchuan Yu + */ + +#ifndef PARTICLE_GENERATOR_LATTICE_H +#define PARTICLE_GENERATOR_LATTICE_H + + + +#include "base_particle_generator.h" + +namespace SPH { + + class ComplexShape; + class ParticleSpacingByBodyShape; + + /** + * @class ParticleGeneratorLattice + * @brief generate particles from lattice positions for a body. + */ + class ParticleGeneratorLattice : public ParticleGenerator + { + public: + ParticleGeneratorLattice(); + virtual ~ParticleGeneratorLattice() {}; + + virtual void initialize(SPHBody* sph_body) override; + virtual void createBaseParticles(BaseParticles* base_particles) override; + protected: + Real lattice_spacing_; + BoundingBox domain_bounds_; + ComplexShape* body_shape_; + + virtual void createABaseParticle(BaseParticles* base_particles, + Vecd& particle_position, Real particle_volume); + }; + + /** + * @class ParticleGeneratorMultiResolution + * @brief generate multi-resolution particles from lattice positions for a body. + */ + class ParticleGeneratorMultiResolution : public ParticleGeneratorLattice + { + public: + ParticleGeneratorMultiResolution(); + virtual ~ParticleGeneratorMultiResolution() {}; + virtual void initialize(SPHBody* sph_body) override; + protected: + ParticleSpacingByBodyShape* particle_adapation_; + + virtual void createABaseParticle(BaseParticles* base_particles, + Vecd& particle_position, Real particle_volume) override; + }; +} +#endif //PARTICLE_GENERATOR_LATTICE_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/CMakeLists.txt b/SPHINXsys/src/shared/particles/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/particles/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/all_particles.h b/SPHINXsys/src/shared/particles/all_particles.h new file mode 100644 index 0000000000..a4b5a5fe58 --- /dev/null +++ b/SPHINXsys/src/shared/particles/all_particles.h @@ -0,0 +1,11 @@ + +#ifndef ALL_PARTICLES_H +#define ALL_PARTICLES_H + + + +#include "fluid_particles.h" +#include "solid_particles.h" +#include "diffusion_reaction_particles.h" + +#endif //ALL_PARTICLES_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/base_particles.cpp b/SPHINXsys/src/shared/particles/base_particles.cpp new file mode 100644 index 0000000000..a007b58ee3 --- /dev/null +++ b/SPHINXsys/src/shared/particles/base_particles.cpp @@ -0,0 +1,339 @@ +/** + * @file base_particles.cpp + * @brief Definition of functions declared in base_particles.h + * @author Xiangyu Hu and Chi Zhang + */ +#include "base_particles.h" + +#include "base_body.h" +#include "base_material.h" +#include "base_particle_generator.h" +#include "xml_engine.h" + +namespace SPH +{ + //=================================================================================================// + BaseParticles::BaseParticles(SPHBody* body, BaseMaterial* base_material) : + base_material_(base_material), speed_max_(0.0), signal_speed_max_(0.0), + total_real_particles_(0), real_particles_bound_(0), total_ghost_particles_(0), + body_(body), body_name_(body->getBodyName()), + restart_xml_engine_("xml_restart", "particles"), + reload_xml_engine_("xml_particle_reload", "particles") + { + body->assignBaseParticles(this); + rho0_ = base_material->ReferenceDensity(); + sigma0_ = body->particle_adaptation_->ReferenceNumberDensity(); + //---------------------------------------------------------------------- + // register particle data + //---------------------------------------------------------------------- + registerAVariable(pos_n_, "Position"); + registerAVariable(vel_n_, "Velocity"); + registerAVariable(dvel_dt_, "Acceleration"); + registerAVariable(dvel_dt_prior_, "OtherAcceleration"); + registerAVariable(Vol_, "Volume"); + registerAVariable(rho_n_, "Density"); + registerAVariable(mass_, "Mass"); + //---------------------------------------------------------------------- + // add basic output particle data + //---------------------------------------------------------------------- + addAVariableToWrite("Velocity"); + addAVariableToWrite("Density"); + //---------------------------------------------------------------------- + // add restart output particle data + //---------------------------------------------------------------------- + addAVariableNameToList(variables_to_restart_, "Position"); + addAVariableNameToList(variables_to_restart_, "Velocity"); + addAVariableNameToList(variables_to_restart_, "Volume"); + addAVariableNameToList(variables_to_restart_, "Density"); + + ParticleGenerator* particle_generator = body_->particle_generator_; + particle_generator->initialize(body_); + particle_generator->createBaseParticles(this); + real_particles_bound_ = total_real_particles_; + + body->particle_adaptation_->assignBaseParticles(this); + base_material->assignBaseParticles(this); + } + //=================================================================================================// + BaseParticles::BaseParticles(SPHBody* body) + : BaseParticles(body, new BaseMaterial()) {} + //=================================================================================================// + void BaseParticles::initializeABaseParticle(Vecd pnt, Real Vol_0) + { + total_real_particles_++; + sequence_.push_back(0); + sorted_id_.push_back(pos_n_.size()); + unsorted_id_.push_back(pos_n_.size()); + + pos_n_.push_back(pnt); + vel_n_.push_back(Vecd(0)); + dvel_dt_.push_back(Vecd(0)); + dvel_dt_prior_.push_back(Vecd(0)); + + Vol_.push_back(Vol_0); + rho_n_.push_back(rho0_); + mass_.push_back(rho0_ * Vol_0); + } + //=================================================================================================// + void BaseParticles::addAParticleEntry() + { + sequence_.push_back(0); + sorted_id_.push_back(pos_n_.size()); + unsorted_id_.push_back(pos_n_.size()); + + loopParticleData(all_particle_data_); + } + //=================================================================================================// + void BaseParticles::addBufferParticles(size_t buffer_size) + { + for (size_t i = 0; i != buffer_size; ++i) + { + addAParticleEntry(); + } + real_particles_bound_ += buffer_size; + } + //=================================================================================================// + void BaseParticles::copyFromAnotherParticle(size_t this_index, size_t another_index) + { + updateFromAnotherParticle(this_index, another_index); + } + //=================================================================================================// + void BaseParticles::updateFromAnotherParticle(size_t this_index, size_t another_index) + { + loopParticleData(all_particle_data_, this_index, another_index); + } + //=================================================================================================// + size_t BaseParticles::insertAGhostParticle(size_t index_i) + { + total_ghost_particles_ += 1; + size_t expected_size = real_particles_bound_ + total_ghost_particles_; + size_t expected_particle_index = expected_size - 1; + if (expected_size <= pos_n_.size()) { + copyFromAnotherParticle(expected_particle_index, index_i); + /** For a ghost particle, its sorted id is that of corresponding real particle. */ + sorted_id_[expected_particle_index] = index_i; + + } + else { + addAParticleEntry(); + copyFromAnotherParticle(expected_particle_index, index_i); + /** For a ghost particle, its sorted id is that of corresponding real particle. */ + sorted_id_[expected_particle_index] = index_i; + } + return expected_particle_index; + } + //=================================================================================================// + void BaseParticles::switchToBufferParticle(size_t index_i) + { + size_t last_real_particle_index = total_real_particles_ - 1; + updateFromAnotherParticle(index_i, last_real_particle_index); + unsorted_id_[index_i] = unsorted_id_[last_real_particle_index]; + total_real_particles_ -= 1; + } + //=================================================================================================// + void BaseParticles::writeParticlesToVtuFile(std::ofstream& output_file) + { + size_t total_real_particles = total_real_particles_; + + //write particle positions first + output_file << " \n"; + output_file << " \n"; + output_file << " "; + for (size_t i = 0; i != total_real_particles; ++i) { + Vec3d particle_position = upgradeToVector3D(pos_n_[i]); + output_file << particle_position[0] << " " << particle_position[1] << " " << particle_position[2] << " "; + } + output_file << std::endl; + output_file << " \n"; + output_file << " \n"; + + //write header of particles data + output_file << " \n"; + + //write sorted particles ID + output_file << " \n"; + output_file << " "; + for (size_t i = 0; i != total_real_particles; ++i) { + output_file << i << " "; + } + output_file << std::endl; + output_file << " \n"; + + //write unsorted particles ID + output_file << " \n"; + output_file << " "; + for (size_t i = 0; i != total_real_particles; ++i) { + output_file << unsorted_id_[i] << " "; + } + output_file << std::endl; + output_file << " \n"; + + //write matrices + for (std::pair& name_index : variables_to_write_[indexMatrix]) + { + std::string variable_name = name_index.first; + StdLargeVec& variable = *(std::get(all_particle_data_)[name_index.second]); + output_file << " \n"; + output_file << " "; + for (size_t i = 0; i != total_real_particles; ++i) { + Mat3d matrix_value = upgradeToMatrix3D(variable[i]); + for (int k = 0; k != 3; ++k) { + Vec3d col_vector = matrix_value.col(k); + output_file << std::fixed << std::setprecision(9) << col_vector[0] << " " << col_vector[1] << " " << col_vector[2] << " "; + } + } + output_file << std::endl; + output_file << " \n"; + } + + //write vectors + for (std::pair& name_index : variables_to_write_[indexVector]) + { + std::string variable_name = name_index.first; + StdLargeVec& variable = *(std::get(all_particle_data_)[name_index.second]); + output_file << " \n"; + output_file << " "; + for (size_t i = 0; i != total_real_particles; ++i) { + Vec3d vector_value = upgradeToVector3D(variable[i]); + output_file << std::fixed << std::setprecision(9) << vector_value[0] << " " << vector_value[1] << " " << vector_value[2] << " "; + } + output_file << std::endl; + output_file << " \n"; + } + + //write scalars + for (std::pair& name_index : variables_to_write_[indexScalar]) + { + std::string variable_name = name_index.first; + StdLargeVec& variable = *(std::get(all_particle_data_)[name_index.second]); + output_file << " \n"; + output_file << " "; + for (size_t i = 0; i != total_real_particles; ++i) { + output_file << std::fixed << std::setprecision(9) << variable[i] << " "; + } + output_file << std::endl; + output_file << " \n"; + } + + //write integers + for (std::pair& name_index : variables_to_write_[indexInteger]) + { + std::string variable_name = name_index.first; + StdLargeVec& variable = *(std::get(all_particle_data_)[name_index.second]); + output_file << " \n"; + output_file << " "; + for (size_t i = 0; i != total_real_particles; ++i) { + output_file << std::fixed << std::setprecision(9) << variable[i] << " "; + } + output_file << std::endl; + output_file << " \n"; + } + } + //=================================================================================================// + void BaseParticles::writePltFileHeader(std::ofstream& output_file) + { + output_file << " VARIABLES = \"x\",\"y\",\"z\",\"ID\""; + for (size_t l = 0; l != variables_to_write_[indexVector].size(); ++l) { + std::string variable_name = variables_to_write_[indexVector][l].first; + output_file << ",\"" << variable_name << "_x\"" << ",\"" << variable_name << "_y\"" << ",\"" << variable_name << "_z\""; + }; + for (size_t l = 0; l != variables_to_write_[indexScalar].size(); ++l) { + std::string variable_name = variables_to_write_[indexScalar][l].first; + output_file << ",\"" << variable_name << "\""; + }; + } + //=================================================================================================// + void BaseParticles::writePltFileParticleData(std::ofstream& output_file, size_t index_i) + { + //write particle positions and index first + Vec3d particle_position = upgradeToVector3D(pos_n_[index_i]); + output_file << particle_position[0] << " " << particle_position[1] << " " << particle_position[2] << " " + << index_i << " "; + + for (std::pair& name_index : variables_to_write_[indexVector]) + { + std::string variable_name = name_index.first; + StdLargeVec& variable = *(std::get(all_particle_data_)[name_index.second]); + Vec3d vector_value = upgradeToVector3D(variable[index_i]); + output_file << vector_value[0] << " " << vector_value[1] << " " << vector_value[2] << " "; + }; + + for (std::pair& name_index : variables_to_write_[indexScalar]) + { + std::string variable_name = name_index.first; + StdLargeVec& variable = *(std::get(all_particle_data_)[name_index.second]); + output_file << variable[index_i] << " "; + }; + } + //=================================================================================================// + void BaseParticles::writeParticlesToPltFile(std::ofstream& output_file) + { + writePltFileHeader(output_file); + output_file << "\n"; + + size_t total_real_particles = total_real_particles_; + for (size_t i = 0; i != total_real_particles; ++i) { + writePltFileParticleData(output_file, i); + output_file << "\n"; + }; + } + //=================================================================================================// + void BaseParticles::resizeXmlDocForParticles(XmlEngine& xml_engine) + { + size_t total_elements = xml_engine.SizeOfXmlDoc(); + + if (total_elements <= total_real_particles_) + { + for (size_t i = total_elements; i != total_real_particles_; ++i) + xml_engine.addElementToXmlDoc("particle"); + } + } + //=================================================================================================// + void BaseParticles::writeParticlesToXmlForRestart(std::string& filefullpath) + { + resizeXmlDocForParticles(restart_xml_engine_); + WriteAParticleVariableToXml write_variable_to_xml(restart_xml_engine_, total_real_particles_); + loopParticleData(all_particle_data_, variables_to_restart_, write_variable_to_xml); + restart_xml_engine_.writeToXmlFile(filefullpath); + } + //=================================================================================================// + void BaseParticles::readParticleFromXmlForRestart(std::string& filefullpath) + { + restart_xml_engine_.loadXmlFile(filefullpath); + ReadAParticleVariableFromXml read_variable_from_xml(restart_xml_engine_, total_real_particles_); + loopParticleData(all_particle_data_, variables_to_restart_, read_variable_from_xml); + } + //=================================================================================================// + void BaseParticles::writeToXmlForReloadParticle(std::string& filefullpath) + { + resizeXmlDocForParticles(reload_xml_engine_); + SimTK::Xml::element_iterator ele_ite = reload_xml_engine_.root_element_.element_begin(); + for (size_t i = 0; i != total_real_particles_; ++i) + { + reload_xml_engine_.setAttributeToElement(ele_ite, "Position", pos_n_[i]); + reload_xml_engine_.setAttributeToElement(ele_ite, "Volume", Vol_[i]); + ele_ite++; + } + reload_xml_engine_.writeToXmlFile(filefullpath); + } + //=================================================================================================// + void BaseParticles::readFromXmlForReloadParticle(std::string& filefullpath) + { + reload_xml_engine_.loadXmlFile(filefullpath); + SimTK::Xml::element_iterator ele_ite = reload_xml_engine_.root_element_.element_begin(); + for (size_t i = 0; i != total_real_particles_; ++i) + { + reload_xml_engine_.getRequiredAttributeValue(ele_ite, "Position", pos_n_[i]); + reload_xml_engine_.getRequiredAttributeValue(ele_ite, "Volume", Vol_[i]); + ele_ite++; + } + + if (reload_xml_engine_.SizeOfXmlDoc() != total_real_particles_) + { + std::cout << "\n Error: reload particle number does not match!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/particles/base_particles.h b/SPHINXsys/src/shared/particles/base_particles.h new file mode 100644 index 0000000000..3b0b8243ca --- /dev/null +++ b/SPHINXsys/src/shared/particles/base_particles.h @@ -0,0 +1,351 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file base_particles.h + * @brief This is the base class of SPH particles. The basic data of the particles + * is saved in separated large vectors. Each derived class will introduce several extra + * vectors for the new data. Note that there is no class of single particle. + * @author Xiangyu Hu and Chi Zhang + */ + +#ifndef BASE_PARTICLES_H +#define BASE_PARTICLES_H + + + +#include "base_data_package.h" +#include "sph_data_conainers.h" +#include "xml_engine.h" + +#include + +namespace SPH +{ + + class SPHBody; + class BaseMaterial; + + /** + * @class BaseParticles + * @brief Particles with essential (geometric and kinematic) data. + * There are three types of particles, all particles of a same type are saved with continuous memory segments. + * The first type is real particles whose states are updated by particle dynamics. + * One is buffer particles whose state are not updated by particle dynamics. + * Buffer particles are saved behind real particles. + * The global value of total_real_particles_ separate the real and buffer particles. + * They may be switched from real particles or switch to real particles. + * As the memory for both particles are continuous, such switch is achieved at the memory boundary sequentially. + * The basic idea is swap the data of the last real particle with the one will be switched particle, + * and then switch this swapped last particle as buffer particle by decrease the total_real_particles_ by one. + * Switch from buffer particle to real particle is easy. One just need to assign expect state to + * the first buffer particle and increase total_real_particles_ by one. + * The other is ghost particles whose states are updated according to + * boundary condition if their indices are included in the neighbor particle list. + * The ghost particles are saved behind the buffer particles. + * The global value of real_particles_bound_ separate the sum of real and buffer particles with ghost particles. + * The global value of total_ghost_particles_ indicates the total number of ghost particles in use. + * It will be initialized to zero before a time step. + */ + class BaseParticles + { + public: + BaseParticles(SPHBody* body, BaseMaterial* base_material); + BaseParticles(SPHBody* body); + virtual ~BaseParticles() {}; + + BaseMaterial* base_material_; /**< for dynamic cast in particle data delegation */ + + StdLargeVec pos_n_; /**< current position */ + StdLargeVec vel_n_; /**< current particle velocity */ + StdLargeVec dvel_dt_; /**< inner pressure- or stress-induced acceleration */ + StdLargeVec dvel_dt_prior_; /**< other, such as gravity and viscous, accelerations */ + + StdLargeVec Vol_; /**< particle volume */ + StdLargeVec rho_n_; /**< current particle density */ + StdLargeVec mass_; /**< particle mass */ + //---------------------------------------------------------------------- + //Global information for all particles + //---------------------------------------------------------------------- + Real rho0_; /**< reference density*/ + Real sigma0_; /**< reference number density. */ + Real speed_max_; /**< Maximum particle speed. */ + Real signal_speed_max_; /**< Maximum signal speed.*/ + //---------------------------------------------------------------------- + //Global information for defining particle groups + //---------------------------------------------------------------------- + size_t total_real_particles_; + size_t real_particles_bound_; /**< Maximum possible number of real particles. Also the start index of ghost particles. */ + size_t total_ghost_particles_; + //---------------------------------------------------------------------- + // Generalized particle data for parameterized management + //---------------------------------------------------------------------- + ParticleData all_particle_data_; + ParticleDataMap all_variable_maps_; + + /** register a variable defined in a class (can be non-particle class) to base particles */ + template + void registerAVariable(StdLargeVec& variable_addrs, + std::string variable_name, VariableType initial_value = VariableType(0)) + { + if (all_variable_maps_[DataTypeIndex].find(variable_name) == all_variable_maps_[DataTypeIndex].end()) + { + variable_addrs.resize(real_particles_bound_, initial_value); + std::get(all_particle_data_).push_back(&variable_addrs); + all_variable_maps_[DataTypeIndex].insert(make_pair(variable_name, std::get(all_particle_data_).size() - 1)); + } + else + { + std::cout << "\n Error: the variable '" << variable_name << "' has already been registered!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + }; + + /** Create and register a new variable which has not been defined yet in particles. + * If the variable is registered already, the registered variable will be returned. */ + template + StdLargeVec* createAVariable(std::string new_variable_name, VariableType initial_value = VariableType(0)) + { + if (all_variable_maps_[DataTypeIndex].find(new_variable_name) == all_variable_maps_[DataTypeIndex].end()) { + StdLargeVec* new_variable = new StdLargeVec; + registerAVariable(*new_variable, new_variable_name, initial_value); + return new_variable; + } + std::cout << "\n Warning: the variable '" << new_variable_name << "' has already registered!"; + std::cout << "\n So, the previously registered variable is assigned!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + return getVariableByName(new_variable_name); + }; + + /** create and register a new variable, which has not been defined yet in particles, + * by copying data from an exist variable */ + template + StdLargeVec* createAVariable(std::string new_variable_name, std::string old_variable_name) + { + if (all_variable_maps_[DataTypeIndex].find(old_variable_name) != all_variable_maps_[DataTypeIndex].end()) + { + if (all_variable_maps_[DataTypeIndex].find(new_variable_name) == all_variable_maps_[DataTypeIndex].end()) + { + StdLargeVec* new_variable = new StdLargeVec; + registerAVariable(*new_variable, new_variable_name); + StdLargeVec* old_variable = + std::get(all_particle_data_)[all_variable_maps_[DataTypeIndex][old_variable_name]]; + for (size_t i = 0; i != real_particles_bound_; ++i) (*new_variable)[i] = (*old_variable)[i]; + return new_variable; + } + std::cout << "\n Error: the new variable '" << new_variable_name << "' has already been registered!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + std::cout << "\n Error: the old variable '" << old_variable_name << "' is not registered!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + return nullptr; + }; + + /** get a registered variable from particles by its name */ + template + StdLargeVec* getVariableByName(std::string variable_name) + { + if (all_variable_maps_[DataTypeIndex].find(variable_name) != all_variable_maps_[DataTypeIndex].end()) + return std::get(all_particle_data_)[all_variable_maps_[DataTypeIndex][variable_name]]; + + std::cout << "\n Error: the variable '" << variable_name << "' is not registered!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + return nullptr; + }; + + /** add a variable into a particle vairable name list */ + template + void addAVariableNameToList(ParticleVariableList& variable_name_list, std::string variable_name) + { + if (all_variable_maps_[DataTypeIndex].find(variable_name) != all_variable_maps_[DataTypeIndex].end()) + { + bool is_to_add = true; + for (size_t i = 0; i != variable_name_list[DataTypeIndex].size(); ++i) { + if (variable_name_list[DataTypeIndex][i].first == variable_name) is_to_add = false; + } + if (is_to_add) { + size_t variable_index = all_variable_maps_[DataTypeIndex][variable_name]; + variable_name_list[DataTypeIndex].push_back(make_pair(variable_name, variable_index)); + } + } + else + { + std::cout << "\n Error: the variable '" << variable_name << "' you are going to write is not particle data!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + }; + + /** add a variable into the list for state output */ + template + void addAVariableToWrite(std::string variable_name) + { + addAVariableNameToList(variables_to_write_, variable_name); + }; + + /** add a variable into the list for restart */ + template + void addAVariableToRestart(std::string variable_name) + { + addAVariableNameToList(variables_to_restart_, variable_name); + }; + + //---------------------------------------------------------------------- + // Particle data for sorting + //---------------------------------------------------------------------- + StdLargeVec sequence_; + StdLargeVec sorted_id_; + StdLargeVec unsorted_id_; + ParticleData sortable_data_; + ParticleDataMap sortable_variable_maps_; + + /** register an already defined variable as sortable */ + template + void registerASortableVariable(std::string variable_name) + { + if (sortable_variable_maps_[DataTypeIndex].find(variable_name) == sortable_variable_maps_[DataTypeIndex].end()) + { + if (all_variable_maps_[DataTypeIndex].find(variable_name) != all_variable_maps_[DataTypeIndex].end()) + { + StdLargeVec* variable = + std::get(all_particle_data_)[all_variable_maps_[DataTypeIndex][variable_name]]; + std::get(sortable_data_).push_back(variable); + sortable_variable_maps_[DataTypeIndex].insert(make_pair(variable_name, std::get(sortable_data_).size() - 1)); + } + else + { + std::cout << "\n Error: the variable '" << variable_name << "' is not registered!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + } + else + { + std::cout << "\n Warning: the variable '" << variable_name << "' has already registered as a sortabele variable!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + } + }; + + SPHBody* getSPHBody() { return body_; }; + void initializeABaseParticle(Vecd pnt, Real Vol_0); + void addBufferParticles(size_t buffer_size); + void copyFromAnotherParticle(size_t this_index, size_t another_index); + void updateFromAnotherParticle(size_t this_index, size_t another_index); + size_t insertAGhostParticle(size_t index_i); + void switchToBufferParticle(size_t index_i); + + /** Write particle data in VTU format for Paraview. */ + virtual void writeParticlesToVtuFile(std::ofstream& output_file); + /** Write particle data in PLT format for Tecplot. */ + void writeParticlesToPltFile(std::ofstream& output_file); + + void resizeXmlDocForParticles(XmlEngine& xml_engine); + void writeParticlesToXmlForRestart(std::string& filefullpath); + void readParticleFromXmlForRestart(std::string& filefullpath); + XmlEngine* getReloadXmlEngine() { return &reload_xml_engine_; }; + void writeToXmlForReloadParticle(std::string& filefullpath); + void readFromXmlForReloadParticle(std::string& filefullpath); + + virtual BaseParticles* ThisObjectPtr() { return this; }; + + /** Normalize the kernel gradient. */ + virtual Vecd normalizeKernelGradient(size_t particle_index_i, Vecd& kernel_gradient) { return kernel_gradient; }; + /** Get the kernel gradient in weak form. */ + virtual Vecd getKernelGradient(size_t particle_index_i, size_t particle_index_j, + Real dW_ij, Vecd& e_ij) { + return dW_ij * e_ij; + }; + protected: + SPHBody* body_; /**< The body in which the particles belongs to. */ + std::string body_name_; + XmlEngine restart_xml_engine_; + XmlEngine reload_xml_engine_; + ParticleVariableList variables_to_write_; + ParticleVariableList variables_to_restart_; + void addAParticleEntry(); + + virtual void writePltFileHeader(std::ofstream& output_file); + virtual void writePltFileParticleData(std::ofstream& output_file, size_t index_i); + + template + struct addAParticleDataValue + { + void operator () (ParticleData& particle_data) const + { + for (size_t i = 0; i != std::get(particle_data).size(); ++i) + std::get(particle_data)[i]->push_back(VariableType(0)); + }; + }; + + template + struct copyAParticleDataValue + { + void operator () (ParticleData& particle_data, size_t this_index, size_t another_index) const + { + for (size_t i = 0; i != std::get(particle_data).size(); ++i) + (*std::get(particle_data)[i])[this_index] = + (*std::get(particle_data)[i])[another_index]; + }; + }; + }; + + struct WriteAParticleVariableToXml + { + XmlEngine& xml_engine_; + size_t& total_real_particles_; + WriteAParticleVariableToXml(XmlEngine& xml_engine, size_t& total_real_particles) : + xml_engine_(xml_engine), total_real_particles_(total_real_particles) {}; + template + void operator () (std::string& variable_name, StdLargeVec& variable) const + { + SimTK::Xml::element_iterator ele_ite = xml_engine_.root_element_.element_begin(); + for (size_t i = 0; i != total_real_particles_; ++i) + { + xml_engine_.setAttributeToElement(ele_ite, variable_name, variable[i]); + ele_ite++; + } + } + }; + + struct ReadAParticleVariableFromXml + { + XmlEngine& xml_engine_; + size_t& total_real_particles_; + ReadAParticleVariableFromXml(XmlEngine& xml_engine, size_t& total_real_particles) : + xml_engine_(xml_engine), total_real_particles_(total_real_particles) {}; + template + void operator () (std::string& variable_name, StdLargeVec& variable) const + { + SimTK::Xml::element_iterator ele_ite = xml_engine_.root_element_.element_begin(); + for (size_t i = 0; i != total_real_particles_; ++i) + { + xml_engine_.getRequiredAttributeValue(ele_ite, variable_name, variable[i]); + ele_ite++; + } + } + }; +} +#endif //BASE_PARTICLES_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/diffusion_reaction_particles.cpp b/SPHINXsys/src/shared/particles/diffusion_reaction_particles.cpp new file mode 100644 index 0000000000..41b6c824d6 --- /dev/null +++ b/SPHINXsys/src/shared/particles/diffusion_reaction_particles.cpp @@ -0,0 +1,24 @@ +/** + * @file diffusion_reaction_particles.cpp + * @author Chi Zhang and Xiangyu Hu + */ +#include "diffusion_reaction_particles.h" + + //=================================================================================================// +namespace SPH +{ + //=================================================================================================// + ElectroPhysiologyParticles + ::ElectroPhysiologyParticles(SPHBody* body, + DiffusionReactionMaterial* diffusion_reaction_material) + : DiffusionReactionParticles(body, diffusion_reaction_material) + { + } + //=================================================================================================// + ElectroPhysiologyReducedParticles::ElectroPhysiologyReducedParticles(SPHBody* body, + DiffusionReactionMaterial* diffusion_reaction_material) + : DiffusionReactionParticles(body, diffusion_reaction_material) + { + } + //=================================================================================================// +} \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/diffusion_reaction_particles.h b/SPHINXsys/src/shared/particles/diffusion_reaction_particles.h new file mode 100644 index 0000000000..211511264a --- /dev/null +++ b/SPHINXsys/src/shared/particles/diffusion_reaction_particles.h @@ -0,0 +1,133 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file diffusion_reaction_particles.h +* @brief This is the derived class of diffusion reaction particles. +* @author Xiangyu Huand Chi Zhang +*/ + + +#ifndef DIFFUSION_REACTION_PARTICLES_H +#define DIFFUSION_REACTION_PARTICLES_H + + + +#include "base_particles.h" +#include "base_body.h" +#include "base_material.h" +#include "diffusion_reaction.h" +#include "xml_engine.h" +#include "solid_particles.h" + +namespace SPH { + + /** + * @class DiffusionReactionParticles + * @brief A group of particles with diffusion or/and reactions particle data. + */ + template + class DiffusionReactionParticles : public BaseParticlesType + { + protected: + size_t number_of_species_; /**< Total number of diffusion and reaction species . */ + size_t number_of_diffusion_species_; /**< Total number of diffusion species . */ + std::map species_indexes_map_; + public: + StdVec> species_n_; /**< array of diffusion/reaction scalars */ + StdVec> diffusion_dt_;/**< array of the time derivative of diffusion species */ + + DiffusionReactionParticles(SPHBody* body, + DiffusionReactionMaterial* diffusion_reaction_material) + : BaseParticlesType(body, diffusion_reaction_material) + { + diffusion_reaction_material->assignDiffusionReactionParticles(this); + species_indexes_map_ = diffusion_reaction_material->SpeciesIndexMap(); + number_of_species_ = diffusion_reaction_material->NumberOfSpecies(); + species_n_.resize(number_of_species_); + + std::map::iterator itr; + for (itr = species_indexes_map_.begin(); itr != species_indexes_map_.end(); ++itr) + { + //Register a species. Note that we call a template function from a template class + this->template registerAVariable(species_n_[itr->second], itr->first); + //the scalars will be sorted if particle sorting is called + this->template registerASortableVariable(itr->first); + // add species to basic output particle data + this->template addAVariableToWrite(itr->first); + } + + number_of_diffusion_species_ = diffusion_reaction_material->NumberOfSpeciesDiffusion(); + diffusion_dt_.resize(number_of_diffusion_species_); + for (size_t m = 0; m < number_of_diffusion_species_; ++m) + { + //---------------------------------------------------------------------- + // register reactive change rate terms without giving variable name + //---------------------------------------------------------------------- + std::get(this->all_particle_data_).push_back(&diffusion_dt_[m]); + diffusion_dt_[m].resize(this->real_particles_bound_, Real(0)); + } + }; + virtual ~DiffusionReactionParticles() {}; + + std::map SpeciesIndexMap() { return species_indexes_map_; }; + + virtual DiffusionReactionParticles* + ThisObjectPtr() override { return this; }; + }; + + /** + * @class ElectroPhysiologyParticles + * @brief A group of particles with electrophysiology particle data. + */ + class ElectroPhysiologyParticles + : public DiffusionReactionParticles + { + public: + ElectroPhysiologyParticles(SPHBody* body, + DiffusionReactionMaterial* diffusion_reaction_material); + virtual ~ElectroPhysiologyParticles() {}; + virtual ElectroPhysiologyParticles* ThisObjectPtr() override { return this; }; + + }; + /** + * @class ElectroPhysiologyReducedParticles + * @brief A group of reduced particles with electrophysiology particle data. + */ + class ElectroPhysiologyReducedParticles + : public DiffusionReactionParticles + { + public: + /** Constructor. */ + ElectroPhysiologyReducedParticles(SPHBody* body, + DiffusionReactionMaterial* diffusion_reaction_material); + /** Destructor. */ + virtual ~ElectroPhysiologyReducedParticles() {}; + virtual ElectroPhysiologyReducedParticles* ThisObjectPtr() override { return this; }; + + virtual Vecd getKernelGradient(size_t particle_index_i, size_t particle_index_j, Real dW_ij, Vecd& e_ij) override + { + return dW_ij * e_ij; + }; + }; +} +#endif //DIFFUSION_REACTION_PARTICLES_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/fluid_particles.cpp b/SPHINXsys/src/shared/particles/fluid_particles.cpp new file mode 100644 index 0000000000..cfabeaefb2 --- /dev/null +++ b/SPHINXsys/src/shared/particles/fluid_particles.cpp @@ -0,0 +1,77 @@ +/** + * @file fluid_particles.cpp + * @author Xiangyu Hu and Chi Zhang + */ + +#include "fluid_particles.h" + +#include "base_body.h" +#include "weakly_compressible_fluid.h" +#include "compressible_fluid.h" +#include "xml_engine.h" + +namespace SPH +{ + //=================================================================================================// + FluidParticles::FluidParticles(SPHBody *body, Fluid* fluid) + : BaseParticles(body, fluid) + { + fluid->assignFluidParticles(this); + //---------------------------------------------------------------------- + // register particle data + //---------------------------------------------------------------------- + registerAVariable(p_, "Pressure"); + registerAVariable(drho_dt_, "DensityChangeRate"); + registerAVariable(rho_sum_, "DensitySummation"); + registerAVariable(surface_indicator_, "SurfaceIndicator"); + //---------------------------------------------------------------------- + // register sortable particle data + //---------------------------------------------------------------------- + registerASortableVariable("Position"); + registerASortableVariable("Velocity"); + registerASortableVariable("Mass"); + registerASortableVariable("Density"); + registerASortableVariable("Pressure"); + } + //=================================================================================================// + ViscoelasticFluidParticles + ::ViscoelasticFluidParticles(SPHBody *body, Oldroyd_B_Fluid* oldroyd_b_fluid) + : FluidParticles(body, oldroyd_b_fluid) + { + oldroyd_b_fluid->assignViscoelasticFluidParticles(this); + //---------------------------------------------------------------------- + // register particle data + //---------------------------------------------------------------------- + registerAVariable(tau_, "ElasticStress"); + registerAVariable(dtau_dt_, "ElasticStressChangeRate"); + //---------------------------------------------------------------------- + // register sortable particle data + //---------------------------------------------------------------------- + registerASortableVariable("ElasticStress"); + //---------------------------------------------------------------------- + // add restart output particle data + //---------------------------------------------------------------------- + addAVariableNameToList(variables_to_restart_, "ElasticStress"); + } + //=================================================================================================// + CompressibleFluidParticles + ::CompressibleFluidParticles(SPHBody *body, CompressibleFluid* compressiblefluid) + : FluidParticles(body, compressiblefluid) + { + compressiblefluid->assignCompressibleFluidParticles(this); + //---------------------------------------------------------------------- + // register particle data + //---------------------------------------------------------------------- + registerAVariable(mom_, "Momentum"); + registerAVariable(dmom_dt_, "MomentumChangeRate"); + registerAVariable(dmom_dt_prior_, "OtherMomentumChangeRate"); + registerAVariable(E_, "TotalEnergy"); + registerAVariable(dE_dt_, "TotalEnergyChangeRate"); + registerAVariable(dE_dt_prior_, "OtherEnergyChangeRate"); + //---------------------------------------------------------------------- + // add output particle data + //---------------------------------------------------------------------- + addAVariableToWrite("Pressure"); + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/particles/fluid_particles.h b/SPHINXsys/src/shared/particles/fluid_particles.h new file mode 100644 index 0000000000..deb37648c0 --- /dev/null +++ b/SPHINXsys/src/shared/particles/fluid_particles.h @@ -0,0 +1,96 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file fluid_particles.h + * @brief This is the derived class of base particle. + * @author Xiangyu Hu and Chi Zhang + */ + +#ifndef FLUID_PARTICLES_H +#define FLUID_PARTICLES_H + + + +#include "base_particles.h" + +namespace SPH { + + class Fluid; + class Oldroyd_B_Fluid; + class CompressibleFluid; + + /** + * @class FluidParticles + * @brief newtonian fluid particles. + */ + class FluidParticles : public BaseParticles + { + public: + explicit FluidParticles(SPHBody *body, Fluid *fluid); + virtual ~FluidParticles() {}; + + StdLargeVec p_; /**< pressure */ + StdLargeVec drho_dt_; /**< density change rate */ + StdLargeVec rho_sum_; /**< number density */ + StdLargeVec surface_indicator_; /**< free surface indicator */ + + virtual FluidParticles* ThisObjectPtr() override {return this;}; + }; + + /** + * @class ViscoelasticFluidParticles + * @brief Viscoelastic fluid particles. + */ + class ViscoelasticFluidParticles : public FluidParticles + { + public: + explicit ViscoelasticFluidParticles(SPHBody *body, Oldroyd_B_Fluid* oldroyd_b_fluid); + virtual ~ViscoelasticFluidParticles() {}; + + StdLargeVec tau_; /**< elastic stress */ + StdLargeVec dtau_dt_; /**< change rate of elastic stress */ + + virtual ViscoelasticFluidParticles* ThisObjectPtr() override {return this;}; + }; + + /** + * @class CompressibleFluidParticles + * @brief Compressible fluid particles. + */ + class CompressibleFluidParticles : public FluidParticles + { + public: + explicit CompressibleFluidParticles(SPHBody *body, CompressibleFluid* compressiblefluid); + virtual ~CompressibleFluidParticles() {}; + + StdLargeVec mom_; /**< momentum */ + StdLargeVec dmom_dt_; /**< change rate of momentum */ + StdLargeVec dmom_dt_prior_; + StdLargeVec E_; /**< total energy per unit volume */ + StdLargeVec dE_dt_; /**< change rate of total energy */ + StdLargeVec dE_dt_prior_; + + virtual CompressibleFluidParticles* ThisObjectPtr() override { return this; }; + }; +} +#endif //FLUID_PARTICLES_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/neighbor_relation.cpp b/SPHINXsys/src/shared/particles/neighbor_relation.cpp new file mode 100644 index 0000000000..3c62e17c24 --- /dev/null +++ b/SPHINXsys/src/shared/particles/neighbor_relation.cpp @@ -0,0 +1,175 @@ +/** + * @file neighboring_particles.cpp + * @author Xiangyu Hu and Chi Zhang + */ + +#include "neighbor_relation.h" + +#include "base_body.h" +#include "base_particles.h" + + +namespace SPH +{ + //=================================================================================================// + void Neighborhood::removeANeighbor(size_t neighbor_n) + { + current_size_ --; + j_[neighbor_n] = j_[current_size_]; + W_ij_[neighbor_n] = W_ij_[current_size_]; + dW_ij_[neighbor_n] = dW_ij_[current_size_]; + r_ij_[neighbor_n] = r_ij_[current_size_]; + e_ij_[neighbor_n] = e_ij_[current_size_]; + } + //=================================================================================================// + void NeighborRelation::createRelation(Neighborhood& neighborhood, + Real& distance, Vecd& displacement, size_t j_index) const + { + neighborhood.j_.push_back(j_index); + neighborhood.W_ij_.push_back(kernel_->W(distance, displacement)); + neighborhood.dW_ij_.push_back(kernel_->dW(distance, displacement)); + neighborhood.r_ij_.push_back(distance); + neighborhood.e_ij_.push_back(displacement / (distance + TinyReal)); + neighborhood.allocated_size_++; + } + //=================================================================================================// + void NeighborRelation::initializeRelation(Neighborhood& neighborhood, + Real& distance, Vecd& displacement, size_t j_index) const + { + size_t current_size = neighborhood.current_size_; + neighborhood.j_[current_size] = j_index; + neighborhood.W_ij_[current_size] = kernel_->W(distance, displacement); + neighborhood.dW_ij_[current_size] = kernel_->dW(distance, displacement); + neighborhood.r_ij_[current_size] = distance; + neighborhood.e_ij_[current_size] = displacement / (distance + TinyReal); + } + //=================================================================================================// + void NeighborRelation::createRelation(Neighborhood& neighborhood, Real& distance, + Vecd& displacement, size_t j_index, Real i_h_ratio, Real h_ratio_min) const + { + neighborhood.j_.push_back(j_index); + Real weight = distance < kernel_->CutOffRadius(i_h_ratio) ? + kernel_->W(i_h_ratio, distance, displacement) : 0.0; + neighborhood.W_ij_.push_back(weight); + neighborhood.dW_ij_.push_back(kernel_->dW(h_ratio_min, distance, displacement)); + neighborhood.r_ij_.push_back(distance); + neighborhood.e_ij_.push_back(displacement / (distance + TinyReal)); + neighborhood.allocated_size_++; + } + //=================================================================================================// + void NeighborRelation::initializeRelation(Neighborhood& neighborhood,Real& distance, + Vecd& displacement, size_t j_index, Real i_h_ratio, Real h_ratio_min) const + { + size_t current_size = neighborhood.current_size_; + neighborhood.j_[current_size] = j_index; + neighborhood.W_ij_[current_size] = distance < kernel_->CutOffRadius(i_h_ratio) ? + kernel_->W(i_h_ratio, distance, displacement) : 0.0; + neighborhood.dW_ij_[current_size] = kernel_->dW(h_ratio_min, distance, displacement); + neighborhood.r_ij_[current_size] = distance; + neighborhood.e_ij_[current_size] = displacement / (distance + TinyReal); + } + //=================================================================================================// + NeighborRelationInner::NeighborRelationInner(SPHBody* body) : NeighborRelation() + { + kernel_ = body->particle_adaptation_->getKernel(); + } + //=================================================================================================// + void NeighborRelationInner::operator () (Neighborhood& neighborhood, + Vecd& displacement, size_t i_index, size_t j_index) const + { + Real distance = displacement.norm(); + if (distance < kernel_->CutOffRadius() && i_index != j_index) + { + neighborhood.current_size_ >= neighborhood.allocated_size_ ? + createRelation(neighborhood, distance, displacement, j_index) + : initializeRelation(neighborhood, distance, displacement, j_index); + neighborhood.current_size_++; + } + }; + //=================================================================================================// + NeighborRelationInnerVariableSmoothingLength:: + NeighborRelationInnerVariableSmoothingLength(SPHBody* body) : NeighborRelation(), + h_ratio_(*body->base_particles_->getVariableByName("SmoothingLengthRatio")) + { + kernel_ = body->particle_adaptation_->getKernel(); + } + //=================================================================================================// + void NeighborRelationInnerVariableSmoothingLength::operator () (Neighborhood& neighborhood, + Vecd& displacement, size_t i_index, size_t j_index) const + { + Real i_h_ratio = h_ratio_[i_index]; + Real h_ratio_min = SMIN(i_h_ratio, h_ratio_[j_index]); + Real cutoff_radius = kernel_->CutOffRadius(h_ratio_min); + Real distance = displacement.norm(); + if (distance < cutoff_radius && i_index != j_index) + { + neighborhood.current_size_ >= neighborhood.allocated_size_ ? + createRelation(neighborhood, distance, displacement, j_index, i_h_ratio, h_ratio_min) + : initializeRelation(neighborhood, distance, displacement, j_index, i_h_ratio, h_ratio_min); + neighborhood.current_size_++; + } + }; + //=================================================================================================// + NeighborRelationContact:: + NeighborRelationContact(SPHBody* body, SPHBody* contact_body) : NeighborRelation() + { + Kernel* source_kernel = body->particle_adaptation_->getKernel(); + Kernel* target_kernel = contact_body->particle_adaptation_->getKernel(); + kernel_ = source_kernel->SmoothingLength() > target_kernel->SmoothingLength() ? source_kernel : target_kernel; + } + //=================================================================================================// + void NeighborRelationContact::operator () (Neighborhood& neighborhood, + Vecd& displacement, size_t i_index, size_t j_index) const + { + Real distance = displacement.norm(); + if (distance < kernel_->CutOffRadius()) + { + neighborhood.current_size_ >= neighborhood.allocated_size_ ? + createRelation(neighborhood, distance, displacement, j_index) + : initializeRelation(neighborhood, distance, displacement, j_index); + neighborhood.current_size_++; + } + }; + //=================================================================================================// + NeighborRelationSolidContact::NeighborRelationSolidContact(SPHBody* body, SPHBody* contact_body) + : NeighborRelationContact(body, contact_body) + { + Real source_smoothing_length = body->particle_adaptation_->ReferenceSmoothingLength(); + Real target_smoothing_length = contact_body->particle_adaptation_->ReferenceSmoothingLength(); + kernel_ = new KernelWendlandC2(); + kernel_->initialize(0.5 * (source_smoothing_length + target_smoothing_length)); + } + //=================================================================================================// + NeighborRelationContactBodyPart:: + NeighborRelationContactBodyPart(SPHBody* body, BodyPart* contact_body_part) + : NeighborRelation(), + part_indicator_(*contact_body_part->getBody()->base_particles_->createAVariable("BodyPartByParticleIndicator")) + { + Kernel* source_kernel = body->particle_adaptation_->getKernel(); + Kernel* target_kernel = contact_body_part->getBody()->particle_adaptation_->getKernel(); + kernel_ = source_kernel->SmoothingLength() > target_kernel->SmoothingLength() ? source_kernel : target_kernel; + + BodyPartByParticle* contact_body_part_by_particle = dynamic_cast(contact_body_part); + IndexVector part_particles = contact_body_part_by_particle->body_part_particles_; + + for (size_t i = 0; i != part_particles.size(); ++i) + { + part_indicator_[part_particles[i]] = 1; + } + } + //=================================================================================================// + void NeighborRelationContactBodyPart::operator () (Neighborhood& neighborhood, + Vecd& displacement, size_t i_index, size_t j_index) const + { + Real distance = displacement.norm(); + if (distance < kernel_->CutOffRadius() && part_indicator_[j_index] == 1) + { + neighborhood.current_size_ >= neighborhood.allocated_size_ ? + createRelation(neighborhood, distance, displacement, j_index) + : initializeRelation(neighborhood, distance, displacement, j_index); + neighborhood.current_size_++; + } + } + //=================================================================================================// +} +//=================================================================================================// diff --git a/SPHINXsys/src/shared/particles/neighbor_relation.h b/SPHINXsys/src/shared/particles/neighbor_relation.h new file mode 100644 index 0000000000..cbd6c989d6 --- /dev/null +++ b/SPHINXsys/src/shared/particles/neighbor_relation.h @@ -0,0 +1,162 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file neighbor_relation.h + * @brief There are the classes for neighboring particles. + * It saves the information for carring out pair + * interaction, and also considered as the topology of the particles. + * @author Xiangyu Hu and Chi Zhang + */ + +#ifndef NEIGHBOR_RELATION_H +#define NEIGHBOR_RELATION_H + + +#include "base_data_package.h" +#include "all_kernels.h" + +namespace SPH { + + class SPHBody; + class BodyPart; + + /** + * @class Neighborhood + * @brief A neighborhood around particle i. + */ + class Neighborhood + { + public: + size_t current_size_; /**< the current number of neighors */ + size_t allocated_size_; /**< the limit of neighors does not require memory allocation */ + + StdLargeVec j_; /**< index of the neighbor particle. */ + StdLargeVec W_ij_; /**< kernel value or particle volume contribution */ + StdLargeVec dW_ij_; /**< derivative of kernel function or inter-particle surface contribution */ + StdLargeVec r_ij_; /**< distance between j and i. */ + StdLargeVec e_ij_; /**< unit vector pointing from j to i or inter-particle surface direction */ + + Neighborhood() : current_size_(0), allocated_size_(0) {}; + ~Neighborhood() {}; + + void removeANeighbor(size_t neighbor_n); + }; + + /** Inner neighborhoods for all particles in a body for a inner body relation. */ + using ParticleConfiguration = StdLargeVec; + /** All contact neighborhoods for all particles in a body for a contact body relation. */ + using ContatcParticleConfiguration = StdVec; + + /** + * @class NeighborRelation + * @brief Base neighbor relation between particles i and j. + */ + class NeighborRelation + { + protected: + Kernel* kernel_; + //---------------------------------------------------------------------- + // Below are for constant smoothing length. + //---------------------------------------------------------------------- + void createRelation(Neighborhood& neighborhood, Real& distance, + Vecd& displacement, size_t j_index) const; + void initializeRelation(Neighborhood& neighborhood, Real& distance, + Vecd& displacement, size_t j_index) const; + //---------------------------------------------------------------------- + // Below are for variable smoothing length. + //---------------------------------------------------------------------- + void createRelation(Neighborhood& neighborhood, Real& distance, + Vecd& displacement, size_t j_index, Real i_h_ratio, Real h_ratio_min) const; + void initializeRelation(Neighborhood& neighborhood, Real& distance, + Vecd& displacement, size_t j_index, Real i_h_ratio, Real h_ratio_min) const; + public: + NeighborRelation() : kernel_(nullptr) {}; + virtual ~NeighborRelation() {}; + }; + + /** + * @class NeighborRelationInner + * @brief A inner neighbor relation functor between particles i and j. + */ + class NeighborRelationInner : public NeighborRelation + { + public: + NeighborRelationInner(SPHBody* body); + void operator () (Neighborhood& neighborhood, + Vecd& displacement, size_t i_index, size_t j_index) const; + }; + + /** + * @class NeighborRelationInnerVariableSmoothingLength + * @brief A inner neighbor relation functor between particles i and j. + */ + class NeighborRelationInnerVariableSmoothingLength : public NeighborRelation + { + public: + NeighborRelationInnerVariableSmoothingLength(SPHBody* body); + void operator () (Neighborhood& neighborhood, + Vecd& displacement, size_t i_index, size_t j_index) const; + protected: + StdLargeVec& h_ratio_; + }; + + /** + * @class NeighborRelationContact + * @brief A contact neighbor relation functor between particles i and j. + */ + class NeighborRelationContact : public NeighborRelation + { + public: + NeighborRelationContact(SPHBody* body, SPHBody* contact_body); + virtual ~NeighborRelationContact() {}; + void operator () (Neighborhood& neighborhood, + Vecd& displacement, size_t i_index, size_t j_index) const; + }; + + /** + * @class NeighborRelationSolidContact + * @brief A solid contact neighbor relation functor between particles i and j. + */ + class NeighborRelationSolidContact : public NeighborRelationContact + { + public: + NeighborRelationSolidContact(SPHBody* body, SPHBody* contact_body); + virtual ~NeighborRelationSolidContact() {}; + }; + + /** + * @class NeighborRelationContactBodyPart + * @brief A contact neighbor relation functor between particles i and j. + */ + class NeighborRelationContactBodyPart : public NeighborRelation + { + public: + NeighborRelationContactBodyPart(SPHBody* body, BodyPart* contact_body_part); + virtual ~NeighborRelationContactBodyPart() {}; + void operator () (Neighborhood& neighborhood, + Vecd& displacement, size_t i_index, size_t j_index) const; + protected: + StdLargeVec& part_indicator_; /**< indicator of the body part */ + }; +} +#endif //NEIGHBOR_RELATION_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/particle_adaptation.cpp b/SPHINXsys/src/shared/particles/particle_adaptation.cpp new file mode 100644 index 0000000000..3ec8370dc7 --- /dev/null +++ b/SPHINXsys/src/shared/particles/particle_adaptation.cpp @@ -0,0 +1,181 @@ +/** + * @file particle_adaptation.cpp + * @brief Definition of functions declared in particle_adaptation.h + * @author Xiangyu Hu and Chi Zhang + */ + +#include "particle_adaptation.h" + +#include "sph_system.h" +#include "all_kernels.h" +#include "base_body.h" +#include "base_particles.h" +#include "cell_linked_list.h" +#include "level_set.h" + +namespace SPH +{ + //=================================================================================================// + ParticleAdaptation::ParticleAdaptation(Real h_spacing_ratio, Real system_resolution_ratio) + : h_spacing_ratio_(h_spacing_ratio), + system_resolution_ratio_(system_resolution_ratio), + local_refinement_level_(0), + local_coarse_level_(local_refinement_level_ / 2), + spacing_ref_(0), h_ref_(0), + spacing_min_(0), spacing_ratio_min_(1.0), + spacing_ratio_max_(1.0), h_ratio_min_(1.0), h_ratio_max_(1.0), + number_density_min_(1.0), number_density_max_(1.0), + kernel_(new KernelWendlandC2()), + sph_body_(nullptr), system_domain_bounds_(), + base_particles_(nullptr){}; + //=================================================================================================// + void ParticleAdaptation::initialize(SPHBody *sph_body) + { + sph_body_ = sph_body; + system_domain_bounds_ = sph_body_->getSPHSystem().system_domain_bounds_; + spacing_ref_ = sph_body_->getSPHSystem().resolution_ref_ / system_resolution_ratio_; + h_ref_ = h_spacing_ratio_ * spacing_ref_; + kernel_->initialize(h_ref_); + spacing_min_ = RefinedSpacing(spacing_ref_, local_refinement_level_); + spacing_ratio_min_ = powerN(0.5, local_refinement_level_); + spacing_ratio_max_ = powerN(2.0, local_coarse_level_); + h_ratio_min_ = powerN(0.5, local_coarse_level_); + h_ratio_max_ = powerN(2.0, local_refinement_level_); + number_density_min_ = computeReferenceNumberDensity(Vecd(0), h_ratio_max_); + number_density_max_ = computeReferenceNumberDensity(Vecd(0), h_ratio_min_); + } + //=================================================================================================// + void ParticleAdaptation::replaceKernel(Kernel *another_kernel) + { + delete kernel_; + kernel_ = another_kernel; + } + //=================================================================================================// + Real ParticleAdaptation:: + RefinedSpacing(Real coarse_particle_spacing, int refinement_level) + { + return coarse_particle_spacing / powerN(2.0, refinement_level); + } + //=================================================================================================// + Real ParticleAdaptation::computeReferenceNumberDensity(Vec2d zero, Real h_ratio) + { + Real sigma(0); + Real cutoff_radius = kernel_->CutOffRadius(h_ratio); + Real particle_spacing = ReferenceSpacing() / h_ratio; + int search_depth = int(cutoff_radius / particle_spacing) + 1; + for (int j = -search_depth; j <= search_depth; ++j) + for (int i = -search_depth; i <= search_depth; ++i) + { + Vec2d particle_location(Real(i) * particle_spacing, Real(j) * particle_spacing); + Real distance = particle_location.norm(); + if (distance < cutoff_radius) + sigma += kernel_->W(h_ratio, distance, particle_location); + } + return sigma; + } + //=================================================================================================// + Real ParticleAdaptation::computeReferenceNumberDensity(Vec3d zero, Real h_ratio) + { + Real sigma(0); + Real cutoff_radius = kernel_->CutOffRadius(h_ratio); + Real particle_spacing = ReferenceSpacing() / h_ratio; + int search_depth = int(cutoff_radius / particle_spacing) + 1; + for (int k = -search_depth; k <= search_depth; ++k) + for (int j = -search_depth; j <= search_depth; ++j) + for (int i = -search_depth; i <= search_depth; ++i) + { + Vec3d particle_location(Real(i) * particle_spacing, + Real(j) * particle_spacing, Real(k) * particle_spacing); + Real distance = particle_location.norm(); + if (distance < cutoff_radius) + sigma += kernel_->W(h_ratio, distance, particle_location); + } + return sigma; + } + //=================================================================================================// + Real ParticleAdaptation::ReferenceNumberDensity() + { + return computeReferenceNumberDensity(Vecd(0), 1.0); + } + //=================================================================================================// + Real ParticleAdaptation::probeNumberDensity(Vecd zero, Real h_ratio) + { + Real alpha = (h_ratio_max_ - h_ratio) / (h_ratio_max_ - h_ratio_min_ + TinyReal); + + return alpha * number_density_max_ + (1.0 - alpha) * number_density_min_; + } + //=================================================================================================// + void ParticleAdaptation::assignBaseParticles(BaseParticles *base_particles) + { + base_particles_ = base_particles; + } + //=================================================================================================// + BaseCellLinkedList *ParticleAdaptation::createCellLinkedList() + { + return new CellLinkedList(system_domain_bounds_, kernel_->CutOffRadius(), *sph_body_, *this); + } + //=================================================================================================// + BaseLevelSet *ParticleAdaptation::createLevelSet(ComplexShape &complex_shape) + { + return new LevelSet(complex_shape.findBounds(), ReferenceSpacing(), complex_shape, *this); + } + //=================================================================================================// + ParticleWithLocalRefinement:: + ParticleWithLocalRefinement(Real h_spacing_ratio, + Real system_resolution_ratio, int local_refinement_level) + : ParticleAdaptation(h_spacing_ratio, system_resolution_ratio) + { + local_refinement_level_ = local_refinement_level; + local_coarse_level_ = local_refinement_level_ / 2; + } + //=================================================================================================// + size_t ParticleWithLocalRefinement::getCellLinkedListTotalLevel() + { + return size_t(local_coarse_level_ + local_refinement_level_); + } + //=================================================================================================// + size_t ParticleWithLocalRefinement::getLevelSetTotalLevel() + { + return getCellLinkedListTotalLevel() + 1; + } + //=================================================================================================// + void ParticleWithLocalRefinement::assignBaseParticles(BaseParticles *base_particles) + { + ParticleAdaptation::assignBaseParticles(base_particles); + base_particles->registerAVariable(h_ratio_, "SmoothingLengthRatio", 1.0); + } + //=================================================================================================// + BaseCellLinkedList *ParticleWithLocalRefinement::createCellLinkedList() + { + return new MultilevelCellLinkedList(system_domain_bounds_, kernel_->CutOffRadius(), + getCellLinkedListTotalLevel(), + MaximumSpacingRatio(), *sph_body_, *this); + } + //=================================================================================================// + BaseLevelSet *ParticleWithLocalRefinement::createLevelSet(ComplexShape &complex_shape) + { + return new MultilevelLevelSet(complex_shape.findBounds(), + ReferenceSpacing(), getLevelSetTotalLevel(), + MaximumSpacingRatio(), complex_shape, *this); + } + //=================================================================================================// + ParticleSpacingByBodyShape:: + ParticleSpacingByBodyShape(Real smoothing_length_ratio, + Real system_resolution_ratio, int local_refinement_level) + : ParticleWithLocalRefinement(smoothing_length_ratio, + system_resolution_ratio, local_refinement_level){}; + //=================================================================================================// + Real ParticleSpacingByBodyShape::getLocalSpacing(ComplexShape &complex_shape, Vecd &position) + { + Real phi = abs(complex_shape.findSignedDistance(position)); + Real ratio_ref = phi / (2.0 * spacing_ref_ * spacing_ratio_max_); + Real target_ratio = spacing_ratio_max_; + if (ratio_ref < kernel_->KernelSize()) + { + Real weight = kernel_->W_1D(ratio_ref); + target_ratio = weight * spacing_ratio_min_ + (1.0 - weight) * spacing_ratio_max_; + } + return target_ratio * spacing_ref_; + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/particles/particle_adaptation.h b/SPHINXsys/src/shared/particles/particle_adaptation.h new file mode 100644 index 0000000000..1720f7490a --- /dev/null +++ b/SPHINXsys/src/shared/particles/particle_adaptation.h @@ -0,0 +1,144 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file particle_adaptation.h + * @brief Particle adaptation is for adaptivity of SPH particles. + * The particle adaptation is defined before SPH body. + * @author Xiangyu Hu and Chi Zhang + */ + +#ifndef PARTICLE_ADAPTATION_H +#define PARTICLE_ADAPTATION_H + +#include "base_data_package.h" +#include "sph_data_conainers.h" + +namespace SPH +{ + + class SPHBody; + class ComplexShape; + class Kernel; + class BaseCellLinkedList; + class BaseLevelSet; + class BaseParticles; + + /** + * @class ParticleAdaptation + * @brief Base class for particle adaptation + * The base class defined essential global parameteres. + * It is also used for single resolution SPH method, + * where constructor parameter system_resolution_ratio defines . + * The derived class will be used if further adaptation is introduced. + */ + class ParticleAdaptation + { + protected: + Real h_spacing_ratio_; /**< ratio of reference kernel smoothing length to particle spacing */ + Real system_resolution_ratio_; /**< ratio of body resolution to system resolution, set to 1.0 by default */ + int local_refinement_level_; /**< refinement level respect to reference particle spacing */ + int local_coarse_level_; //TODO: try to delete this because it leads to confusion on particle resolutions + Real spacing_ref_; /**< reference particle spacing used to determine local particle spacing */ + Real h_ref_; /**< reference particle spacing used to determine local particle smoothing length */ + Real spacing_min_; /**< minimum particle spacing determined by local refinement level */ + Real spacing_ratio_min_; + Real spacing_ratio_max_; + Real h_ratio_min_; + Real h_ratio_max_; + Real number_density_min_; + Real number_density_max_; + + Kernel *kernel_; + SPHBody *sph_body_; + BoundingBox system_domain_bounds_; + BaseParticles *base_particles_; + + public: + ParticleAdaptation(Real h_spacing_ratio = 1.3, Real system_resolution_ratio = 1.0); + virtual ~ParticleAdaptation(){}; + /** Note: called after construction of this and derived classes. */ + virtual void initialize(SPHBody *sph_body); + + int LocalRefinementLevel() { return local_refinement_level_; }; + Real ReferenceSpacing() { return spacing_ref_; }; + Real ReferenceSmoothingLength() { return h_ref_; }; + Kernel *getKernel() { return kernel_; }; + /**Note: replace a kernel should be done before kernel initialization */ + void replaceKernel(Kernel *another_kernel); + Real MinimumSpacing() { return spacing_min_; }; + Real MinimumSpacingRatio() { return spacing_ratio_min_; }; + Real MaximumSpacingRatio() { return spacing_ratio_max_; }; + Real computeReferenceNumberDensity(Vec2d zero, Real h_ratio); + Real computeReferenceNumberDensity(Vec3d zero, Real h_ratio); + Real ReferenceNumberDensity(); + Real probeNumberDensity(Vecd zero, Real h_ratio); + virtual Real SmoothingLengthRatio(size_t particle_index_i) { return 1.0; }; + + virtual void assignBaseParticles(BaseParticles *base_particles); + virtual BaseCellLinkedList *createCellLinkedList(); + virtual BaseLevelSet *createLevelSet(ComplexShape &complex_shape); + protected: + Real RefinedSpacing(Real coarse_particle_spacing, int refinement_level); + }; + + /** + * @class ParticleWithLocalRefinement + * @brief Base class for particle with refinement. + */ + class ParticleWithLocalRefinement : public ParticleAdaptation + { + public: + StdLargeVec h_ratio_; /**< the ratio between reference smoothing length to variable smoothing length */ + + ParticleWithLocalRefinement(Real h_spacing_ratio_, + Real system_resolution_ratio, + int local_refinement_level); + virtual ~ParticleWithLocalRefinement(){}; + + size_t getCellLinkedListTotalLevel(); + size_t getLevelSetTotalLevel(); + virtual Real SmoothingLengthRatio(size_t particle_index_i) override + { + return h_ratio_[particle_index_i]; + }; + + virtual void assignBaseParticles(BaseParticles *base_particles) override; + virtual BaseCellLinkedList *createCellLinkedList() override; + virtual BaseLevelSet *createLevelSet(ComplexShape &complex_shape) override; + }; + /** + * @class ParticleSpacingByBodyShape + * @brief Adaptive resolutions within a SPH body according to the distance to the body surface. + */ + class ParticleSpacingByBodyShape : public ParticleWithLocalRefinement + { + public: + ParticleSpacingByBodyShape(Real smoothing_length_ratio, + Real system_resolution_ratio, + int local_refinement_level); + virtual ~ParticleSpacingByBodyShape(){}; + + Real getLocalSpacing(ComplexShape &complex_shape, Vecd &position); + }; +} +#endif //PARTICLE_ADAPTATION_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/particle_sorting.cpp b/SPHINXsys/src/shared/particles/particle_sorting.cpp new file mode 100644 index 0000000000..eab4b89f18 --- /dev/null +++ b/SPHINXsys/src/shared/particles/particle_sorting.cpp @@ -0,0 +1,63 @@ +/** + * @file particle_sorting.cpp + * @author Xiangyu Hu + */ + +#include "particle_sorting.h" + +#include "base_body.h" +#include "base_particles.h" +#include "cell_linked_list.h" + +namespace SPH { + //=================================================================================================// + SwapParticleData::SwapParticleData(BaseParticles* base_particles) : + sequence_(base_particles->sequence_), + unsorted_id_(base_particles->unsorted_id_), + sortable_data_(base_particles->sortable_data_) {} + //=================================================================================================// + void SwapParticleData::operator () (size_t* a, size_t* b) + { + std::swap(*a, *b); + + size_t index_a = a - sequence_.data(); + size_t index_b = b - sequence_.data(); + std::swap(unsorted_id_[index_a], unsorted_id_[index_b]); + loopParticleData(sortable_data_, index_a, index_b); + } + //=================================================================================================// + ParticleSorting::ParticleSorting(RealBody* real_body) : + base_particles_(nullptr), swap_particle_data_(nullptr), compare_(), + quick_sort_particle_range_(nullptr), quick_sort_particle_body_() {} + //=================================================================================================// + void ParticleSorting::sortingParticleData(size_t* begin, size_t size) + { + quick_sort_particle_range_->begin_ = begin; + quick_sort_particle_range_->size_ = size; + parallel_for(*quick_sort_particle_range_, quick_sort_particle_body_, ap); + updateSortedId(); + } + //=================================================================================================// + void ParticleSorting::updateSortedId() + { + StdLargeVec& unsorted_id = base_particles_->unsorted_id_; + StdLargeVec& sorted_id = base_particles_->sorted_id_; + size_t total_real_particles = base_particles_->total_real_particles_; + parallel_for(blocked_range(0, total_real_particles), + [&](const blocked_range& r) { + for (size_t i = r.begin(); i != r.end(); ++i) { + sorted_id[unsorted_id[i]] = i; + } + }, ap); + } + //=================================================================================================// + void ParticleSorting::assignBaseParticles(BaseParticles* base_particles) + { + base_particles_ = base_particles; + swap_particle_data_ = new SwapParticleData(base_particles); + size_t* begin = base_particles_->sequence_.data(); + quick_sort_particle_range_ = new tbb::interafce9::internal::QuickSortParticleRange(begin, 0, compare_, *swap_particle_data_); + }; + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/particles/particle_sorting.h b/SPHINXsys/src/shared/particles/particle_sorting.h new file mode 100644 index 0000000000..937960f252 --- /dev/null +++ b/SPHINXsys/src/shared/particles/particle_sorting.h @@ -0,0 +1,265 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ + +/** +* @file particle_sorting.h +* @brief Here gives the classes for particle sorting. +* @author Xiangyu Hu +*/ + + +#ifndef PARTICLE_SORTING_H +#define PARTICLE_SORTING_H + + + +#include "base_data_package.h" +#include "sph_data_conainers.h" + +/** this is a reformulation of tbb parallel_sort for particle data */ +namespace tbb { + namespace interafce9 { + namespace internal { + + #ifdef TBB_2021_2_0 + using tbb::detail::no_assign; + #else + using tbb::internal::no_assign; + #endif + + /** sorting particle */ + template + class QuickSortParticleRange : private no_assign { + + inline size_t median_of_three(const RandomAccessIterator& array, size_t l, size_t m, size_t r) const { + return comp_(array[l], array[m]) ? (comp_(array[m], array[r]) ? m : (comp_(array[l], array[r]) ? r : l)) + : (comp_(array[r], array[m]) ? m : (comp_(array[r], array[l]) ? r : l)); + } + + inline size_t PseudoMedianOfNine(const RandomAccessIterator& array, const QuickSortParticleRange& range) const { + size_t offset = range.size_ / 8u; + return median_of_three(array, + median_of_three(array, 0, offset, offset * 2), + median_of_three(array, offset * 3, offset * 4, offset * 5), + median_of_three(array, offset * 6, offset * 7, range.size_ - 1)); + + } + + size_t splitRange(QuickSortParticleRange& range) { + RandomAccessIterator array = range.begin_; + RandomAccessIterator key0 = range.begin_; + size_t m = PseudoMedianOfNine(array, range); + if (m) swap_particle_data_(array, array + m); + + size_t i = 0; + size_t j = range.size_; + // Partition interval [i+1,j-1] with key *key0. + for (;;) { + __TBB_ASSERT(i < j, nullptr); + // Loop must terminate since array[l]==*key0. + do { + --j; + __TBB_ASSERT(i <= j, "bad ordering relation?"); + } while (comp_(*key0, array[j])); + do { + __TBB_ASSERT(i <= j, nullptr); + if (i == j) goto quick_sort_particle_partition; + ++i; + } while (comp_(array[i], *key0)); + if (i == j) goto quick_sort_particle_partition; + swap_particle_data_(array + i, array + j); + } + quick_sort_particle_partition: + // Put the partition key were it belongs + swap_particle_data_(array + j, key0); + // array[l..j) is less or equal to key. + // array(j..r) is greater or equal to key. + // array[j] is equal to key + i = j + 1; + size_t new_range_size = range.size_ - i; + range.size_ = j; + return new_range_size; + } + + public: + + static const size_t grainsize_ = 500; + const Compare& comp_; + SwapType& swap_particle_data_; + size_t size_; + RandomAccessIterator begin_; + + QuickSortParticleRange(RandomAccessIterator begin, + size_t size, const Compare& compare, SwapType& swap_particle_data) : + comp_(compare), swap_particle_data_(swap_particle_data), + size_(size), begin_(begin) {} + + bool empty() const { return size_ == 0; } + bool is_divisible() const { return size_ >= grainsize_; } + + QuickSortParticleRange(QuickSortParticleRange& range, split) + : comp_(range.comp_), swap_particle_data_(range.swap_particle_data_) + , size_(splitRange(range)) + // +1 accounts for the pivot element, which is at its correct place + // already and, therefore, is not included into subranges. + , begin_(range.begin_ + range.size_ + 1) {} + }; + + /* + Description : QuickSort in Iterator format + Link : https://stackoverflow.com/a/54976413/3547485 + Ref : http://www.cs.fsu.edu/~lacher/courses/COP4531/lectures/sorts/slide09.html + */ + template + RandomAccessIterator Partition(RandomAccessIterator first, RandomAccessIterator last, Compare& compare, SwapType& swap_particle_data) + { + auto pivot = std::prev(last, 1); + auto i = first; + for (auto j = first; j != pivot; ++j) { + // bool format + if (compare(*j, *pivot)) { + swap_particle_data(i++, j); + } + } + swap_particle_data(i, pivot); + return i; + } + + template + void SerialQuickSort(RandomAccessIterator first, RandomAccessIterator last, Compare& compare, SwapType& swap_particle_data) + { + if (std::distance(first, last) > 1) { + RandomAccessIterator bound = Partition(first, last, compare, swap_particle_data); + SerialQuickSort(first, bound, compare, swap_particle_data); + SerialQuickSort(bound + 1, last, compare, swap_particle_data); + } + } + + /* + Description : Insertsort in Iterator format + Link : http://www.codecodex.com/wiki/Insertion_sort + */ + + template< typename RandomAccessIterator, typename Compare, typename SwapType> + void InsertionSort(RandomAccessIterator First, RandomAccessIterator Last, Compare& compare, SwapType& swap_particle_data) + { + RandomAccessIterator min = First; + for (RandomAccessIterator i = First + 1; i < Last; ++i) + if (compare(*i, *min)) min = i; + + swap_particle_data(First, min); + while (++First < Last) + for (RandomAccessIterator j = First; compare(*j, *(j - 1)); --j) + swap_particle_data((j - 1), j); + } + + /** Body class used to sort elements in a range that is smaller than the grainsize. */ + template + struct QuickSortParticleBody { + void operator()(const QuickSortParticleRange& range) const { + SerialQuickSort(range.begin_, range.begin_ + range.size_, range.comp_, range.swap_particle_data_); + } + }; + + } + } +} + +namespace SPH { + + class RealBody; + class BaseParticles; + class BaseCellLinkedList; + + /** + * @class CompareParticleSequence + * @brief compare the sequence of two particles + */ + struct CompareParticleSequence + { + bool operator () (const size_t& x, const size_t& y) const + { return x < y; }; + }; + + /** + * @class SwapParticleData + * @brief swap sortable particle data according to a sequence + */ + class SwapParticleData + { + protected: + StdLargeVec& sequence_; + StdLargeVec& unsorted_id_; + ParticleData& sortable_data_; + + template + struct swapParticleDataValue + { + void operator () (ParticleData& particle_data, size_t index_a, size_t index_b) const + { + StdVec*> variables = std::get(particle_data); + for (size_t i = 0; i != variables.size(); ++i) + { + StdLargeVec& variable= *variables[i]; + std::swap(variable[index_a], variable[index_b]); + } + }; + }; + + public: + SwapParticleData(BaseParticles* base_particles); + ~SwapParticleData() {}; + + /** the operater overload for swapping particle data. + * the arguments are the same with std::iter_swap + */ + void operator () (size_t* a, size_t* b); + }; + + /** + * @class ParticleSorting + * @brief The class for sorting particle according a given sequence. + */ + class ParticleSorting + { + protected: + BaseParticles* base_particles_; + + SwapParticleData* swap_particle_data_; + CompareParticleSequence compare_; + tbb::interafce9::internal::QuickSortParticleRange* quick_sort_particle_range_; + tbb::interafce9::internal::QuickSortParticleBody quick_sort_particle_body_; + public: + ParticleSorting(RealBody* real_body); + virtual ~ParticleSorting() {}; + + void assignBaseParticles(BaseParticles* base_particles); + /** sorting particle data according to the cell location of particles */ + virtual void sortingParticleData(size_t* begin, size_t size); + /** update the reference of sorted data from unsorted data */ + virtual void updateSortedId(); + }; +} +#endif //PARTICLE_SORTING_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particles/solid_particles.cpp b/SPHINXsys/src/shared/particles/solid_particles.cpp new file mode 100644 index 0000000000..b0421c9cfc --- /dev/null +++ b/SPHINXsys/src/shared/particles/solid_particles.cpp @@ -0,0 +1,173 @@ +/** + * @file solid_particles.cpp + * @brief Definition of functions declared in solid_particles.h + * @author Xiangyu Hu and Chi Zhang + */ +#include "solid_particles.h" + +#include "geometry.h" +#include "base_body.h" +#include "elastic_solid.h" +#include "inelastic_solid.h" +#include "xml_engine.h" + +namespace SPH { + //=============================================================================================// + SolidParticles::SolidParticles(SPHBody* body) + : SolidParticles(body, new Solid()){} + //=============================================================================================// + SolidParticles::SolidParticles(SPHBody* body, Solid* solid) + : BaseParticles(body, solid) + { + solid->assignSolidParticles(this); + //---------------------------------------------------------------------- + // register particle data + //---------------------------------------------------------------------- + registerAVariable(pos_0_, "InitialPosition"); + registerAVariable(n_, "NormalDirection"); + registerAVariable(n_0_, "InitialNormalDirection"); + registerAVariable(B_, "CorrectionMatrix", Matd(1.0)); + //---------------------------------------------------------------------- + // for FSI + //---------------------------------------------------------------------- + registerAVariable(vel_ave_, "AverageVelocity"); + registerAVariable(dvel_dt_ave_, "AverageAcceleration"); + registerAVariable(force_from_fluid_, "ForceFromFluid"); + //---------------------------------------------------------------------- + // For solid-solid contact + //---------------------------------------------------------------------- + registerAVariable(contact_density_, "ContactDensity"); + registerAVariable(contact_force_, "ContactForce"); + //----------------------------------------------------------------------------------------- + // register sortable particle data before building up particle configuration + //----------------------------------------------------------------------------------------- + registerASortableVariable("Position"); + registerASortableVariable("InitialPosition"); + registerASortableVariable("Volume"); + //set the initial value for initial particle position + for (size_t i = 0; i != pos_n_.size(); ++i) pos_0_[i] = pos_n_[i]; + //sorting particle once + //dynamic_cast(body)->sortParticleWithCellLinkedList(); + } + //=============================================================================================// + void SolidParticles::offsetInitialParticlePosition(Vecd offset) + { + for (size_t i = 0; i != total_real_particles_; ++i) + { + pos_n_[i] += offset; + pos_0_[i] += offset; + } + } + //=================================================================================================// + void SolidParticles::initializeNormalDirectionFromGeometry() + { + ComplexShape* body_shape = body_->body_shape_; + for (size_t i = 0; i != total_real_particles_; ++i) + { + Vecd normal_direction = body_shape->findNormalDirection(pos_n_[i]); + n_[i] = normal_direction; + n_0_[i] = normal_direction; + } + } + //=================================================================================================// + Vecd SolidParticles::normalizeKernelGradient(size_t particle_index_i, Vecd& kernel_gradient) + { + return B_[particle_index_i] * kernel_gradient; + } + //=================================================================================================// + Vecd SolidParticles::getKernelGradient(size_t particle_index_i, size_t particle_index_j, Real dW_ij, Vecd& e_ij) + { + return 0.5 * dW_ij * (B_[particle_index_i] + B_[particle_index_j]) * e_ij; + } + //=============================================================================================// + ElasticSolidParticles::ElasticSolidParticles(SPHBody* body, ElasticSolid* elastic_solid) + : SolidParticles(body, elastic_solid) + { + elastic_solid->assignElasticSolidParticles(this); + //---------------------------------------------------------------------- + // register particle data + //---------------------------------------------------------------------- + registerAVariable(F_, "DeformationGradient", Matd(1.0)); + registerAVariable(dF_dt_, "DeformationRate"); + registerAVariable(stress_PK1_, "FirstPiolaKirchhoffStress"); + //---------------------------------------------------------------------- + // add restart output particle data + //---------------------------------------------------------------------- + addAVariableNameToList(variables_to_restart_, "DeformationGradient"); + } + //=================================================================================================// + void ElasticSolidParticles::writeParticlesToVtuFile(std::ofstream& output_file) + { + SolidParticles::writeParticlesToVtuFile(output_file); + + size_t total_real_particles = total_real_particles_; + + output_file << " \n"; + output_file << " "; + for (size_t i = 0; i != total_real_particles; ++i) { + output_file << std::fixed << std::setprecision(9) << von_Mises_stress(i) << " "; + } + output_file << std::endl; + output_file << " \n"; + } + //=================================================================================================// + void ElasticSolidParticles::writePltFileHeader(std::ofstream& output_file) + { + SolidParticles::writePltFileHeader(output_file); + + output_file << ",\" von Mises stress \""; + } + //=================================================================================================// + void ElasticSolidParticles::writePltFileParticleData(std::ofstream& output_file, size_t index_i) + { + SolidParticles::writePltFileParticleData(output_file, index_i); + + output_file << von_Mises_stress(index_i) << " "; + } + //=============================================================================================// + void ActiveMuscleParticles::initializeActiveMuscleParticleData() + { + //---------------------------------------------------------------------- + // register particle data + //---------------------------------------------------------------------- + registerAVariable(active_stress_, "ActiveStress"); + registerAVariable(active_contraction_stress_, "ActiveContractionStress"); + //---------------------------------------------------------------------- + // add restart output particle data + //---------------------------------------------------------------------- + addAVariableNameToList(variables_to_restart_, "ActiveContractionStress"); + } + //=============================================================================================// + ShellParticles::ShellParticles(SPHBody* body, ElasticSolid* elastic_solid, Real thickness) + : ElasticSolidParticles(body, elastic_solid) + { + elastic_solid->assignElasticSolidParticles(this); + //---------------------------------------------------------------------- + // register particle data + //---------------------------------------------------------------------- + registerAVariable(transformation_matrix_, "TransformationMatrix", Matd(1.0)); + registerAVariable(shell_thickness_, "Thickness", thickness); + registerAVariable(pseudo_n_, "PseudoNormal"); + registerAVariable(dpseudo_n_dt_, "PseudoNormalChangeRate"); + registerAVariable(dpseudo_n_d2t_, "PseudoNormal2ndOrderTimeDerivative"); + registerAVariable(rotation_, "Rotation"); + registerAVariable(angular_vel_, "AngularVelocity"); + registerAVariable(dangular_vel_dt_, "AngularAcceleration"); + registerAVariable(F_bending_, "BendingDeformationGradient"); + registerAVariable(dF_bending_dt_, "BendingDeformationGradientChangeRate"); + registerAVariable(global_shear_stress_, "GlobalShearStress"); + registerAVariable(global_stress_, "GlobalStress"); + registerAVariable(global_moment_, "GlobalMoment"); + //---------------------------------------------------------------------- + // add basic output particle data + //---------------------------------------------------------------------- + addAVariableToWrite("Rotation"); + //---------------------------------------------------------------------- + // add restart output particle data + //---------------------------------------------------------------------- + addAVariableNameToList(variables_to_restart_, "PseudoNormal"); + addAVariableNameToList(variables_to_restart_, "Rotation"); + addAVariableNameToList(variables_to_restart_, "AngularVelocity"); + } + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/particles/solid_particles.h b/SPHINXsys/src/shared/particles/solid_particles.h new file mode 100644 index 0000000000..a87f0e9b08 --- /dev/null +++ b/SPHINXsys/src/shared/particles/solid_particles.h @@ -0,0 +1,173 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file solid_particles.h + * @brief This is the derived class of base particles. + * @author Xiangyu Hu and Chi Zhang + */ + +#ifndef SOLID_PARTICLES_H +#define SOLID_PARTICLES_H + + +#include "base_particles.h" + +namespace SPH { + + //---------------------------------------------------------------------- + // preclaimed classes + //---------------------------------------------------------------------- + class Solid; + class ElasticSolid; + class PlasticSolid; + template class ActiveMuscle; + + /** + * @class SolidParticles + * @brief A group of particles with solid body particle data. + */ + class SolidParticles : public BaseParticles + { + public: + SolidParticles(SPHBody* body); + SolidParticles(SPHBody* body, Solid* solid); + virtual ~SolidParticles() {}; + + StdLargeVec pos_0_; /**< initial position */ + StdLargeVec n_; /**< current normal direction */ + StdLargeVec n_0_; /**< inital normal direction */ + StdLargeVec B_; /**< configuration correction for linear reproducing */ + //---------------------------------------------------------------------- + // for fluid-structure interaction (FSI) + //---------------------------------------------------------------------- + StdLargeVec vel_ave_; /**< fluid time-step averaged particle velocity */ + StdLargeVec dvel_dt_ave_; /**< fluid time-step averaged particle acceleration */ + StdLargeVec force_from_fluid_; /**< forces (including pressure and viscous) from fluid */ + //---------------------------------------------------------------------- + // for solid-solid contact dynamics + //---------------------------------------------------------------------- + StdLargeVec contact_density_; /**< density due to contact of solid-solid. */ + StdLargeVec contact_force_; /**< contact force from other solid body or bodies */ + + void offsetInitialParticlePosition(Vecd offset); + void initializeNormalDirectionFromGeometry(); + void ParticleTranslationAndRotation(Transformd& transform); + + /** Normalize a gradient. */ + virtual Vecd normalizeKernelGradient(size_t particle_index_i, Vecd& gradient) override; + /** Get the kernel gradient in weak form. */ + virtual Vecd getKernelGradient(size_t particle_index_i, size_t particle_index_j, Real dW_ij, Vecd& e_ij) override; + + virtual SolidParticles* ThisObjectPtr() override {return this;}; + }; + + /** + * @class ElasticSolidParticles + * @brief A group of particles with elastic body particle data. + */ + class ElasticSolidParticles : public SolidParticles + { + protected: + /**< Computing von_Mises_stress. */ + Real von_Mises_stress(size_t particle_i); + virtual void writePltFileHeader(std::ofstream& output_file); + virtual void writePltFileParticleData(std::ofstream& output_file, size_t index_i); + + public: + ElasticSolidParticles(SPHBody* body, ElasticSolid* elastic_solid); + virtual ~ElasticSolidParticles() {}; + + StdLargeVec F_; /**< deformation tensor */ + StdLargeVec dF_dt_; /**< deformation tensor change rate */ + StdLargeVec stress_PK1_; /**< first Piola-Kirchhoff stress tensor */ + + virtual void writeParticlesToVtuFile(std::ofstream &output_file) override; + + virtual ElasticSolidParticles* ThisObjectPtr() override {return this;}; + }; + + /** + * @class ActiveMuscleParticles + * @brief A group of particles with active muscle particle data. + */ + class ActiveMuscleParticles : public ElasticSolidParticles + { + public: + + StdLargeVec active_contraction_stress_; /**< active contraction stress */ + StdLargeVec active_stress_; /**< active stress */ //seems to be moved to method class + + template + ActiveMuscleParticles(SPHBody* body, ActiveMuscle* active_muscle) : + ElasticSolidParticles(body, active_muscle) + { + active_muscle->assignActiveMuscleParticles(this); + initializeActiveMuscleParticleData(); + }; + virtual ~ActiveMuscleParticles() {}; + + virtual ActiveMuscleParticles* ThisObjectPtr() override { return this; }; + private: + void initializeActiveMuscleParticleData(); + }; + + /** + * @class ShellParticles + * @brief A group of particles with shell particle data. + */ + class ShellParticles : public ElasticSolidParticles + { + public: + ShellParticles(SPHBody* body, ElasticSolid* elastic_solid, Real thickness); + virtual ~ShellParticles() {}; + + StdLargeVec transformation_matrix_; /**< initial transformation matrix from global to local coordinates */ + StdLargeVec shell_thickness_; /**< shell thickness */ + //---------------------------------------------------------------------- + // extra generalized coordinates in global coordinate + //---------------------------------------------------------------------- + StdLargeVec pseudo_n_; /**< current pseudo-normal vector */ + StdLargeVec dpseudo_n_dt_; /**< pseudo-normal vector change rate */ + StdLargeVec dpseudo_n_d2t_; /**< pseudo-normal vector second order time derivation */ + //---------------------------------------------------------------------- + // extra generalized coordinate and velocity in local coordinate + //---------------------------------------------------------------------- + StdLargeVec rotation_; /**< rotation angle of the initial normal respective to each axis */ + StdLargeVec angular_vel_; /**< angular velocity respective to each axis */ + StdLargeVec dangular_vel_dt_; /**< angular accelration of respective to each axis*/ + //---------------------------------------------------------------------- + // extra deformation and deformation rate in local coordinate + //---------------------------------------------------------------------- + StdLargeVec F_bending_; /**< bending deformation gradient */ + StdLargeVec dF_bending_dt_; /**< bending deformation gradient change rate */ + //---------------------------------------------------------------------- + // extra stress for pair interaction in global coordinate + //---------------------------------------------------------------------- + StdLargeVec global_shear_stress_; /**< global shear stress */ + StdLargeVec global_stress_; /**< global stress for pair interaction */ + StdLargeVec global_moment_; /**< global bending moment for pair interaction */ + + virtual ShellParticles* ThisObjectPtr() override {return this;}; + }; +} +#endif //SOLID_PARTICLES_H diff --git a/SPHINXsys/src/shared/regression_testing/CMakeLists.txt b/SPHINXsys/src/shared/regression_testing/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/regression_testing/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/regression_testing/meanvalue_variance_method.h b/SPHINXsys/src/shared/regression_testing/meanvalue_variance_method.h new file mode 100644 index 0000000000..06212e3df2 --- /dev/null +++ b/SPHINXsys/src/shared/regression_testing/meanvalue_variance_method.h @@ -0,0 +1,203 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file regression_testing.h + * @brief Classes for the comparison between validated and tested results. + * @author Bo Zhang, Xiangyu Hu + */ + +#pragma once +#include "in_output.h" +#include "xml_engine.h" +#include "all_physical_dynamics.h" + +namespace SPH +{ + /** + * @class meanvalue_variance_method.h + * @brief the regression testing is based on the converged meanvalue and variance. + */ + + template + class RegressionTesting : public ObserveMethodType + { + /* identify the the variable type from the parent class. */ + using VariableType = decltype(ObserveMethodType::type_indicator_); + + protected: + std::string result_filefullpath_; /* the path for result. */ + std::string meanvalue_filefullpath_; /* the path for meanvalue. */ + std::string variance_filefullpath_; /* the path for variance. */ + std::string runtimes_filefullpath_; /* the path for runtimes. */ + std::string converged; /* the tag for result converged. */ + + XmlEngine result_xml_engine_in_; /* xml engine for result input. */ + XmlEngine meanvalue_xml_engine_in_; /* xml engine for meanvalue input. */ + XmlEngine variance_xml_engine_in_; /* xml engine for variance input. */ + XmlEngine result_xml_engine_out_; /* xml engine for result output. */ + XmlEngine meanvalue_xml_engine_out_; /* xml engine for meanvalue output. */ + XmlEngine variance_xml_engine_out_; /* xml engine for variance output. */ + + VariableType threshold_mean_, threshold_variance_; /* the threshold container for mean and variance. */ + StdVec> result_; /* the container of results in all runs. */ + DataVec meanvalue_, meanvalue_new_; /* the container of meanvalue. */ + DataVec variance_, variance_new_; /* the container of variance. */ + + size_t i_, j_; /* the size of each layer of the result vector. */ + size_t number_of_snapshot_old_; /* the snapshot size of existed result. */ + size_t difference_; /* the difference of snapshot between old and new result. */ + size_t number_of_run_; /* the times of run. */ + size_t label_for_repeat_; /* the int label for stable convergence. */ + + /* initialize the threshold of meanvalue and variance. */ + void InitializeThreshold(Real& threshold_mean, Real& threshold_variance); + void InitializeThreshold(Vecd& threshold_mean, Vecd& threshold_variance); + void InitializeThreshold(Matd& threshold_mean, Matd& threshold_variance); + + /* the method for calculating the meanvalue. */ + void GetNewMeanValue(DataVec& current_result, DataVec& meanvalue, DataVec& meanvalue_new); + void GetNewMeanValue(DataVec& current_result, DataVec& meanvalue, DataVec& meanvalue_new); + void GetNewMeanValue(DataVec& current_result, DataVec& meanvalue, DataVec& meanvalue_new); + + /* the method for calculating the variance. */ + void GetNewVariance(StdVec>& result, DataVec& meanvalue_new, DataVec& variance, DataVec& variance_new); + void GetNewVariance(StdVec>& result, DataVec& meanvalue_new, DataVec& variance, DataVec& variance_new); + void GetNewVariance(StdVec>& result, DataVec& meanvalue_new, DataVec& variance, DataVec& variance_new); + + /* the method for writing data to xml memory. */ + void WriteDataToXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, const DataVec& quantity); + void WriteDataToXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, const DataVec& quantity); + void WriteDataToXmlMemory(XmlEngine xmlengine, SimTK::Xml::Element element, const DataVec& quantity); + + /* the method for comparing the meanvalue and variance. */ + size_t CompareParameter(string par_name, DataVec& parameter, DataVec& parameter_new, Real& threshold); + size_t CompareParameter(string par_name, DataVec& parameter, DataVec& parameter_new, Vecd& threshold); + size_t CompareParameter(string par_name, DataVec& parameter, DataVec& parameter_new, Matd& threshold); + + /** the method for testing the new result. */ + size_t TestingNewResult(size_t diff, DataVec& current_result, DataVec& meanvalue, DataVec& variance); + size_t TestingNewResult(size_t diff, DataVec& current_result, DataVec& meanvalue, DataVec& variance); + size_t TestingNewResult(size_t diff, DataVec& current_result, DataVec& meanvalue, DataVec& variance); + + /** setup (load .xml file) and correct the number of old and new result. */ + void SettingupAndCorrection(); + + /** read the result, meanvalue, variance from the .xml files. */ + void ReadResultFromXml(); + void ReadMeanAndVarianceToXml(); + + /** update the meanvalue, variance to a new value. */ + void UpdateMeanValueAndVariance(); + + /** write the result, meanvalue, variance to the .xml files. */ + void WriteResultToXml(); + void WriteMeanAndVarianceToXml(); + + /** compare the meanvalue, variance if converged. */ + bool CompareMeanValueAndVariance(); + + /** testing the new result if the converged within the range. */ + void ResultTesting(); + + public: + template + explicit RegressionTesting(ConstructorArgs... constructor_args) : + ObserveMethodType(constructor_args...), + result_xml_engine_in_("result_xml_engine_in", "result"), + meanvalue_xml_engine_in_("meanvalue_xml_engine_in", "meanvalue"), + variance_xml_engine_in_("variance_xml_engine_in", "variance"), + result_xml_engine_out_("result_xml_engine_out", "result"), + meanvalue_xml_engine_out_("meanvalue_xml_engine_out", "meanvalue"), + variance_xml_engine_out_("variance_xml_engine_out", "variance") + { + result_filefullpath_ = this->in_output_.input_folder_ + "/" + this->body_name_ + + "_" + this->quantity_name_ + "_" + this->in_output_.restart_step_ + "_result.xml"; + meanvalue_filefullpath_ = this->in_output_.input_folder_ + "/" +this-> body_name_ + + "_" + this->quantity_name_ + "_" + this->in_output_.restart_step_ + "._meanvalue.xml"; + variance_filefullpath_ = this->in_output_.input_folder_ + "/" + this->body_name_ + + "_" + this->quantity_name_ + "_" + this->in_output_.restart_step_ + "._variance.xml"; + runtimes_filefullpath_ = this->in_output_.input_folder_ + "/" + this->body_name_ + + "_" + this->quantity_name_ + "_" + this->in_output_.restart_step_ + "_runtimes.dat"; + + if (!fs::exists(runtimes_filefullpath_)) + { + number_of_run_ = 1; + converged = "false"; + label_for_repeat_ = 0; + } + else + { + std::ifstream in_file(runtimes_filefullpath_.c_str()); + in_file >> converged; + in_file >> number_of_run_; + in_file >> label_for_repeat_; + in_file.close(); + }; + }; + virtual ~RegressionTesting(); + + /* the interface to write date into xml memory. */ + void writeToFile(Real iteration = 0) + { + ObserveMethodType::writeToFile(); + this->WriteToXml(iteration); + } + + /* the interface to write XML memory into XML file. */ + void WriteXmlToXmlFile() { this->observe_xml_engine_.writeToXmlFile(this->filefullpath_input_); }; + + /* read local data from XML file. */ + void ReadXmlFromXmlFile() { this->ReadFromXml(); }; + + /** the interface for generating the priori converged result. */ + void generateDataBase(VariableType threshold_mean, VariableType threshold_variance) + { + WriteXmlToXmlFile(); + ReadXmlFromXmlFile(); + InitializeThreshold(threshold_mean, threshold_variance); + if (converged == "false") + { + SettingupAndCorrection(); + ReadResultFromXml(); + ReadMeanAndVarianceToXml(); + UpdateMeanValueAndVariance(); + WriteResultToXml(); + WriteMeanAndVarianceToXml(); + CompareMeanValueAndVariance(); + } + }; + + /* the interface for testing the new result. */ + void newResultTesting() + { + WriteXmlToXmlFile(); + ReadXmlFromXmlFile(); + SettingupAndCorrection(); + ReadMeanAndVarianceToXml(); + ResultTesting(); + }; + + /* calculate the meanvalue of a time series result. */ + void GetMeanvalueInTime(size_t iteration = 0); + }; +}; \ No newline at end of file diff --git a/SPHINXsys/src/shared/regression_testing/meanvalue_variance_method.hpp b/SPHINXsys/src/shared/regression_testing/meanvalue_variance_method.hpp new file mode 100644 index 0000000000..99d234b234 --- /dev/null +++ b/SPHINXsys/src/shared/regression_testing/meanvalue_variance_method.hpp @@ -0,0 +1,663 @@ +/** + * @file regression_testing.cpp + * @author Bo Zhang, Xiangyu Hu + */ + +#include "meanvalue_variance_method.h" + + //=================================================================================================// +namespace SPH +{ + //=================================================================================================// + template + void RegressionTesting::InitializeThreshold( + Real& threshold_mean, Real& threshold_variance) + { + threshold_mean_ = threshold_mean; + threshold_variance_ = threshold_variance; + }; + //=================================================================================================// + template + void RegressionTesting::InitializeThreshold( + Vecd& threshold_mean, Vecd& threshold_variance) + { + for (size_t index_i = 0; index_i != threshold_mean_.size(); ++index_i) + { + threshold_mean_[index_i] = threshold_mean[index_i]; + threshold_variance_[index_i] = threshold_variance[index_i]; + } + }; + //=================================================================================================// + template + void RegressionTesting::InitializeThreshold( + Matd& threshold_mean, Matd& threshold_variance) + { + for (size_t index_i = 0; index_i != threshold_mean_.size(); ++index_i) + { + for (size_t index_j = 0; index_j != threshold_mean_.size(); ++index_j) + { + threshold_mean_[index_i][index_j] = threshold_mean[index_i][index_j]; + threshold_variance_[index_i][index_j] = threshold_variance[index_i][index_j]; + } + } + }; + //=================================================================================================// + template + void RegressionTesting::GetNewMeanValue(DataVec& current_result, + DataVec& meanvalue, DataVec& meanvalue_new) + { + for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) + { + for (size_t j = 0; j != j_; ++j) + { + meanvalue_new[i][j] = (meanvalue[i][j] * (number_of_run_ - 1) + + current_result[i][j]) / number_of_run_; + } + } + }; + //=================================================================================================// + template + void RegressionTesting::GetNewMeanValue(DataVec& current_result, + DataVec& meanvalue, DataVec& meanvalue_new) + { + for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) + { + for (size_t j = 0; j != j_; ++j) + { + for (size_t index_i = 0; index_i != meanvalue[0][0].size(); ++index_i) + { + meanvalue_new[i][j][index_i] = (meanvalue[i][j][index_i] * (number_of_run_ - 1) + + current_result[i][j][index_i]) / number_of_run_; + } + } + } + }; + //=================================================================================================// + template + void RegressionTesting::GetNewMeanValue(DataVec& current_result, + DataVec& meanvalue, DataVec& meanvalue_new) + { + for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) + { + for (size_t j = 0; j != j_; ++j) + { + for (size_t index_i = 0; index_i != meanvalue[0][0].size(); ++index_i) + { + for (size_t index_j = 0; index_j != meanvalue[0][0].size(); +index_j) + { + meanvalue_new[i][j][index_i][index_j] = (meanvalue[i][j][index_i][index_j] * + (number_of_run_ - 1) + current_result[i][j][index_i][index_j]) / number_of_run_; + } + } + } + } + }; + //=================================================================================================// + template + void RegressionTesting::GetNewVariance(StdVec>& result, + DataVec& meanvalue_new, DataVec& variance, DataVec& variance_new) + { + for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) + { + for (size_t j = 0; j != j_; ++j) + { + for (size_t n = 0; n != number_of_run_; ++n) + { + variance_new[i][j] = SMAX(variance[i][j], variance_new[i][j], + std::pow((result[n][i][j] - meanvalue_new[i][j]), 2)); + variance_new[i][j] = variance_new[i][j] < std::pow(meanvalue_new[i][j] * 1.0e-6, 2) ? + std::pow(meanvalue_new[i][j] * 1.0e-6, 2) : variance_new[i][j]; + } + } + } + }; + //=================================================================================================// + template + void RegressionTesting::GetNewVariance(StdVec>& result, + DataVec& meanvalue_new, DataVec& variance, DataVec& variance_new) + { + for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) + { + for (size_t j = 0; j != j_; ++j) + { + for (size_t n = 0; n != number_of_run_; ++n) + { + for (size_t index_i = 0; index_i != variance[0][0].size(); ++index_i) + { + variance_new[i][j][index_i] = SMAX(variance[i][j][index_i], variance_new[i][j][index_i], + std::pow((result[n][i][j][index_i] - meanvalue_new[i][j][index_i]), 2)); + variance_new[i][j][index_i] = variance_new[i][j][index_i] < + std::pow(meanvalue_new[i][j][index_i] * 1.0e-6, 2) ? + std::pow(meanvalue_new[i][j][index_i] * 1.0e-6, 2) : variance_new[i][j][index_i]; + } + } + } + } + }; + //=================================================================================================// + template + void RegressionTesting::GetNewVariance(StdVec>& result, + DataVec& meanvalue_new, DataVec& variance, DataVec& variance_new) + { + for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) + { + for (size_t j = 0; j != j_; ++j) + { + for (size_t n = 0; n != number_of_run_; ++n) + { + for (size_t index_i = 0; index_i != variance[0][0].size(); ++index_i) + { + for (size_t index_j = 0; index_j != variance[0][0].size(); ++index_j) + { + variance_new[i][j][index_i][index_j] = SMAX(variance[i][j][index_i][index_j], + variance_new[i][j][index_i][index_j], std::pow((result[n][i][j][index_i][index_j] + - meanvalue_new[i][j][index_i][index_j]), 2)); + variance_new[i][j][index_i][index_j] = variance_new[i][j][index_i][index_j] < + std::pow(meanvalue_new[i][j][index_i][index_j] * 1.0e-6, 2) ? + std::pow(meanvalue_new[i][j][index_i][index_j] * 1.0e-6, 2) : + variance_new[i][j][index_i][index_j]; + } + } + } + } + } + }; + //=================================================================================================// + template + void RegressionTesting::WriteDataToXmlMemory(XmlEngine xmlengine, + SimTK::Xml::Element element, const DataVec& quantity) + { + for (size_t snapshot_n_ = 0; snapshot_n_ != SMIN(i_, number_of_snapshot_old_); ++snapshot_n_) + { + std::string element_name_ = this->element_tag_[snapshot_n_]; + xmlengine.addChildToElement(element, element_name_); + for (size_t particle_n_ = 0; particle_n_ != j_; ++particle_n_) + { + SimTK::Xml::element_iterator ele_ite = element.element_begin(element_name_); + std::string attribute_name_ = this->quantity_name_ + "_" + std::to_string(particle_n_); + xmlengine.setAttributeToElement(ele_ite, attribute_name_, quantity[snapshot_n_][particle_n_]); + } + } + }; + //=================================================================================================// + template + void RegressionTesting::WriteDataToXmlMemory(XmlEngine xmlengine, + SimTK::Xml::Element element, const DataVec& quantity) + { + for (size_t snapshot_n_ = 0; snapshot_n_ != SMIN(i_, number_of_snapshot_old_); ++snapshot_n_) + { + std::string element_name_ = this->element_tag_[snapshot_n_]; + xmlengine.addChildToElement(element, element_name_); + for (size_t particle_n_ = 0; particle_n_ != j_; ++particle_n_) + { + SimTK::Xml::element_iterator ele_ite = element.element_begin(element_name_); + std::string attribute_name_ = this->quantity_name_ + "_" + std::to_string(particle_n_); + xmlengine.setAttributeToElement(ele_ite, attribute_name_, quantity[snapshot_n_][particle_n_]); + } + } + }; + //=================================================================================================// + template + void RegressionTesting::WriteDataToXmlMemory(XmlEngine xmlengine, + SimTK::Xml::Element element, const DataVec& quantity) + { + for (size_t snapshot_n_ = 0; snapshot_n_ != SMIN(i_, number_of_snapshot_old_); ++snapshot_n_) + { + std::string element_name_ = this->element_tag_[snapshot_n_]; + xmlengine.addChildToElement(element, element_name_); + for (size_t particle_n_ = 0; particle_n_ != j_; ++particle_n_) + { + SimTK::Xml::element_iterator ele_ite = element.element_begin(element_name_); + std::string attribute_name_ = this->quantity_name_ + "_" + std::to_string(particle_n_); + xmlengine.setAttributeToElement(ele_ite, attribute_name_, quantity[snapshot_n_][particle_n_]); + } + } + }; + //=================================================================================================// + template + size_t RegressionTesting::CompareParameter(string par_name, + DataVec& parameter, DataVec& parameter_new, Real& threshold) + { + size_t count = 0; + for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) + { + for (size_t j = 0; j != j_; ++j) + { + if ((ABS(parameter[i][j] - parameter_new[i][j]) / (parameter_new[i][j] + TinyReal)) > threshold) + { + std::cout << par_name << ": " << this->quantity_name_ << "[" << j << "] in " + << this->element_tag_[i] << "is not converged, and difference is " + << (ABS(parameter[i][j] - parameter_new[i][j]) / (parameter_new[i][j] + TinyReal)) << endl; + count++; + } + } + } + return count; + }; + //=================================================================================================// + template + size_t RegressionTesting::CompareParameter(string par_name, + DataVec& parameter, DataVec& parameter_new, Vecd& threshold) + { + size_t count = 0; + for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) + { + for (size_t j = 0; j != j_; ++j) + { + for (size_t index_i = 0; index_i != parameter[0][0].size(); ++index_i) + { + if ((ABS(parameter[i][j][index_i] - parameter_new[i][j][index_i]) + / (parameter_new[i][j][index_i] + TinyReal)) > threshold[index_i]) + { + std::cout << par_name << ": " << this->quantity_name_ << "[" << j << "][" << + index_i << "] in " << this->element_tag_[i] << "is not converged, and difference is " + << (ABS(parameter[i][j][index_i] - parameter_new[i][j][index_i]) / + (parameter_new[i][j][index_i] + TinyReal)) << endl; + count++; + } + } + } + } + return count; + }; + //=================================================================================================// + template + size_t RegressionTesting::CompareParameter(string par_name, + DataVec& parameter, DataVec& parameter_new, Matd& threshold) + { + size_t count = 0; + for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) + { + for (size_t j = 0; j != j_; ++j) + { + for (size_t index_i = 0; index_i != parameter[0][0].size(); ++index_i) + { + for (size_t index_j = 0; index_j != parameter[0][0].size(); ++index_j) + { + if ((ABS(parameter[i][j][index_i][index_j] - parameter_new[i][j][index_i][index_j]) / + (parameter_new[i][j][index_i][index_j] + TinyReal)) > threshold[index_i][index_j]) + { + std::cout << par_name << ": " << this->quantity_name_ << "[" << j << "][" << + index_i << "][" << index_j << " ] in " << this->element_tag_[i] << + "is not converged, and difference is " << (ABS(parameter[i][j][index_i][index_j] + - parameter_new[i][j][index_i][index_j]) / + (parameter_new[i][j][index_i][index_j] + TinyReal)) << endl; + count++; + } + } + } + } + } + return count; + }; + //=================================================================================================// + template + size_t RegressionTesting::TestingNewResult(size_t diff, + DataVec& current_result, DataVec& meanvalue, DataVec& variance) + { + size_t count = 0; + for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) + { + for (size_t j = 0; j != j_; ++j) + { + if (std::pow(current_result[i][j] - meanvalue[i + diff][j], 2) > variance[i + diff][j]) + { + std::cout << this->quantity_name_ << "[" << j << "] in " << this->element_tag_[i] << + " is beyond the expection, and difference is " << + (ABS((std::pow(current_result[i][j] - meanvalue[i + diff][j], 2) - variance[i + diff][j])) + / variance[i + diff][j]) << endl; + count++; + } + } + } + return count; + }; + //=================================================================================================// + template + size_t RegressionTesting::TestingNewResult(size_t diff, + DataVec& current_result, DataVec& meanvalue, DataVec& variance) + { + size_t count = 0; + for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) + { + for (size_t j = 0; j != j_; ++j) + { + for (size_t index_i = 0; index_i != meanvalue[0][0].size(); ++index_i) + { + if (std::pow(current_result[i][j][index_i] - meanvalue[i + diff][j][index_i], 2) + > variance[i + diff][j][index_i]) + { + std::cout << this->quantity_name_ << "[" << j << "][" << index_i << "] in " + << this->element_tag_[i] << " is beyond the expection, and difference is " + << (ABS((std::pow(current_result[i][j][index_i] - meanvalue[i + diff][j][index_i], 2) + - variance[i + diff][j][index_i])) / variance[i + diff][j][index_i]) << endl; + count++; + } + } + } + } + return count; + }; + //=================================================================================================// + template + size_t RegressionTesting::TestingNewResult(size_t diff, + DataVec& current_result, DataVec& meanvalue, DataVec& variance) + { + size_t count = 0; + for (size_t i = 0; i != SMIN(i_, number_of_snapshot_old_); ++i) + { + for (size_t j = 0; j != j_; ++j) + { + for (size_t index_i = 0; index_i != meanvalue[0][0].size(); ++index_i) + { + for (size_t index_j = 0; index_j != meanvalue[0][0].size(); ++index_j) + { + if (std::pow(current_result[i][j][index_i][index_j] - + meanvalue[i + diff][j][index_i][index_j], 2) > + variance[i + diff][j][index_i][index_j]) + { + std::cout << this->quantity_name_ << "[" << j << "][" << index_i << "] in " + << this->element_tag_[i] << " is beyond the expection, and difference is " + << (ABS((std::pow(current_result[i][j][index_i][index_j] - + meanvalue[i + diff][j][index_i][index_j], 2) + - variance[i + diff][j][index_i][index_j])) / + variance[i + diff][j][index_i][index_j]) << endl; + count++; + } + } + + } + } + } + return count; + }; + //=================================================================================================// + template + void RegressionTesting::SettingupAndCorrection() + { + /* obtain the size of result. */ + i_ = this->current_result_.size(); + j_ = this->current_result_[0].size(); + + if (number_of_run_ > 1) + { + if (!fs::exists(result_filefullpath_)) + { + std::cout << "\n Error: the input file:" << result_filefullpath_ << " is not exists" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + else if (!fs::exists(meanvalue_filefullpath_)) + { + std::cout << "\n Error: the input file:" << meanvalue_filefullpath_ << " is not exists" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + else if (!fs::exists(variance_filefullpath_)) + { + std::cout << "\n Error: the input file:" << variance_filefullpath_ << " is not exists" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + else + { + /** load the result from .xml file. */ + result_xml_engine_in_.loadXmlFile(result_filefullpath_); + /** load the meanvalue from .xml file. */ + meanvalue_xml_engine_in_.loadXmlFile(meanvalue_filefullpath_); + /** load the variance from .xml file. */ + variance_xml_engine_in_.loadXmlFile(variance_filefullpath_); + + number_of_snapshot_old_ = std::distance(meanvalue_xml_engine_in_.root_element_.element_begin(), + meanvalue_xml_engine_in_.root_element_.element_end()); + + DataVec meanvalue_temp_(SMAX(i_, number_of_snapshot_old_), StdVec(j_)); + DataVec variance_temp_(SMAX(i_, number_of_snapshot_old_), StdVec(j_)); + meanvalue_ = meanvalue_temp_; + variance_ = variance_temp_; + + if (number_of_snapshot_old_ < i_) + { + difference_ = i_ - number_of_snapshot_old_; + for (size_t delete_ = 0; delete_ != difference_; ++delete_) + { + this->current_result_.pop_back(); + } + } + else if (number_of_snapshot_old_ > i_) + { + difference_ = number_of_snapshot_old_ - i_; + } + else + { + difference_ = 0; + } + } + } + else if (number_of_run_ == 1) + { + number_of_snapshot_old_ = i_; + DataVec meanvalue_temp_(i_, StdVec(j_)); + DataVec variance_temp_(i_, StdVec(j_)); + result_.push_back(this->current_result_); + meanvalue_ = meanvalue_temp_; + variance_ = variance_temp_; + } + }; + //=================================================================================================// + template + void RegressionTesting::ReadResultFromXml() + { + if (number_of_run_ > 1) + { + /** read result from .xml */ + DataVec result_in_(SMAX(i_, number_of_snapshot_old_), StdVec(j_)); + for (size_t run_n_ = 0; run_n_ != number_of_run_ - 1; ++run_n_) + { + std::string node_name_ = "Round_" + std::to_string(run_n_); + SimTK::Xml::Element father_element_ = result_xml_engine_in_.getChildElement(node_name_); + for (size_t particle_n_ = 0; particle_n_ != j_; ++particle_n_) + { + this->ReadDataFromXmlMemory(result_xml_engine_in_, father_element_, particle_n_, result_in_); + } + DataVec result_temp_ = result_in_; + for (size_t delete_ = 0; delete_ != difference_; ++delete_) + { + result_temp_.pop_back(); + } + result_.push_back(result_temp_); + } + result_.push_back(this->current_result_); + } + }; + //=================================================================================================// + template + void RegressionTesting::ReadMeanAndVarianceToXml() + { + if (number_of_run_ > 1) + { + /** read mean value from .xml file. */ + SimTK::Xml::Element element_name_meanvalue_ = meanvalue_xml_engine_in_.root_element_; + SimTK::Xml::Element element_name_variance_ = variance_xml_engine_in_.root_element_; + for (size_t particle_n_ = 0; particle_n_ != j_; ++particle_n_) + { + this->ReadDataFromXmlMemory(meanvalue_xml_engine_in_, element_name_meanvalue_, particle_n_, meanvalue_); + this->ReadDataFromXmlMemory(variance_xml_engine_in_, element_name_variance_, particle_n_, variance_); + } + } + }; + //=================================================================================================// + template + void RegressionTesting::UpdateMeanValueAndVariance() + { + if (number_of_run_ > 1) + { + for (size_t delete_ = 0; delete_ != difference_; ++delete_) + { + meanvalue_.pop_back(); + variance_.pop_back(); + } + } + meanvalue_new_ = meanvalue_; + variance_new_ = variance_; + /** update meanvalue of result. */ + GetNewMeanValue(this->current_result_, meanvalue_, meanvalue_new_); + /** update variance of result. */ + GetNewVariance(result_, meanvalue_new_, variance_, variance_new_); + }; + //=================================================================================================// + template + bool RegressionTesting::CompareMeanValueAndVariance() + { + size_t count_not_converged_m = 0; + size_t count_not_converged_v = 0; + /** determine if the average value has converged. */ + count_not_converged_m = CompareParameter("meanvalue", meanvalue_, meanvalue_new_, threshold_mean_); + count_not_converged_v = CompareParameter("variance", variance_, variance_new_, threshold_variance_); + if (count_not_converged_m == 0) + { + std::cout << "The meanvalue of " << this->quantity_name_ << " are converged now." << endl; + if (count_not_converged_v == 0) + { + if (label_for_repeat_ == 1) + { + converged = "true"; + std::cout << "The meanvalue and variance of " << this->quantity_name_ << + " are converged enough times, and rum will stop now." << endl; + return true; + } + else + { + converged = "false"; + label_for_repeat_++; + std::cout << "The variance of " << this->quantity_name_ << " are also converged," + "but they should be converged again to be stable." << endl; + return false; + } + } + else if (count_not_converged_v != 0) + { + converged = "false"; + label_for_repeat_ = 0; + std::cout << "The variance of " << this->quantity_name_ << " is not converged "<< + count_not_converged_v << " times." << endl; + return false; + } + } + else if (count_not_converged_m != 0) + { + converged = "false"; + label_for_repeat_ = 0; + std::cout << "The meanvalue of " << this->quantity_name_ << " is not converged " << + count_not_converged_m << " times." << endl; + return false; + + } + }; + //=================================================================================================// + template + void RegressionTesting::WriteResultToXml() + { + /** Write result to .xml file */ + for (size_t run_n_ = 0; run_n_ != number_of_run_; ++run_n_) + { + std::string node_name_ = "Round_" + std::to_string(run_n_); + result_xml_engine_out_.addElementToXmlDoc(node_name_); + SimTK::Xml::Element father_element_ = + result_xml_engine_out_.getChildElement(node_name_); + WriteDataToXmlMemory(result_xml_engine_out_, father_element_, result_[run_n_]); + } + /** write the result in XmlEngine to xml file. */ + result_xml_engine_out_.writeToXmlFile(result_filefullpath_); + }; + //=================================================================================================// + template + void RegressionTesting::WriteMeanAndVarianceToXml() + { + /** Write meanvalue and variance to .xml file */ + SimTK::Xml::Element element_meanvalue_ = meanvalue_xml_engine_out_.root_element_; + SimTK::Xml::Element element_variance_ = variance_xml_engine_out_.root_element_; + WriteDataToXmlMemory(meanvalue_xml_engine_out_, element_meanvalue_, meanvalue_new_); + WriteDataToXmlMemory(variance_xml_engine_out_, element_variance_, variance_new_); + /** write the meanvalue and variance in XmlEngine to xml file. */ + meanvalue_xml_engine_out_.writeToXmlFile(meanvalue_filefullpath_); + variance_xml_engine_out_.writeToXmlFile(variance_filefullpath_); + }; + //=================================================================================================// + template + RegressionTesting::~RegressionTesting() + { + if (converged == "false") + { + number_of_run_ += 1; + } + if (fs::exists(runtimes_filefullpath_)) + { + fs::remove(runtimes_filefullpath_); + } + std::ofstream out_file(runtimes_filefullpath_.c_str()); + out_file << converged; + out_file << "\n"; + out_file << number_of_run_; + out_file << "\n"; + out_file << label_for_repeat_; + out_file.close(); + } + //=================================================================================================// + template + void RegressionTesting::ResultTesting() + { + /* compare the current result to the converged mean value and variance. */ + size_t test_wrong = 0; + if (i_ < number_of_snapshot_old_) + { + test_wrong = TestingNewResult(difference_, this->current_result_, meanvalue_, variance_); + if (test_wrong == 0) + std::cout << "The result of " << this->quantity_name_ << + " is correct based on the regression testing!" << endl; + else + { + std::cout << "There are " << test_wrong << + " snapshots are not within the expected range." << endl; + std::cout << "Please try again. If it still post this sectence, " + "the result is not correct!" << endl; + } + } + else if (i_ >= number_of_snapshot_old_) + { + for (size_t delete_ = 0; delete_ != difference_; ++delete_) + { + meanvalue_.pop_back(); + variance_.pop_back(); + } + test_wrong = TestingNewResult(0, this->current_result_, meanvalue_, variance_); + if (test_wrong == 0) + std::cout << "The result of " << this->quantity_name_ << + " is correct based on the regression testing!" << endl; + else + { + std::cout << "There are " << test_wrong << + " snapshots are not within the expected range." << endl; + std::cout << "Please try again. If it still post this sectence, " + "the result is not correct!" << endl; + } + } + }; + //=================================================================================================// + template + void RegressionTesting::GetMeanvalueInTime(size_t iteration) + { + VariableType average = { 0 }; + for (size_t i = iteration; i != i_; ++i) + { + average += this->current_result_[i][0]; + } + average = average / (i_ - iteration); + + meanvalue_xml_engine_out_.addElementToXmlDoc("meanvalue"); + SimTK::Xml::element_iterator element_iterator = meanvalue_xml_engine_out_.root_element_.element_begin(); + meanvalue_xml_engine_out_.setAttributeToElement(element_iterator, "AverageViscousForce", average); + meanvalue_xml_engine_out_.writeToXmlFile(meanvalue_filefullpath_); + }; + //=================================================================================================// +} \ No newline at end of file diff --git a/SPHINXsys/src/shared/regression_testing/regression_testing.h b/SPHINXsys/src/shared/regression_testing/regression_testing.h new file mode 100644 index 0000000000..762c33adf1 --- /dev/null +++ b/SPHINXsys/src/shared/regression_testing/regression_testing.h @@ -0,0 +1,33 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file regression_testing.h +* @brief This is the header file that user code should include to pick up all +* the regression testing method used in SPHinXsys. +* @author Bo Zhang +*/ +#pragma once + +#include "meanvalue_variance_method.h" +#include "meanvalue_variance_method.hpp" + diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/CMakeLists.txt b/SPHINXsys/src/shared/simbody_sphinxsys/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/simbody_sphinxsys/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/all_simbody.h b/SPHINXsys/src/shared/simbody_sphinxsys/all_simbody.h new file mode 100644 index 0000000000..ebb061d5a4 --- /dev/null +++ b/SPHINXsys/src/shared/simbody_sphinxsys/all_simbody.h @@ -0,0 +1,14 @@ + +#ifndef ALL_SIMBODY_H +#define ALL_SIMBODY_H + + + +/** @file +This is the header file that user code should include to pick up all +interface function to Simbody library. **/ + +#include "xml_engine.h" +#include "state_engine.h" + +#endif //ALL_SIMBODY_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/array.h b/SPHINXsys/src/shared/simbody_sphinxsys/array.h new file mode 100644 index 0000000000..4d5bf35c36 --- /dev/null +++ b/SPHINXsys/src/shared/simbody_sphinxsys/array.h @@ -0,0 +1,648 @@ +/** + * @file array.h + * @details An Array toolkit for array operator. + * @author Chi Zhang and Xiangyu Hu. + */ + +#ifndef ARRAY_SIMBODY_H +#define ARRAY_SIMBODY_H + + + +#include "base_data_package.h" + +#include +#include +#include +#include + +static const int Array_CAPMIN = 1; + +namespace SPH { + /** + * @class Array + * @ details A class for storing an array of values of type T. The capacity of the class + * grows as needed. To use this template for a class of type T, class T should + * implement the following methods: default constructor, copy constructor, + * assignment operator (=), equality operator (==), and less than + * operator (<). + */ + template class Array + { + protected: + /** Size of the array. Also the index of the first empty array element. */ + int size_; + /** Current capacity of the array. */ + int capacity_; + /** Increment by which the current capacity is increased when the capacity + of this storage instance is reached. If negative, capacity doubles. */ + int capacityIncrement_; + /** Default value of elements. */ + T defaultValue_; + /** Array of values. */ + T *array_; + public: + /** + * @brief Default constructor. + * + * @param aDefaultValue Default value of an array element. This value + * is used to initialize array elements as the size of the array is + * changed. + * @param aSize Initial size of the array. The array elements are + * initialized to aDefaultValue. + * @param aCapacity Initial capacity of the array. The initial capacity + * is guaranteed to be at least as large as aSize + 1. + */ + explicit Array(const T &aDefaultValue=T(),int aSize=0,int aCapacity=Array_CAPMIN) + { + setNull(); + defaultValue_ = aDefaultValue; + int newCapacity; + int min = aSize + 1; + if(min < aCapacity) min = aCapacity; + computeNewCapacity(min,newCapacity); + ensureCapacity(newCapacity); + size_ = aSize; + if(size_<0) size_=0; + } + /** + * @brief Copy constructor. + * + * @param aArray Array to be copied. + */ + Array(const Array &aArray) + { + setNull(); + *this = aArray; + } + /** + * @brief Destructor. + * + * @details When the array is deleted, references to elements of this array become + * invalid. + */ + virtual ~Array() + { + if(array_!=nullptr) { delete[] array_; array_ = nullptr; } + } + private: + /** + * %Set all member variables to their null or default values. + */ + void setNull() + { + size_ = 0; + capacityIncrement_ = -1; + capacity_ = 0; + array_ = nullptr; + } + //============================================================================= + // OPERATORS + //============================================================================= + public: + /** + * @brief A non-operator version of operator == + */ + bool arrayEquals(const Array &aArray) const + { + return *this == aArray; + } + /** + * @brief Get the array element at a specified index. This overloaded operator + * can be used both to set and get element values: + * @code + * Array array(2); + * T value = array[i]; + * array[i] = value; + * @endcode + * + * @details This operator is intended for accessing array elements with as little + * overhead as possible, so no error checking is performed. + * The caller must make sure the specified index is within the bounds of + * the array. If error checking is desired, use Array::get(). + * + * @param aIndex Index of the desired element (0 <= aIndex < size_). + * @return Reference to the array element. + * @see get(). + */ + T& operator[](int aIndex) const + { + return(array_[aIndex]); + } + /** + * @brief Assign this array to a specified array. + * This operator makes a complete copy of the specified array; all member + * variables are copied. So, the result is two identical, independent arrays. + * + * @param aArray Array to be copied. + * @return Reference to this array. + */ + Array& operator=(const Array &aArray) + { + size_ = aArray.size_; + capacity_ = aArray.capacity_; + capacityIncrement_ = aArray.capacityIncrement_; + defaultValue_ = aArray.defaultValue_; + + // ARRAY + if(array_!=nullptr) delete[] array_; + array_ = new T[capacity_]; + for(int i = 0;i < capacity_; i++) array_[i] = aArray.array_[i]; + + return(*this); + } + /** + * @brief Determine if two arrays are equal. + * + * @details Two arrays are equal if their contents are equal. That is, each array + * must be the same length and their corresponding array elements must be + * equal. + * + * @param aArray Array to be tested as equal. + * @return True if equal, false if not equal. + */ + bool operator==(const Array &aArray) const + { + if(size_ != aArray.size_) return(false); + + int i; + for(i=0; i &aArray) + { + int i; + for(i=0; i>(std::istream& in, Array& out) + { + return in; + } + /** + * @brief Compute a new capacity that is at least as large as a specified minimum + * capacity; this method does not change the capacity, it simply computes + * a new recommended capacity. + * + * @details If the capacity increment is negative, the current capacity is + * doubled until the computed capacity is greater than or equal to the + * specified minimum capacity. If the capacity increment is positive, the + * current capacity increment by this amount until the computed capacity is + * greater than or equal to the specified minimum capacity. If the capacity + * increment is zero, the computed capacity is set to the current capacity + * and false is returned. + * + * @param rNewCapacity New computed capacity. + * @param aMinCapacity Minimum new computed capacity. The computed capacity + * is incremented until it is at least as large as aMinCapacity, assuming + * the capacity increment is not zero. + * @return True if the new capacity was increased, false otherwise (i.e., + * if the capacity increment is set to 0). + * @see setCapacityIncrement() + */ + bool computeNewCapacity(int aMinCapacity,int &rNewCapacity) + { + rNewCapacity = capacity_; + if(rNewCapacity < Array_CAPMIN) rNewCapacity = Array_CAPMIN; + + if(capacityIncrement_ == 0) { + std::cout << "Array.computeNewCapacity: WARN- capacity is set"; + std::cout << " not to increase (i.e., capacityIncrement_==0).\n"; + return(false); + } + + while(rNewCapacity < aMinCapacity) { + if(capacityIncrement_ < 0) { + rNewCapacity = 2 * rNewCapacity; + } else { + rNewCapacity = rNewCapacity + capacityIncrement_; + } + } + + return(true); + } + /** + * @brief Ensure that the capacity of this array is at least the specified amount. + * Note that the newly allocated array elements are not initialized. + * + * @param aCapacity Desired capacity. + * @return true if the capacity was successfully obtained, false otherwise. + */ + bool ensureCapacity(int aCapacity) + { + if(aCapacity < Array_CAPMIN) aCapacity = Array_CAPMIN; + if(capacity_ >= aCapacity) return(true); + + int i; + T *newArray = new T[aCapacity]; + if(newArray == nullptr) + { + std::cout << "Array.ensureCapacity: ERR- failed to increase capacity.\n"; + return(false); + } + + if(array_ != nullptr) { + for(i =0; i < size_; i++) newArray[i] = array_[i]; + for(i = size_; i < aCapacity; i++) newArray[i] = defaultValue_; + delete []array_; + array_ = nullptr; + } else { + for(i = 0; i < aCapacity; i++) newArray[i] = defaultValue_; + } + + capacity_ = aCapacity; + array_ = newArray; + + return(true); + } + + /** + * @details Trim the capacity of this array so that it is one larger than the size + * of this array. This is useful for reducing the amount of memory used + * by this array. This capacity is kept at one larger than the size so + * that, for example, an array of characters can be treated as a nullptr + * terminated string. + */ + void trim() + { + int newCapacity = size_ + 1; + if(newCapacity >= capacity_) return; + if(newCapacity < Array_CAPMIN) newCapacity = Array_CAPMIN; + + int i; + T *newArray = new T[newCapacity]; + if(newArray==nullptr) { + std::cout << "Array.trim: ERR- unable to allocate temporary array.\n"; + return; + } + + for(i = 0; i < size_; i++) newArray[i] = array_[i]; + + delete[] array_; + + array_ = newArray; + + capacity_ = newCapacity; + } + /** + * Get the capacity of this storage instance. + */ + int getCapacity() const + { + + return(capacity_); + } + /** + * @brief Set the amount by which the capacity is increased when the capacity of + * of the array in exceeded. + * @details If the specified increment is negative, the capacity is set to double + * whenever the capacity is exceeded. + * + * @param aIncrement Desired capacity increment. + */ + void setCapacityIncrement(int aIncrement) + { + capacityIncrement_ = aIncrement; + } + + /** + * @brief Get the amount by which the capacity is increased. + */ + int getCapacityIncrement() const + { + return(capacityIncrement_); + } + /** + * @details Set the size of the array. This method can be used to either increase + * or decrease the size of the array. If this size of the array is + * increased, the new elements are initialized to the default value + * that was specified at the time of construction. + * + * @details Note that the size of an array is different than its capacity. The size + * indicates how many valid elements are stored in an array. The capacity + * indicates how much the size of the array can be increased without + * allocated more memory. At all times size <= capacity. + * + * @param aSize Desired size of the array. The size must be greater than + * or equal to zero. + */ + bool setSize(int aSize) + { + if(aSize == size_) return(true); + if(aSize < 0) aSize = 0; + bool success = true; + if(aSize < size_) + { + int i; + for(i = (size_ - 1);i >= aSize; i--) array_[i] = defaultValue_; + size_ = aSize; + } else if(aSize <= capacity_) { + size_ = aSize; + } else { + int newCapacity; + success = computeNewCapacity(aSize+1, newCapacity); + if(!success) return(false); + success = ensureCapacity(newCapacity); + if(success) size_ = aSize; + } + + return(success); + } + /** + * @brief Get the size of the array. + * + * @return Size of the array. + */ + int getSize() const + { + return(size_); + } + /** Alternate name for getSize(). **/ + int size() const {return getSize();} + + /** + * @brief Append a value onto the array. + * + * @param aValue Value to be appended. + * @return New size of the array, or, equivalently, the index to the new + * first empty element of the array. + */ + int append(const T &aValue) + { + if((size_ + 1) >= capacity_) { + int newCapacity; + bool success; + success = computeNewCapacity(size_ + 1, newCapacity); + if(!success) return(size_); + success = ensureCapacity(newCapacity); + if(!success) return(size_); + } + + array_[size_] = aValue; + size_++; + + return(size_); + } + /** + * @brief Append an array of values. + * + * @param aArray Array of values to append. + * @return New size of the array, or, equivalently, the index to the new + * first empty element of the array. + */ + int append(const Array &aArray) + { + int i,n = aArray.getSize(); + for(i = 0; i < n; i++) { + append(aArray[i]); + } + + return(size_); + } + + /** + * @brief Append an array of values. + * + * @param aSize Size of the array to append. + * @param aArray Array of values to append. + * @return New size of the array, or, equivalently, the index to the new + * first empty element of the array. + */ + int append(int aSize,const T *aArray) + { + if(aSize < 0) return(size_); + if(aArray == nullptr) return(size_); + + int i; + for(i = 0;i < aSize; i++) { + append(aArray[i]); + } + + return(size_); + } + /** + * @brief Insert a value into the array at a specified index. + * + * @details This method is relatively computationally costly since many of the array + * elements may need to be shifted. + * + * @param aValue Value to be inserted. + * @param aIndex Index at which to insert the new value. All current elements + * from aIndex to the end of the array are shifted one place in the direction + * of the end of the array. If the specified index is greater than the + * current size of the array, the size of the array is increased to aIndex+1 + * and the intervening new elements are initialized to the default value that + * was specified at the time of construction. + * @return Size of the array after the insertion. + */ + int insert(int aIndex,const T &aValue) + { + if(aIndex<0) { + std::cout << "Array.insert: ERR- aIndex was less than 0.\n"; + return(size_); + } + + if(aIndex >= size_) { + setSize(aIndex+1); + array_[aIndex] = aValue; + return(size_); + } + + if((size_ + 1) >= capacity_) { + int newCapacity; + bool success; + success = computeNewCapacity(size_ + 1,newCapacity); + if(!success) return(size_); + success = ensureCapacity(newCapacity); + if(!success) return(size_); + } + + int i; + for(i = size_; i > aIndex; i--) { + array_[i] = array_[i-1]; + } + + array_[aIndex] = aValue; + size_++; + + return(size_); + } + /** + * @brief Remove a value from the array at a specified index. + * + * @details This method is relatively computationally costly since many of the array + * elements may need to be shifted. + * + * @param aIndex Index of the value to remove. All elements from aIndex to + * the end of the array are shifted one place toward the beginning of + * the array. If aIndex is less than 0 or greater than or equal to the + * current size of the array, no element is removed. + * @return Size of the array after the removal. + */ + int remove(int aIndex) + { + if(aIndex < 0) { + std::cout << "Array.remove: ERR- aIndex was less than 0.\n"; + return(size_); + } + if(aIndex >= size_) { + std::cout << "Array.remove: ERR- aIndex was greater than or equal the "; + std::cout << "size of the array.\n"; + return(size_); + } + + int i; + size_--; + for(i = aIndex; i < size_; i++) { + array_[i] = array_[i+1]; + } + array_[size_] = defaultValue_; + + return(size_); + } + /** + * @brief Set the value at a specified index. + * + * @param aIndex Index of the array element to be set. It is permissible + * for aIndex to be past the current end of the array- the capacity will + * be increased if necessary. Values between the current end of the array + * and aIndex are not initialized. + * @param aValue Value. + */ + void set(int aIndex,const T &aValue) + { + if(aIndex < 0) return; + + bool success = false; + if((aIndex+2) >= capacity_) { + int newCapacity; + success = computeNewCapacity(aIndex+2, newCapacity); + if(!success) return; + success = ensureCapacity(newCapacity); + if(!success) return; + } + array_[aIndex] = aValue; + if(aIndex >= size_) size_ = aIndex + 1; + } + /** + * @brief Get a pointer to the low-level array. + * + * @return Vecder to the low-level array. + */ + T* get() + { + return(array_); + } + /** + * @brief Get a pointer to the low-level array. + * + * @return Vecder to the low-level array. + */ + + const T* get() const + { + return(array_); + } + /** + * @brief Get a const reference to the value at a specified array index. + * + * @details If the index is negative or passed the end of the array, an exception + * is thrown. + * + * For faster execution, the array elements can be accessed through the + * overloaded operator[], which does no bounds checking. + * + * @param aIndex Index of the desired array element. + * @return const reference to the array element. + * @throws Exception if (aIndex<0)||(aIndex>=size_). + * @see operator[]. + */ + const T& get(int aIndex) const + { + if((aIndex < 0) || (aIndex >= size_)) { + std::stringstream msg; + msg << "Array index out of bounds. " << "."; + throw (msg.str(),__FILE__,__LINE__); + } + return(array_[aIndex]); + } + /** + * Get the last value in the array. + * + * @return Last value in the array. + * @throws Exception if the array is empty. + */ + const T& getLast() const + { + if(size_ <= 0) { + std::stringstream msg; + msg << "Array is empty. " << "."; + throw (msg.str(),__FILE__,__LINE__); + } + return(array_[size_ - 1]); + } + /** + * Get writable reference to last value in the array. + * + * @return writable reference to Last value in the array. + * @throws Exception if the array is empty. + */ + T& updLast() const + { + if(size_ <= 0) { + std::stringstream msg; + msg << "Array is empty. " << "."; + throw (msg.str(),__FILE__,__LINE__); + } + return(array_[size_ - 1]); + } + /** + * Linear search for an element matching a given value. + * + * @param aValue Value to which the array elements are compared. + * @return Index of the array element matching aValue. If there is more than + * one such elements with the same value the index of the first of these elements + * is returned. If no match is found, -1 is returned. + */ + int findIndex(const T &aValue) const + { + for(int i = 0; i < size_; i++) if(array_[i] == aValue) return i; + return -1; + } + + /** + * Linear search in reverse for an element matching a given value. + * + * @param aValue Value to which the array elements are compared. + * @return Index of the array element matching aValue. If there is more than + * one such elements with the same value the index of the last of these elements + * is returned. If no match is found, -1 is returned. + */ + int rfindIndex(const T &aValue) const + { + for(int i=size_ - 1; i >= 0; i--) if(array_[i]==aValue) return i; + return -1; + } + }; /** ended of class Array. */ + +} +#endif //ARRAY_SIMBODY_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/exception.cpp b/SPHINXsys/src/shared/simbody_sphinxsys/exception.cpp new file mode 100644 index 0000000000..0d564cace5 --- /dev/null +++ b/SPHINXsys/src/shared/simbody_sphinxsys/exception.cpp @@ -0,0 +1,84 @@ +/** + * @file exception.cpp + * @author Chi Zhang and Xiangyu Hu + */ +#include "exception.h" + +namespace SPH { + //===============================================================// + Exception::Exception(const std::string &aMsg, const std::string &aFile, int aLine): + exception() + { + setNull(); + + setMessage(aMsg); + file_ = aFile; + line_ = aLine; + } + //===============================================================// + namespace { + std::string findFileName(const std::string& filepath) + { + std::string::size_type pos = filepath.find_last_of("/\\"); + if (pos + 1 >= filepath.size()) pos = 0; + return filepath.substr(pos + 1); + } + } + //===============================================================// + Exception::Exception(const std::string& file, size_t line, + const std::string& func) + { + addMessage("\tThrown at " + findFileName(file) + ":" + + std::to_string(line) + " in " + func + "()."); + } + //===============================================================// + Exception::Exception(const std::string& file, size_t line, + const std::string& func, const std::string& msg) + : Exception{file, line, func} + { + addMessage(msg); + } + //===============================================================// + void Exception::addMessage(const std::string& msg) + { + if(msg_.length() == 0) + msg_ = msg; + else + msg_ = msg + "\n" + msg_; + } + //===============================================================// + const char* Exception::what() const noexcept + { + return getMessage(); + } + //===============================================================// + void Exception::setNull() + { + setMessage(""); + line_ = -1; + } + //===============================================================// + void Exception::setMessage(const std::string &aMsg) + { + msg_ = aMsg; + } + //===============================================================// + const char* Exception::getMessage() const + { + return(msg_.c_str()); + } + //===============================================================// + void Exception::print(std::ostream &aOut) const + { + aOut << "\nException:\n"; + + if(file_.size()>0) { + aOut << " file= " << file_ << '\n'; + } + + if(line_ >= 0) { + aOut << " line= " << line_ << '\n'; + } + aOut << '\n' << std::endl; + } +}/** End of namespcae */ \ No newline at end of file diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/exception.h b/SPHINXsys/src/shared/simbody_sphinxsys/exception.h new file mode 100644 index 0000000000..3a747f2c51 --- /dev/null +++ b/SPHINXsys/src/shared/simbody_sphinxsys/exception.h @@ -0,0 +1,142 @@ +/** + * @file exception.h + * @details A toolkit for exception functionality. + * @author Chi Zhang and Xiangyu Hu. + */ + +#ifndef EXCEPTION_SIMBODY_H +#define EXCEPTION_SIMBODY_H + + + +#include "base_data_package.h" + +#include +#include +#include +#include +#include + +namespace SPH { + /** + * @class Exception + * @brief A class for basic exception functionality. + * @details Exception class manages the concatenation of error messages from all the + * derived classes. When creating new exceptions, remember to call addMessage() + * as shown above if the exception class does have any error message. + */ + class Exception : public std::exception { + private: + std::string msg_; /**< A user set message for the exception. */ + std::string file_; /**< File in which the error occurred. */ + int line_; /**< Line number at which the error occurred. */ +public: + /** + * @brief Default Constructor + * @details This constructor is for backward compatibility. Use the constructor + * taking file, line, func. + */ + Exception(const std::string &aMsg="", const std::string &aFile="", int aLine=-1); + + /** + * @brief Call this constructor from derived classes to add file, line and + * function information to the error message. Use this when throwing + * Derived classes. Use OPENSIM_THROW_<> macros at throw sites. + */ + Exception(const std::string& file, size_t line, const std::string& func); + /** + * @brief Use this when you want to throw an Exception and also provide a message. + */ + Exception(const std::string& file, size_t line, + const std::string& func, const std::string& msg); + /** + * @ brief Destructor. + */ + virtual ~Exception() throw() {} + + protected: + /** Add to the error message that will be returned for the exception. */ + void addMessage(const std::string& msg); + + private: + void setNull(); + + public: + void setMessage(const std::string &aMsg); + const char* getMessage() const; + + virtual void print(std::ostream &aOut) const; + + const char* what() const noexcept override; + }; + + class InvalidArgument : public Exception { + public: + InvalidArgument(const std::string& file, + size_t line, + const std::string& func, + const std::string& msg = "") : + Exception(file, line, func) { + std::string mesg = "Invalid Argument. " + msg; + + addMessage(mesg); + } + }; + + class InvalidCall : public Exception { + public: + InvalidCall(const std::string& file, + size_t line, + const std::string& func, + const std::string& msg = "") : + Exception(file, line, func) { + std::string mesg = "Invalid Call. " + msg; + + addMessage(mesg); + } + }; + + class InvalidTemplateArgument : public Exception { + public: + InvalidTemplateArgument(const std::string& file, + size_t line, + const std::string& func, + const std::string& msg) : + Exception(file, line, func) { + std::string mesg = "Invalid Template argument. " + msg; + + addMessage(mesg); + } + }; + + class IndexOutOfRange : public Exception { + public: + IndexOutOfRange(const std::string& file, + size_t line, + const std::string& func, + size_t index, + size_t min, + size_t max) : + Exception(file, line, func) { + std::string msg = "min = " + std::to_string(min); + msg += " max = " + std::to_string(max); + msg += " index = " + std::to_string(index); + + addMessage(msg); + } + }; + + class KeyNotFound : public Exception { + public: + KeyNotFound(const std::string& file, + size_t line, + const std::string& func, + const std::string& key) : + Exception(file, line, func) { + std::string msg = "Key '" + key + "' not found."; + + addMessage(msg); + } + }; +}/** end of namespace */ +#endif //EXCEPTION_SIMBODY_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/simbody_middle.h b/SPHINXsys/src/shared/simbody_sphinxsys/simbody_middle.h new file mode 100644 index 0000000000..c08b07f584 --- /dev/null +++ b/SPHINXsys/src/shared/simbody_sphinxsys/simbody_middle.h @@ -0,0 +1,23 @@ +/** + * @file simbody_middle.h + * @brief file to include Simbody headers and suppress their warnings + * @author Wen-Yang Chu + */ + +#ifndef SIMBODY_MIDDLE_H +#define SIMBODY_MIDDLE_H + +#ifdef __linux__ +#pragma GCC system_header //for GCC/CLANG +#elif _WIN32 +#pragma warning(push, 0) //for MSVC +#endif + +#include "SimTKcommon.h" +#include "SimTKmath.h" +#include "Simbody.h" + +#ifdef _WIN32 +#pragma warning(pop) //for MSVC +#endif +#endif //SIMBODY_MIDDLE_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/state_engine.cpp b/SPHINXsys/src/shared/simbody_sphinxsys/state_engine.cpp new file mode 100644 index 0000000000..6ba45f2526 --- /dev/null +++ b/SPHINXsys/src/shared/simbody_sphinxsys/state_engine.cpp @@ -0,0 +1,363 @@ +/** + * @file statengine.cpp + * @brief engine of state functions are defined here + * @author Chi Zhang and Xiangyu Hu + */ + +#include "state_engine.h" + +namespace SPH { + //===============================================================// + StateEngine:: + StateEngine(SimTK::MultibodySystem& system) : + simbody_xml_engine_("state_xml", "mbsystem") + { + mbsystem_ = system; + restart_folder_ = "./rstfile"; + if (!fs::exists(restart_folder_)) + { + fs::create_directory(restart_folder_); + } + } + //===============================================================// + void StateEngine::InitializeState() + { + /** Clear cached list of all related + StateVariables if any from a previousSystem. + */ + allstatevariables_.clear(); + getMultibodySystem().invalidateSystemTopologyCache(); + getMultibodySystem().realizeTopology(); + /** Set the model's operating state (internal member variable) to the + default state that is stored inside the System. + */ + working_state_ = getMultibodySystem().getDefaultState(); + /** Process the modified modeling option. */ + getMultibodySystem().realizeModel(working_state_); + /** Realize instance variables that may have been set above. This + * means floating point parameters such as mass properties and + * geometry placements are frozen. + */ + getMultibodySystem().realize(working_state_, SimTK::Stage::Instance); + /** Realize the initial configuration in preparation. This + * initial configuration does not necessarily satisfy constraints. + */ + getMultibodySystem().realize(working_state_, SimTK::Stage::Position); + } + //===============================================================// + SimTK::MultibodySystem& StateEngine::getMultibodySystem() + { + return mbsystem_.getRef(); + } + //===============================================================// + void StateEngine::addStateVariable(std::string statevariablename, + SimTK::Stage invalidatestage) + { + if ((invalidatestage < SimTK::Stage::Position) || + (invalidatestage > SimTK::Stage::Dynamics)) + { + std::stringstream msg; + msg << "StateEngine::addStateVariable: invalidatestage " + "must be Position, Velocity or Dynamics. " << __FILE__ << __LINE__; + throw (msg.str()); + } + /** Allocate space for a new state variable. */ + AddedStateVariable* asv = + new AddedStateVariable(statevariablename, *this, invalidatestage); + // Add it to the Component and let it take ownership + addStateVariable(asv); + } + //===============================================================// + void StateEngine::addStateVariable(StateEngine::StateVariable* statevariable) + { + std::string& statevariablename = statevariable->getName(); + /** don't add state if there is another state variable with the same name. */ + std::map::const_iterator it; + it = namedstatevariableinfo_.find(statevariablename); + if (it != namedstatevariableinfo_.end()) + { + std::stringstream msg; + msg << "StateEngine::addStateVariable: State variable " << + statevariablename << " already exists." << __FILE__ << __LINE__; + throw (msg.str()); + } + int order = (int)namedstatevariableinfo_.size(); + /** assign a "slot" for a state variable by name + state variable index will be invalid by default + upon allocation during realizeTopology the index will be set + */ + namedstatevariableinfo_[statevariablename] = StateVariableInfo(statevariable, order); + + } + //===============================================================// + StateEngine::StateVariable* StateEngine:: + traverseToStateVariable(std::string& pathname) + { + auto it = namedstatevariableinfo_.find(pathname); + if (it != namedstatevariableinfo_.end()) + { + return it->second.statevariable_.get(); + } + else { + return nullptr; + } + } + //===============================================================//. + Array StateEngine::getStateVariableNames() + { + std::map::const_iterator it; + it = namedstatevariableinfo_.begin(); + + Array names;//("",(int)namedstatevariableinfo_.size()); + + while (it != namedstatevariableinfo_.end()) + { + names[it->second.order] = it->first; + it++; + } + return names; + } + //===============================================================// + int StateEngine::getNumOfStateVariables() + { + return getNumStateVariablesAddedByEngine(); + } + //===============================================================// + bool StateEngine::isAllStatesVariablesListValid() + { + int nsv = getNumOfStateVariables(); + /** Consider the list of all StateVariables to be valid if all of + the following conditions are true: + 1. a System has been associated with the list of StateVariables + 2. The list of all StateVariables is correctly sized (initialized) + 3. The System associated with the StateVariables is the current System */ + bool valid = + !statesassociatedsystem_.empty() && + (int)allstatevariables_.size() == nsv && + getMultibodySystem().isSameSystem(statesassociatedsystem_.getRef()); + + return valid; + } + //===============================================================// + SimTK::Vector StateEngine::getStateVariableValues() + { + int nsv = getNumOfStateVariables(); + /** if the StateVariables are invalid, rebuild the list. */ + if (!isAllStatesVariablesListValid()) + { + statesassociatedsystem_.reset(&getMultibodySystem()); + allstatevariables_.clear(); + allstatevariables_.resize(nsv); + Array names = getStateVariableNames(); + for (int i = 0; i < nsv; ++i) + allstatevariables_[i].reset(traverseToStateVariable(names[i])); + } + + SimTK::Vector statevariablevalues(nsv, SimTK::NaN); + for (int i = 0; i < nsv; ++i) { + statevariablevalues[i] = allstatevariables_[i]->getValue(); + std::cout << statevariablevalues[i] << std::endl; + } + return statevariablevalues; + } + //-----------------------------------------------------------------------------// + // OTHER REALIZE METHODS + //-----------------------------------------------------------------------------// + /** override virtual methods. */ + Real StateEngine::AddedStateVariable::getValue() + { + SimTK::ZIndex zix(getVarIndex()); + if (getSubsysIndex().isValid() && zix.isValid()) { + const SimTK::Vector& z = getOwner().getDefaultSubsystem().getZ(getOwner().working_state_); + return z[SimTK::ZIndex(zix)]; + } + + std::stringstream msg; + msg << "StateEngine::AddedStateVariable::getValue: ERR- variable '" + << getName() << "' is invalid! " << __FILE__ << __LINE__; + throw (msg.str()); + return SimTK::NaN; + } + //===============================================================// + void StateEngine::AddedStateVariable::setValue(Real value) + { + SimTK::ZIndex zix(getVarIndex()); + if (getSubsysIndex().isValid() && zix.isValid()) { + SimTK::Vector& z = getOwner().getDefaultSubsystem().updZ(getOwner().working_state_); + z[SimTK::ZIndex(zix)] = value; + return; + } + + std::stringstream msg; + msg << "StateEngine::AddedStateVariable::setValue: ERR- variable '" + << getName() << "' is invalid! " << __FILE__ << __LINE__;; + throw (msg.str()); + } + //===============================================================// + double StateEngine::AddedStateVariable:: + getDerivative() + { + //return getCacheVariableValue(state, getName()+"_deriv"); + return 0.0; + } + //===============================================================// + void StateEngine::AddedStateVariable:: + setDerivative(Real deriv) + { + //return setCacheVariableValue(state, getName()+"_deriv", deriv); + } + //===============================================================// + void StateEngine::reporter(SimTK::State& state_) + { + const SimTK::SimbodyMatterSubsystem& matter_ = getMultibodySystem().getMatterSubsystem(); + for (SimTK::MobilizedBodyIndex mbx(0); mbx < matter_.getNumBodies(); ++mbx) + { + + const SimTK::MobilizedBody& mobod = matter_.getMobilizedBody(mbx); + + int num_q_ = mobod.getNumQ(state_); + for (int i = 0; i < num_q_; i++) + { + std::cout << num_q_ << " " << mobod.getOneQ(state_, SimTK::QIndex(i)) << std::endl; + } + int num_u_ = mobod.getNumU(state_); + for (int i = 0; i < num_u_; i++) + { + std::cout << num_u_ << " " << mobod.getOneU(state_, SimTK::UIndex(i)) << std::endl; + } + std::cout << " Body Info : " << std::endl; + std::cout << " Transform : " << mobod.getBodyTransform(state_) << std::endl; + std::cout << " Rotation : " << mobod.getBodyRotation(state_) << std::endl; + std::cout << " Origin : " << mobod.getBodyOriginLocation(state_) << std::endl; + } + } + //===============================================================// + void StateEngine::resizeXmlDocForSimbody(size_t input_size) + { + size_t total_elements = simbody_xml_engine_.SizeOfXmlDoc(); + + if (total_elements <= input_size) + { + for (size_t i = total_elements; i != input_size; ++i) + simbody_xml_engine_.addElementToXmlDoc("mbbody"); + } + } + //===============================================================// + void StateEngine::writeStateInfoToXml(int ite_rst_, const SimTK::State& state_) + { + const SimTK::SimbodyMatterSubsystem& matter_ = getMultibodySystem().getMatterSubsystem(); + resizeXmlDocForSimbody(matter_.getNumBodies()); + SimTK::Xml::element_iterator ele_ite = simbody_xml_engine_.root_element_.element_begin(); + SimTK::MobilizedBodyIndex mbx(0); + for (int k = 0; k != matter_.getNumBodies(); ++k) + { + const SimTK::MobilizedBody& mobod = matter_.getMobilizedBody(mbx); + + int num_q_ = mobod.getNumQ(state_); + for (int i = 0; i < num_q_; i++) + { + Real mobod_q = mobod.getOneQ(state_, SimTK::QIndex(i)); + std::string ele_name = "QIndx_" + std::to_string(i); + simbody_xml_engine_.setAttributeToElement(ele_ite, ele_name, mobod_q); + } + + int num_u_ = mobod.getNumU(state_); + for (int i = 0; i < num_u_; i++) + { + Real mobod_u = mobod.getOneU(state_, SimTK::UIndex(i)); + std::string ele_name = "UIndx_" + std::to_string(i); + simbody_xml_engine_.setAttributeToElement(ele_ite, ele_name, mobod_u); + } + Vec3d transform_ = mobod.getBodyTransform(state_).p(); + simbody_xml_engine_.setAttributeToElement(ele_ite, "Transform", transform_); + + ++ele_ite; + } + std::string filefullpath = restart_folder_ + "/Simbody_Rst_" + std::to_string(ite_rst_) + ".xml"; + simbody_xml_engine_.writeToXmlFile(filefullpath); + } + //===============================================================// + SimTK::State StateEngine::readAndSetStateInfoFromXml(int ite_rst_, SimTK::MultibodySystem& system_) + { + std::string filefullpath = restart_folder_ + "/Simbody_Rst_" + std::to_string(ite_rst_) + ".xml"; + const SimTK::SimbodyMatterSubsystem& matter_ = system_.getMatterSubsystem(); + SimTK::State state_ = system_.getDefaultState(); + if (!fs::exists(filefullpath)) + { + std::cout << "\n Error: the input file:" << filefullpath << " is not valid" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + else { + int num_mobod = 0; + simbody_xml_engine_.loadXmlFile(filefullpath); + SimTK::Xml::element_iterator ele_ite_ = simbody_xml_engine_.root_element_.element_begin(); + for (; ele_ite_ != simbody_xml_engine_.root_element_.element_end(); ++ele_ite_) + { + const SimTK::MobilizedBody& mobod = matter_.getMobilizedBody(SimTK::MobilizedBodyIndex(num_mobod)); + int num_q_ = mobod.getNumQ(state_); + Real q_tmp_ = 0.0; + if (num_q_ != 0) + { + for (int i = 0; i < num_q_; i++) + { + std::string attr_name = "QIndx_" + std::to_string(i); + simbody_xml_engine_.getRequiredAttributeValue(ele_ite_, attr_name, q_tmp_); + mobod.setOneQ(state_, SimTK::QIndex(i), q_tmp_); + } + } + int num_u_ = mobod.getNumU(state_); + Real u_tmp_ = 0.0; + if (num_u_ != 0) + { + for (int i = 0; i < num_u_; i++) + { + std::string attr_name = "UIndx_" + std::to_string(i); + simbody_xml_engine_.getRequiredAttributeValue(ele_ite_, attr_name, u_tmp_); + mobod.setOneU(state_, SimTK::UIndex(i), u_tmp_); + } + } + Vec3d transform_(0); + simbody_xml_engine_.getRequiredAttributeValue(ele_ite_, "Transform", transform_); + mobod.setQToFitTransform(state_, SimTK::Transform(transform_)); + + num_mobod++; + } + } + system_.realizeModel(state_); + return state_; + } + //------------------------------------------------------------------------------ + // REALIZE THE SYSTEM TO THE REQUIRED COMPUTATIONAL STAGE + //------------------------------------------------------------------------------ + void StateEngine::realizeTime() + { + getMultibodySystem().realize(working_state_, SimTK::Stage::Time); + } + //===============================================================// + void StateEngine::realizePosition() + { + getMultibodySystem().realize(working_state_, SimTK::Stage::Position); + } + //===============================================================// + void StateEngine::realizeVelocity() + { + getMultibodySystem().realize(working_state_, SimTK::Stage::Velocity); + } + //===============================================================// + void StateEngine::realizeDynamics() + { + getMultibodySystem().realize(working_state_, SimTK::Stage::Dynamics); + } + //===============================================================// + void StateEngine::realizeAcceleration() + { + getMultibodySystem().realize(working_state_, SimTK::Stage::Acceleration); + } + //===============================================================// + void StateEngine::realizeReport() + { + getMultibodySystem().realize(working_state_, SimTK::Stage::Report); + } + //===============================================================// +} diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/state_engine.h b/SPHINXsys/src/shared/simbody_sphinxsys/state_engine.h new file mode 100644 index 0000000000..8ba6cdcfe4 --- /dev/null +++ b/SPHINXsys/src/shared/simbody_sphinxsys/state_engine.h @@ -0,0 +1,380 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file state_engine.h + * @details The StateEngine class defines the interface used to add computational + * elements to the underlying SimTK::System (MultibodySystem). It specifies + * the interface that simbodystates must satisfy in order to be part of the system + * and provides a series of helper methods for adding variables + * (state, discrete, cache, ...) to the underlying system. As such, SimbodyState + * handles all of the bookkeeping of system indices and provides convenience + * access to variable values (incl. derivatives) via their names as strings. + * @author Chi Zhang and Xiangyu Hu. + */ + +#ifndef STATE_ENGINE_SIMBODY_H +#define STATE_ENGINE_SIMBODY_H + + +#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING + +#include "base_data_package.h" +#include "array.h" +#include "exception.h" +#include "all_simbody.h" + +#include +#include +#include +#include + +#include "Simbody.h" +#include "SimTKmath.h" +#include "SimTKcommon.h" +#include "simbody/internal/MultibodySystem.h" + +namespace SPH { + /** + * @class StateEngine + */ + class StateEngine { + //===============================================================// + // PROTECTED + //===============================================================// + protected: + + XmlEngine simbody_xml_engine_; + + void resizeXmlDocForSimbody(size_t input_size); + + /** + * @class StateVariable + * @details Derived simbodysate must create concrete StateVariables to expose their state + * variables. When exposing state variables allocated by the underlying Simbody + * (MobilizedBody, Constraint, Force, etc...) use its interface to + * implement the virtual methods below. + */ + class StateVariable { + //friend void StateEngine::addStateVariable(StateVariable* sv); + public: + /** Concstructor and destructor. */ + StateVariable() :name_(""), owner_(nullptr), + subsysindex_(SimTK::InvalidIndex), + varindex_(SimTK::InvalidIndex), + sysyindex_(SimTK::InvalidIndex) {} + explicit StateVariable(std::string& name, /**< state var name. */ + StateEngine& owner, /**< owning component. */ + SimTK::SubsystemIndex subsys, /**< subsystem for allocation. */ + int varindex) /**< variable's index in subsystem.*/ + : name_(name), owner_(&owner), subsysindex_(subsys), + varindex_(varindex), sysyindex_(SimTK::InvalidIndex) {} + virtual ~StateVariable() {} + + std::string& getName() { return name_; } + StateEngine& getOwner() { return *owner_; } + /** Get the index of simbody state variable. */ + int& getVarIndex() { return varindex_; } + /** Return the index of the subsystem used to make resource allocations. */ + SimTK::SubsystemIndex& getSubsysIndex() { return subsysindex_; } + /** Return the index in the global list of continuous state variables, Y. */ + SimTK::SystemYIndex& getSystemYIndex() { return sysyindex_; } + /** Set the index of simbody variable. */ + void setVarIndex(int index) { varindex_ = index; } + /** Set the index of the subsystem used to resource allocations. */ + void setSubsystemIndex(SimTK::SubsystemIndex& subsysindx) + { + subsysindex_ = subsysindx; + } + /** Concrete StateEngines implement how the state variable value is evaluated. */ + virtual Real getValue() = 0; + virtual void setValue(Real value) = 0; + virtual Real getDerivative() = 0; + /** The derivative a state should be a cache entry and thus does not change the state. */ + virtual void setDerivative(Real deriv) = 0; + private: + std::string name_; + SimTK::ReferencePtr owner_; + /** + * Identify which subsystem this state variable belongs to, which should + * be determined and set at creation time + */ + SimTK::SubsystemIndex subsysindex_; + /** + * The local variable index in the subsystem also provided at creation + * (e.g. can be QIndex, UIndex, or Zindex type) + */ + int varindex_; + /** + * Once allocated a state in the system will have a global index + * and that can be stored here as well + */ + SimTK::SystemYIndex sysyindex_; + }; + //===============================================================// + // PRIVATE + //===============================================================// + /** + * @class AddedStateVariable + * @brief Class for handling state variable added (allocated) by this StateEngine. + */ + class AddedStateVariable : public StateVariable { + public: + /** Constructors adn destrucutors. */ + AddedStateVariable() : StateVariable(), invalidatestage_(SimTK::Stage::Empty) {} + + /** Convenience constructor for defining a StateEngine added state variable */ + explicit AddedStateVariable(std::string& name, /**< state var name. */ + StateEngine& owner, + SimTK::Stage invalidatestage) : /**< stage this variable invalidates. */ + StateVariable(name, owner, + SimTK::SubsystemIndex(SimTK::InvalidIndex), + SimTK::InvalidIndex), + invalidatestage_(SimTK::Stage::Empty) {} + + /** override virtual methods. */ + Real getValue() override; + void setValue(Real value) override; + Real getDerivative() override; + void setDerivative(Real deriv) override; + + private: + /** Changes in state variables trigger recalculation of appropriate cache + * variables by automatically invalidating the realization stage specified + * upon allocation of the state variable. + */ + SimTK::Stage invalidatestage_; + }; + /** + * @struct StateVariableInfo + * @brief To hold related info about discrete variables. + */ + struct StateVariableInfo { + StateVariableInfo() {} + explicit StateVariableInfo(StateEngine::StateVariable* sv, int order) : + statevariable_(sv), order(order) {} + + /** Need empty copy constructor because default compiler generated + will fail since it cannot copy a unique_ptr. */ + StateVariableInfo(const StateVariableInfo&) {} + /** Now handle assignment by moving ownership of the unique pointer. */ + StateVariableInfo& operator=(const StateVariableInfo& svi) { + if (this != &svi) { + /** assignment has to be const but cannot swap const + want to keep unique pointer to guarantee no multiple reference + so use const_cast to swap under the covers. */ + StateVariableInfo* mutableSvi = const_cast(&svi); + statevariable_.swap(mutableSvi->statevariable_); + } + order = svi.order; + return *this; + } + + // State variable + std::unique_ptr statevariable_; + // order of allocation + int order; + }; + //===============================================================// + // PULIC + //===============================================================// + public: + + /** Add a continuous system state variable belonging to this Engine, + and assign a name by which to refer to it. Changing the value of this state + variable will automatically invalidate everything at and above its + \a invalidatesStage, which is normally Stage::Dynamics meaning that there + are forces that depend on this variable. If you define one or more + of these variables you must also override computeStateVariableDerivatives() + to provide time derivatives for them. Note, all corresponding system + indices are automatically determined using this interface. As an advanced + option you may choose to hide the state variable from being accessed outside + of this component, in which case it is considered to be "hidden". + You may also want to create an Output for this state variable; see + #OpenSim_DECLARE_OUTPUT_FOR_STATE_VARIABLE for more information. Reporters + should use such an Output to get the StateVariable's value (instead of using + getStateVariableValue()). + + @param[in] stateVariableName string value to access variable by name + @param[in] invalidatesStage the system realization stage that is + invalidated when variable value is changed + @param[in] isHidden flag (bool) to optionally hide this state + variable from being accessed outside this + component as an Output + */ + void addStateVariable(std::string statevariablename, + SimTK::Stage invalidatestage); + + /** The above method provides a convenient interface to this method, which + automatically creates an 'AddedStateVariable' and allocates resources in the + SimTK::State for this variable. This interface allows the creator to + add/expose state variables that are allocated by underlying Simbody + components and specify how the state variable value is accessed by + implementing a concrete StateVariable and adding it to the StateEngine using + this method. + You may also want to create an Output for this state variable; see + #OpenSim_DECLARE_OUTPUT_FOR_STATE_VARIABLE for more information. Reporters + should use such an Output to get the StateVariable's value (instead of + using getStateVariableValue()). + */ + void addStateVariable(StateEngine::StateVariable* statevariable); + //===========================================================// + SimTK::DefaultSystemSubsystem& getDefaultSubsystem() + { + return const_cast + (getMultibodySystem().getDefaultSubsystem()); + } + SimTK::DefaultSystemSubsystem& updDefaultSubsystem() + { + return getMultibodySystem().updDefaultSubsystem(); + } + //===========================================================// + /** + * Get a StateVariable anywhere in the state engine, given a + * StateVariable path. + * This returns nullptr if a StateVariable does not exist at the specified + * path or if the path is invalid. + */ + StateVariable* traverseToStateVariable(std::string& pathname); + /** Map names of continuous state variables of the Engine to their + underlying SimTK indices. */ + mutable std::map namedstatevariableinfo_; + /** Check that the list of _allStateVariables is valid. */ + bool isAllStatesVariablesListValid(); + + /** Array of all state variables for fast access during simulation. */ + mutable SimTK::Array_ > + allstatevariables_; + /** A handle the System associated with the above state variables. */ + mutable SimTK::ReferencePtr statesassociatedsystem_; + + /** Default constructor **/ + StateEngine(SimTK::MultibodySystem& system); + + /** Reference pointer to the system that this engine manage. */ + SimTK::ReferencePtr mbsystem_; + /** This is the internal 'writable' state of the engine. + * working_state_ will be set to the system default state when + * initializeState() is called. + */ + SimTK::State working_state_; + + /** Destructor is virtual to allow concrete StateEngine to cleanup. **/ + virtual ~StateEngine() {}; + /** Set up the working state in presetn engine */ + void InitializeState(); + /** + * Get the underlying MultibodySystem that this StateEngine is connected to. + * Make sure you have called Model::initSystem() prior to accessing the System. + * Throws an Exception if the System has not been created or the StateEngine + * has not added itself to the System. + * @see hasSystem(). */ + SimTK::MultibodySystem& getMultibodySystem(); + /** + * Get writable reference to the MultibodySystem that this component is + * connected to. + */ + SimTK::MultibodySystem& updMultibodySystem(); + /** + * Get the number of "continuous" state variables maintained by the + * State Engine. + */ + int getNumOfStateVariables(); + /** Get the number of continuous states that the State Engine added to the + underlying computational system.*/ + int getNumStateVariablesAddedByEngine() + { + return (int)namedstatevariableinfo_.size(); + } + /** + * Get the names of "continuous" state variables maintained by the Engine + */ + Array getStateVariableNames(); + /** + * Get all values of the state variables allocated by this StateEngine. + * + * @param state the State for which to get the value + * @return Vector of state variable values of length getNumStateVariables() + * in the order returned by getStateVariableNames() + * @throws StateEngineHasNoSystem if this object has not been added to a + * System (i.e., if initSystem has not been called) + */ + SimTK::Vector getStateVariableValues(); + /** + * report the state info by requested. + */ + void reporter(SimTK::State& state_); + /** + * Write the state data to xml file. + * For all bodies in the matter system, their generalized coordinates, + * generalized velocities and transformations of the origin points are written in + * the output file + */ + std::string restart_folder_; + void writeStateInfoToXml(int ite_rst_, const SimTK::State& state_); + /** + * read state infor from xml and set it to sate. + * For all bodies in the matter system, their generalized coordinates, + * generalized velocities and transformations of the origin points are read from + * the restart file + */ + SimTK::State readAndSetStateInfoFromXml(int ite_rst_, SimTK::MultibodySystem& system_); + /**@name Realize the Simbody System and State to Computational Stage. + Methods in this section enable advanced and scripting users access to + realize the Simbody MultibodySystem and the provided state to a desired + computational (realization) Stage. + */ + + /** + * Perform computations that depend only on time and earlier stages. + */ + void realizeTime(); + /** + * Perform computations that depend only on position-level state + * variables and computations performed in earlier stages (including time). + */ + void realizePosition(); + /** + * Perform computations that depend only on velocity-level state + * variables and computations performed in earlier stages (including position, + * and time). + */ + void realizeVelocity(); + /** + * Perform computations (typically forces) that may depend on + * dynamics-stage state variables, and on computations performed in earlier + * stages (including velocity, position, and time), but not on other forces, + * accelerations, constraint multipliers, or reaction forces. + */ + void realizeDynamics(); + /** + * Perform computations that may depend on applied forces. + */ + void realizeAcceleration(); + /** + * Perform computations that may depend on anything but are only used + * for reporting and cannot affect subsequent simulation behavior. + */ + void realizeReport(); + }; +} +#endif //STATE_ENGINE_SIMBODY_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.cpp b/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.cpp new file mode 100644 index 0000000000..ed33f9835e --- /dev/null +++ b/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.cpp @@ -0,0 +1,116 @@ +/** + * @file xml.cpp + * @brief XML functions are defined here + * @author Chi Zhang and Xiangyu Hu + */ + +#include "xml_engine.h" + +namespace SPH +{ + //=================================================================================================// + XmlEngine::XmlEngine(const std::string& xml_name, const std::string& root_tag) : + xml_name_(xml_name) + { + xmldoc_.setRootTag(root_tag); + root_element_ = xmldoc_.getRootElement(); + } + //=================================================================================================// + void XmlEngine::addElementToXmlDoc(const std::string& element_name) + { + SimTK::Xml::Element* element = new SimTK::Xml::Element(element_name); + root_element_.insertNodeAfter(root_element_.node_end(), *element); + } + //=================================================================================================// + void XmlEngine::addChildToElement(SimTK::Xml::Element& father_element, + const std::string& child_name) + { + SimTK::Xml::Element* child_element = new SimTK::Xml::Element(child_name); + father_element.insertNodeAfter(father_element.node_end(), *child_element); + } + //=================================================================================================// + void XmlEngine::setAttributeToElement(const SimTK::Xml::element_iterator& ele_ite, + const std::string& attrib_name, const Matd& value) + { + int num_dim = value.nrow(); + SimTK::Array_ array_(num_dim * num_dim); + for (int i = 0; i < num_dim; i++) + for (int j = 0; j < num_dim; j++) + array_[i * num_dim + j] = value(i, j); + SimTK::Xml::Attribute attr_(attrib_name, SimTK::String(array_)); + ele_ite->setAttributeValue(attr_.getName(), attr_.getValue()); + } + //=================================================================================================// + void XmlEngine::getRequiredAttributeMatrixValue(SimTK::Xml::element_iterator& ele_ite_, const std::string& attrib_name, Matd& value) + { + std::string value_in_string = ele_ite_->getRequiredAttributeValue(attrib_name); + SimTK::Array_ array_; + array_ = SimTK::convertStringTo>(value_in_string); + int num_dim_2 = array_.size(); + int num_dim; + if (num_dim_2 == 4) { + num_dim = 2; + } + else if (num_dim_2 == 9) { + num_dim = 3; + } + else + { + std::cout << "\n Error: the input dimension of deformation tensor:" << num_dim_2 << " is not valid" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + for (int i = 0; i < num_dim; i++) + for (int j = 0; j < num_dim; j++) + value(i, j) = array_[i * num_dim + j]; + } + //=================================================================================================// + void XmlEngine::writeToXmlFile(const std::string& filefullpath) + { + xmldoc_.writeToFile(filefullpath); + } + //=================================================================================================// + void XmlEngine::loadXmlFile(const std::string& filefullpath) + { + xmldoc_.readFromFile(filefullpath); + root_element_ = xmldoc_.getRootElement(); + } + //=================================================================================================// + std::string XmlEngine::getRootElementTag() + { + return xmldoc_.getRootTag(); + } + //=================================================================================================// + std::string XmlEngine::getElementTag(SimTK::Xml::Element& element) + { + return element.getElementTag(); + } + //=================================================================================================// + void XmlEngine::resizeXmlDocForParticles(size_t input_size) + { + size_t total_elements = std::distance(root_element_.element_begin(), + root_element_.element_end()); + + if (total_elements <= input_size) + { + for (size_t i = total_elements; i != input_size; ++i) addElementToXmlDoc("particle"); + } + else + { + std::cout << "\n Error: XML Engine allows increase date size only!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + } + }; + //=================================================================================================// + size_t XmlEngine::SizeOfXmlDoc() + { + return std::distance(root_element_.element_begin(), root_element_.element_end()); + } + //=================================================================================================// + SimTK::Xml::Element XmlEngine::getChildElement(const std::string& tag) + { + return root_element_.getOptionalElement(tag); + }; + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.h b/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.h new file mode 100644 index 0000000000..ffa0dea909 --- /dev/null +++ b/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.h @@ -0,0 +1,114 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** + * @file xml_engine.h + * @brief XML class for xml input and output, this is GUI of simbody xml parser. + * @author Chi Zhang and Xiangyu Hu. + */ + +#ifndef XML_ENGINE_SIMBODY_H +#define XML_ENGINE_SIMBODY_H + + +#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING + +#include "base_data_package.h" +#include "sph_data_conainers.h" + +#include "SimTKcommon.h" +#include "SimTKcommon/internal/Xml.h" +#include "SimTKcommon/internal/String.h" + +#include +#include +#include + +#include +#ifdef __APPLE__ +#include +namespace fs = boost::filesystem; +#else +#include +namespace fs = std::experimental::filesystem; +#endif + +namespace SPH +{ + class XmlEngine + { + protected: + std::string xml_name_; /**< xml name. */ + SimTK::Xml::Document xmldoc_; /**< the xml document. */ + + public: + /** Constructor for XML output. */ + XmlEngine(const std::string& xml_name, const std::string& root_tag); + /** Defaut distructor. */ + virtual ~XmlEngine() {}; + + SimTK::Xml::Element root_element_; /**< Root element of document. */ + + /**Add existing element to root_element of Xml Doc. */ + void addElementToXmlDoc(const std::string& element_name); + + /**Add child element to a given element. */ + void addChildToElement(SimTK::Xml::Element& father_element, const std::string& child_name); + + /** Add an attribute of type string to an xml element. */ + template + void setAttributeToElement(const SimTK::Xml::element_iterator& ele_ite, const std::string& attrib_name, const T& value) + { + SimTK::Xml::Attribute attr_(attrib_name, SimTK::String(value)); + ele_ite->setAttributeValue(attr_.getName(), attr_.getValue()); + }; + /** Adds attribute of type matrix to an xml element. */ + void setAttributeToElement(const SimTK::Xml::element_iterator& ele_ite, const std::string& attrib_name, const Matd& value); + + /** Get the required attribute value of an element */ + template + void getRequiredAttributeValue(SimTK::Xml::element_iterator& ele_ite_, const std::string& attrib_name, T& value) + { + std::string value_in_string = ele_ite_->getRequiredAttributeValue(attrib_name); + value = SimTK::convertStringTo(value_in_string); + }; + /** Get the required int attribute valaue of an element */ + void getRequiredAttributeMatrixValue(SimTK::Xml::element_iterator& ele_ite_, const std::string& attrib_name, Matd& value); + + /** Write to XML file */ + void writeToXmlFile(const std::string& filefullpath); + /** Load XML file using XML parser. */ + void loadXmlFile(const std::string& filefullpath); + /** Get the Tag of root element as a string */ + std::string getRootElementTag(); + /** Get the Tag of a element as a string */ + std::string getElementTag(SimTK::Xml::Element& element); + /** resize of Xml doc */ + void resizeXmlDocForParticles(size_t input_size); + /** Get the size of Xml doc */ + size_t SizeOfXmlDoc(); + /** Get a reference to a child element */ + SimTK::Xml::Element getChildElement(const std::string& tag); + }; +} + +#endif //XML_ENGINE_SIMBODY_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/sphinxsys_system/CMakeLists.txt b/SPHINXsys/src/shared/sphinxsys_system/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/sphinxsys_system/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/SPHINXsys/src/shared/sphinxsys_system/sph_system.cpp b/SPHINXsys/src/shared/sphinxsys_system/sph_system.cpp new file mode 100644 index 0000000000..3a99583013 --- /dev/null +++ b/SPHINXsys/src/shared/sphinxsys_system/sph_system.cpp @@ -0,0 +1,139 @@ +/** + * @file sph_system.cpp + * @brief Definition of all system level functions + * @author Xiangyu Hu, Luhui Han and Chi Zhang + */ + +#include "sph_system.h" + +#include "base_body.h" +#include "body_relation.h" +#include "solid_dynamics.h" + +namespace SPH +{ + //=================================================================================================// + SPHSystem::SPHSystem(BoundingBox system_domain_bounds, + Real resolution_ref, size_t number_of_threads) : + system_domain_bounds_(system_domain_bounds), + resolution_ref_(resolution_ref), + tbb_global_control_(tbb::global_control::max_allowed_parallelism, number_of_threads), + in_output_(nullptr), restart_step_(0), run_particle_relaxation_(false), + reload_particles_(false) {} + //=================================================================================================// + void SPHSystem::addABody(SPHBody* sph_body) + { + bodies_.push_back(sph_body); + } + //=================================================================================================// + void SPHSystem::addARealBody(RealBody* real_body) + { + real_bodies_.push_back(real_body); + } + //=================================================================================================// + void SPHSystem::addASolidBody(SolidBody* solid_body) + { + solid_bodies_.push_back(solid_body); + } + //=================================================================================================// + void SPHSystem::addAFictitiousBody(FictitiousBody* fictitious_body) + { + fictitious_bodies_.push_back(fictitious_body); + } + //=================================================================================================// + void SPHSystem::initializeSystemCellLinkedLists() + { + for (auto &body : real_bodies_) + { + dynamic_cast(body)->updateCellLinkedList(); + } + } + //=================================================================================================// + void SPHSystem::initializeSystemConfigurations() + { + for (auto& body : bodies_) + { + for (size_t i = 0; i < body->body_relations_.size(); i++) + { + body->body_relations_[i]->updateConfiguration(); + } + + } + } + //=================================================================================================// + Real SPHSystem::getSmallestTimeStepAmongSolidBodies(Real CFL) + { + Real dt = Infinity; + for (size_t i = 0; i < solid_bodies_.size(); i++) + { + solid_dynamics::AcousticTimeStepSize computing_time_step_size(solid_bodies_[i], CFL); + Real dt_temp = computing_time_step_size.parallel_exec(); + if (dt_temp < dt) dt = dt_temp; + } + return dt; + } + //=================================================================================================// + #ifdef BOOST_AVAILABLE + void SPHSystem::handleCommandlineOptions(int ac, char* av[]) + { + try { + + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "produce help message") + ("r", po::value(), "Particle relaxation.") + ("i", po::value(), "Particle reload from input file.") + ("restart_step", po::value(), "Run form a restart file.") + ; + + po::variables_map vm; + po::store(po::parse_command_line(ac, av, desc), vm); + po::notify(vm); + + if (vm.count("help")) { + std::cout << desc << "\n"; + exit(0); + } + + if (vm.count("r")) { + run_particle_relaxation_ = vm["r"].as(); + std::cout << "Particle relaxation was set to " + << vm["r"].as() << ".\n"; + } + else { + std::cout << "Particle relaxation was set to default (" + << run_particle_relaxation_ <<").\n"; + } + + if (vm.count("i")) { + reload_particles_ = vm["i"].as(); + std::cout << "Particle reload from input file was set to " + << vm["i"].as() << ".\n"; + } + else { + std::cout << "Particle reload from input file was set to default (" + << reload_particles_ << ").\n"; + } + + if (vm.count("restart_step")) { + restart_step_ = vm["restart_step"].as(); + std::cout << "Restart step was set to " + << vm["restart_step"].as() << ".\n"; + } + else { + std::cout << "Restart inactivated, i.e. restart_step (" + << restart_step_ << ").\n"; + } + } + catch (std::exception & e) { + std::cerr << "error: " << e.what() << "\n"; + exit(1); + } + catch (...) { + std::cerr << "Exception of unknown type!\n"; + } + + } + #endif + //=================================================================================================// +} diff --git a/SPHINXsys/src/shared/sphinxsys_system/sph_system.h b/SPHINXsys/src/shared/sphinxsys_system/sph_system.h new file mode 100644 index 0000000000..8be4730d7c --- /dev/null +++ b/SPHINXsys/src/shared/sphinxsys_system/sph_system.h @@ -0,0 +1,80 @@ +/** + * @file sph_system.h + * @brief The SPH_System managing objects in the system level. + * @details Note that the system operation prefer these are application independent. + * @author Xiangyu Hu, Luhui Han and Chi Zhang + */ + +#ifndef SPH_SYSTEM_H +#define SPH_SYSTEM_H + + +#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING + +#define TBB_PREVIEW_GLOBAL_CONTROL 1 +#include +#ifdef BOOST_AVAILABLE +#include "boost/program_options.hpp" +namespace po = boost::program_options; +#endif + +#include "base_data_package.h" +#include "sph_data_conainers.h" + +#include +#include +/** Macro for APPLE compilers*/ +#ifdef __APPLE__ +#include +namespace fs = boost::filesystem; +#else +#include +namespace fs = std::experimental::filesystem; +#endif + +namespace SPH +{ + /** + * @brief Preclaimed classes. + */ + class SPHBody; + class In_Output; + + /** + * @class SPHSystem + * @brief The SPH system managing objects in the system level. + */ + class SPHSystem + { + public: + SPHSystem(BoundingBox system_domain_bounds, Real resolution_ref, + size_t number_of_threads = std::thread::hardware_concurrency()); + virtual ~SPHSystem() {}; + + BoundingBox system_domain_bounds_; /**< Lower and Upper domain bounds. */ + Real resolution_ref_; /**< refernce resolution of the SPH system */ + tbb::global_control tbb_global_control_; /**< global controling on the total number parallel threads */ + + In_Output* in_output_; /**< in_output setup */ + size_t restart_step_; /**< restart step */ + bool run_particle_relaxation_; /**< run particle relaxation for body fitted particle distribution */ + bool reload_particles_; /**< start the simulation with relaxed particles. */ + + SPHBodyVector bodies_; /**< All sph bodies. */ + SPHBodyVector fictitious_bodies_; /**< The bodies without inner particle configuration. */ + SPHBodyVector real_bodies_; /**< The bodies with inner particle configuration. */ + SolidBodyVector solid_bodies_; /**< The bodies with inner particle configuration and acoustic time steps . */ + + void addABody(SPHBody* sph_body); + void addARealBody(RealBody* real_body); + void addASolidBody(SolidBody* solid_body); + void addAFictitiousBody(FictitiousBody* fictitious_body); + void initializeSystemCellLinkedLists(); + void initializeSystemConfigurations(); + Real getSmallestTimeStepAmongSolidBodies(Real CFL = 0.6); + #ifdef BOOST_AVAILABLE + void handleCommandlineOptions(int ac, char* av[]); + #endif + }; +} +#endif //SPH_SYSTEM_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/tbb/CMakeLists.txt b/SPHINXsys/src/shared/tbb/CMakeLists.txt new file mode 100644 index 0000000000..4f387fb5d1 --- /dev/null +++ b/SPHINXsys/src/shared/tbb/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) # main (top) cmake dir +include(Headersearch) + +file(GLOB BASE_DIR_HEADERS *.h) +DIR_INC_HEADER_NAMES("${BASE_DIR_HEADERS}" BASE_DIR_HEADER_NAMES) +#message("${BASE_DIR_HEADER_NAMES}") +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 2d_code/include) +INSTALL(FILES ${BASE_DIR_HEADER_NAMES} DESTINATION 3d_code/include) \ No newline at end of file diff --git a/gpuSPHINXsys/gpuSPHinxsys.cu b/gpuSPHINXsys/gpuSPHinxsys.cu deleted file mode 100644 index 2c7f14f0c4..0000000000 --- a/gpuSPHINXsys/gpuSPHinxsys.cu +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Massoud Rezavand 2019. - * Technical University of Munich - * gpuSPHinxsys - An SPH solver for CUDA enabled GPUs - * - * This is the main interface to communicate between - * Host and Device - */ - -#include"gpuSPHinxsys.cuh" -#include"System.h" -#include"ParticleData.cuh" -#include"ParticleGroup.cuh" -#include"CellList.cuh" -#include"timeStepping.cuh" -#include"dtSizeCalc.cuh" - - -using gpu::access; -using real = gpu::real; -using real3 = gpu::real3; -using real4 = gpu::real4; -using timeStepping = gpu::timeStepping; - - -gpuSPHinxsys::gpuSPHinxsys(Parameters par): - particle_size(par.particle_size), - box_size(par.box_size), - body_force(par.body_force), - probe1(par.probe1), probe2(par.probe2), - U_f(par.U_f), c_f(par.c_f), rho0_f(par.rho0_f), - c_s(par.c_s), rho0_s(par.rho0_s), rho0_g(par.rho0_g) -{ - std::cout << "\t*************************************************" << "\n"; - std::cout << "\t gpuSPHinXsys: An SPH solver on GPUs " << "\n"; - std::cout << "\t*************************************************" << "\n"; - // /*checkCudaErrors*/(cudaMalloc((void **)&devicePar->VelMax, sizeof(real))); -} - -gpuSPHinxsys::~gpuSPHinxsys() -{ - std::cout << "\t*************************************************" << "\n"; - std::cout << "\t gpuSPHinXsys call ended! " << "\n"; - std::cout << "\t*************************************************" << "\n"; - // if(devicePar->VelMax != NULL) cudaFree(devicePar->VelMax); -} - -struct GPU{ - std::shared_ptr pd; - std::shared_ptr sys; - std::shared_ptr nl; //neighborlist -}; - -//initialize a GPU system -GPU initializeGPU(size_t np){ - auto sys = std::make_shared(); - auto pd = std::make_shared(np, sys); - auto pg = std::make_shared(pd, sys, "All"); - auto nl = std::make_shared(pd, pg, sys); - return {pd, sys, nl}; -} - -//Copy particle position_type to device and allocate density and mass -void copyPositionsToDevice(const GPU & gpu_state, - const std::vector &fluidParticles, - const std::vector &wallParticles, - const std::vector &thirdBodyParticles, - float rho0_fluid, - float rho0_gas, - int DIM, - float dp){ - auto pos_type = gpu_state.pd->getPos(access::location::cpu, access::mode::write); - auto rho = gpu_state.pd->getRho(access::location::cpu, access::mode::write); - auto rho0 = gpu_state.pd->getRho0(access::location::cpu, access::mode::write); - auto mass = gpu_state.pd->getMass(access::location::cpu, access::mode::write); - auto vol = gpu_state.pd->getVol(access::location::cpu, access::mode::write); - size_t npFluid = fluidParticles.size()/3; - size_t npWall = wallParticles.size()/3; - size_t npThirdBody = thirdBodyParticles.size()/3; - - for(int i = 0; igetVel(access::location::cpu, access::mode::write); - auto p = gpu_state.pd->getPressure(access::location::cpu, access::mode::write); - std::fill(vel.begin(), vel.end(), real3()); - std::fill(p.begin(), p.end(), real()); -#ifdef _TRANSPORT_VELOCITY_ - auto vel_tv = gpu_state.pd->getVel_tv(access::location::cpu, access::mode::write); - auto F_Pb = gpu_state.pd->getF_Pb(access::location::cpu, access::mode::write); - std::fill(vel_tv.begin(), vel_tv.end(), real3()); - std::fill(F_Pb.begin(), F_Pb.end(), real3()); -#endif -} - -void gpuSPHinxsys::call_gpuSPHinxsys(std::vector &fluidParticles, - std::vector &wallParticles, - std::vector &thirdBodyParticles){ - - int npFluid = fluidParticles.size()/3; - int npWall = wallParticles.size()/3; - int npThirdBody = thirdBodyParticles.size()/3; - int np = npFluid + npWall + npThirdBody; - - printf("Copied to device: npFluid: %d npWall: %d npThirdBody: %d np: %d \n", npFluid, npWall, npThirdBody, np); - - //copy the box size - real3 boxSize = gpu::make_real3(std::get<0>(box_size), - std::get<1>(box_size), - std::get<2>(box_size)); - //copy the external body force - real3 bodyForceTmp = gpu::make_real3(std::get<0>(body_force), - std::get<1>(body_force), - std::get<2>(body_force)); - - int DIM = boxSize.z > real(0.0)?3:2; - - auto gpu_state = initializeGPU(np); - - copyPositionsToDevice(gpu_state, fluidParticles, wallParticles, - thirdBodyParticles, rho0_f, rho0_g, DIM, particle_size); - - //creat a pointer to the timeStepping module - timeStepping::Parameters parTS; - parTS.U_f = U_f; - parTS.box = gpu::Box(boxSize); - parTS.h = 1.3*particle_size; - parTS.c_f = c_f; - parTS.rho0_f = rho0_f; - parTS.bodyForce = bodyForceTmp; - parTS.probe1 = probe1; - parTS.probe2 = probe2; - auto timeStepping_ptr = std::make_shared(gpu_state.pd, gpu_state.nl, gpu_state.sys, parTS); - - initialize_properties(gpu_state); - - gpu_state.pd->sortParticles(); - - //the time integration functions to Run the simulation - timeStepping_ptr->velVerletIntg(); //velocity Verlet scheme -// timeStepping_ptr->dualCriteriaIntg(); //dual criteria scheme -// timeStepping_ptr->solidDynaIntg(); //integration scheme for solid dynamics - - gpu_state.sys->finish(); -} diff --git a/gpuSPHINXsys/gpuSPHinxsys.cuh b/gpuSPHINXsys/gpuSPHinxsys.cuh deleted file mode 100644 index 4756f1e946..0000000000 --- a/gpuSPHINXsys/gpuSPHinxsys.cuh +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Massoud Rezavand 2019. - * Technical University of Munich - * gpuSPHinxsys - An SPH solver for CUDA enabled GPUs - * - * This is the main interface to communicate between - * Host and Device - */ - -#ifndef GPUSPHINXSYS_CUH -#define GPUSPHINXSYS_CUH - -#include -#include -#include -#include - -class gpuSPHinxsys -{ -public: - - struct Parameters{ - float particle_size; - std::tuple box_size, body_force; - std::tuple probe1, probe2; - float U_f, c_f, c_s, rho0_f, rho0_g, rho0_s; - }; - - gpuSPHinxsys(Parameters par); - ~gpuSPHinxsys(); - - void call_gpuSPHinxsys(std::vector &fluidParticles, - std::vector &wallParticles, - std::vector &imprtdBodyParticles); - -private: - - float particle_size; - std::tuple box_size, body_force; - std::tuple probe1, probe2; - float U_f, c_f, c_s, rho0_f, rho0_g, rho0_s; - -}; - -#endif diff --git a/gpuSPHINXsys/src/Box.cuh b/gpuSPHINXsys/src/Box.cuh deleted file mode 100644 index 43ed0ae1d9..0000000000 --- a/gpuSPHINXsys/src/Box.cuh +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef BOX_CUH -#define BOX_CUH - -#include -#include"defines.h" -#include"vector.cuh" - -namespace gpu{ - struct Box{ - real3 boxSize, minusInvBoxSize; - - Box():Box(0){} - Box(real L):Box(make_real3(L)){} - Box(real2 L):Box(make_real3(L, 0)){} - Box(real3 L): boxSize(L), minusInvBoxSize(make_real3(real(-1.0)/L.x, real(-1.0)/L.y, real(-1.0)/L.z)){ - if(boxSize.x==real(0.0)) minusInvBoxSize.x = real(0.0); - if(boxSize.y==real(0.0)) minusInvBoxSize.y = real(0.0); - if(boxSize.z==real(0.0)) minusInvBoxSize.z = real(0.0); - } - //Sets the periodicity of each dimension of the box. - inline void setPeriodicity(bool x, bool y, bool z){ - if(!x) minusInvBoxSize.x = 0; - if(!y) minusInvBoxSize.y = 0; - if(!z) minusInvBoxSize.z = 0; - } - inline __host__ __device__ bool isPeriodicX() const{return minusInvBoxSize.x != 0;} - inline __host__ __device__ bool isPeriodicY() const{return minusInvBoxSize.y != 0;} - inline __host__ __device__ bool isPeriodicZ() const{return minusInvBoxSize.z != 0;} - - inline __host__ __device__ real3 apply_pbc(const real3 &r) const{ - //return r - floorf(r/L+real(0.5))*L; //MIC Algorithm - real3 offset = floorf(r*minusInvBoxSize + real(0.5)); //MIC Algorithm - if(!isPeriodicX()) offset.x = 0; - if(!isPeriodicY()) offset.y = 0; - if(!isPeriodicZ()) offset.z = 0; - return r + offset*boxSize; - } - template< class vecType> - inline __device__ __host__ bool isInside(const vecType &pos) const{ - real3 boxSizehalf = real(0.5)*boxSize; - if(pos.x <= -boxSizehalf.x || pos.x > boxSizehalf.x) return false; - if(pos.y <= -boxSizehalf.y || pos.y > boxSizehalf.y) return false; - if(pos.z <= -boxSizehalf.z || pos.z > boxSizehalf.z) return false; - return true; - } - inline __device__ __host__ real getVolume() const{ - if(boxSize.z != real(0.0)) - return boxSize.x*boxSize.y*boxSize.z; - else - return boxSize.x*boxSize.y; - } - - bool operator == (const Box &other) const { - return boxSize.x == other.boxSize.x and - boxSize.y == other.boxSize.y and - boxSize.z == other.boxSize.z and - isPeriodicX() == other.isPeriodicX() and - isPeriodicY() == other.isPeriodicY() and - isPeriodicZ() == other.isPeriodicZ(); - } - bool operator != (const Box &other) const{ - return !(this->operator==(other)); - } - }; - -} -#endif diff --git a/gpuSPHINXsys/src/CellList.cuh b/gpuSPHINXsys/src/CellList.cuh deleted file mode 100644 index f76bceb23b..0000000000 --- a/gpuSPHINXsys/src/CellList.cuh +++ /dev/null @@ -1,568 +0,0 @@ -/* -References: - -[1] http://developer.download.nvidia.com/assets/cuda/files/particles.pdf - - */ -#ifndef CELLLIST_CUH -#define CELLLIST_CUH - -#include"ParticleData.cuh" -#include"ParticleGroup.cuh" -#include"ParticleSorter.cuh" -#include"Box.cuh" -#include"Grid.cuh" -#include"System.h" -#include -#include -#include -#include - -namespace gpu{ - namespace CellList_ns{ - template - __global__ void fillCellList(InputIterator sortPos, - uint *cellStart, int *cellEnd, - uint currentValidCell, - int *errorFlag, - int N, Grid grid){ - uint id = blockIdx.x*blockDim.x + threadIdx.x; - if(id0){ /*Shared memory target VVV*/ - icell2 = grid.getCellIndex(grid.getCell(make_real3(sortPos[id-1]))); - } - else{ - icell2 = 0; - } - const int ncells = grid.getNumberCells(); - if(icell>=ncells or icell2>=ncells){ - errorFlag[0] = 1; - return; - } - if(icell != icell2 or id == 0){ - cellStart[icell] = id+currentValidCell; - if(id>0) - cellEnd[icell2] = id; - } - if(id == N-1) cellEnd[icell] = N; - } - } - - template - __global__ void fillNeighbourList(const real4* sortPos, - const int *groupIndex, - NeighbourContainer ni, - int *neighbourList, int*nNeighbours, - int maxNeighbours, - real cutOff2, - int N, Box box, - int* tooManyNeighboursFlag){ - int id = blockIdx.x*blockDim.x + threadIdx.x; - if(id>=N) return; - int nneigh = 0; - const int offset = id*maxNeighbours; -#if CUB_PTX_ARCH < 300 - constexpr auto cubModifier = cub::LOAD_DEFAULT; -#else - constexpr auto cubModifier = cub::LOAD_LDG; -#endif - const real3 pi = make_real3(cub::ThreadLoad(sortPos + id)); - ni.set(id); - auto it = ni.begin(); - while(it){ - auto n = *it++; - const int cur_j = n.getInternalIndex(); - const real3 pj = make_real3(cub::ThreadLoad(sortPos + cur_j)); - const real3 rij = box.apply_pbc(pj-pi); - if(dot(rij, rij) <= cutOff2){ - nneigh++; - if(nneigh>=maxNeighbours){ - atomicMax(tooManyNeighboursFlag, nneigh); - return; - } - neighbourList[offset + nneigh-1] = cur_j; - } - } - //Include self interactions - neighbourList[offset + nneigh] = id; - nNeighbours[id] = nneigh+1; - } - } - - class CellList{ - protected: - thrust::device_vector cellStart; - uint currentValidCell; - int currentValidCell_counter; - thrust::device_vector cellEnd; - - Grid grid; - - thrust::device_vector errorFlags; - - thrust::device_vector neighbourList, numberNeighbours; - thrust::device_vector sortPos; - int maxNeighboursPerParticle; - ParticleSorter ps; - - shared_ptr pd; - shared_ptr pg; - shared_ptr sys; - - bool force_next_update = true; - bool rebuildNlist; - - real3 currentCutOff; - - connection numParticlesChangedConnection, posWriteConnection; - cudaEvent_t event; - - - void handleNumParticlesChanged(int Nnew){ - sys->log("[CellList] Number particles changed signal handled."); - int numberParticles = pg->getNumberParticles(); - if(neighbourList.size()){ - neighbourList.resize(numberParticles*maxNeighboursPerParticle); - numberNeighbours.resize(numberParticles); - } - currentValidCell_counter = -1; - force_next_update = true; - } - - void handlePosWriteRequested(){ - sys->log("[CellList] Issuing a list update after positions were written to."); - force_next_update = true; - } - - struct NeighbourListOffsetFunctor{ - NeighbourListOffsetFunctor(int str, int* groupIndex): - stride(str), groupIndex(groupIndex){} - int stride; - int *groupIndex; - inline __host__ __device__ int operator()(const int &index) const{ - return groupIndex[index]*stride; - } - }; - - using CountingIterator = cub::CountingInputIterator; - using StrideIterator = cub::TransformInputIterator; - - public: - - CellList(shared_ptr pd, - shared_ptr sys): - CellList(pd, std::make_shared(pd, sys), sys){} - - CellList(shared_ptr pd, - shared_ptr pg, - shared_ptr sys): pd(pd), pg(pg), sys(sys), ps(sys), currentCutOff(real3()){ - sys->log("[CellList] Created"); - - maxNeighboursPerParticle = 32; - - pd->getNumParticlesChangedSignal()->connect([this](int Nnew){this->handleNumParticlesChanged(Nnew);}); - pd->getPosWriteRequestedSignal()->connect([this](){this->handlePosWriteRequested();}); - - CudaSafeCall(cudaEventCreateWithFlags(&event, cudaEventDisableTiming)); - - currentValidCell_counter = -1; - CudaCheckError(); - } - - ~CellList(){ - sys->log("[CellList] Destroyed"); - numParticlesChangedConnection.disconnect(); - posWriteConnection.disconnect(); - } - - struct NeighbourListData{ - int * neighbourList; - int *numberNeighbours; - StrideIterator particleStride = StrideIterator(CountingIterator(0), NeighbourListOffsetFunctor(0, nullptr)); - }; - - NeighbourListData getNeighbourList(cudaStream_t st = 0){ - if(currentCutOff.x != currentCutOff.y or - currentCutOff.x != currentCutOff.z or - currentCutOff.z != currentCutOff.y){ - sys->log("[CellList] Invalid cutoff in getNeighbourList: %f %f %f", - currentCutOff.x, currentCutOff.y, currentCutOff.z); - throw std::invalid_argument("Cannot use NeighbourList with a different cutOff in each direction"); - } - const int numberParticles = pg->getNumberParticles(); - if(rebuildNlist){ - rebuildNeighbourList(st); - } - else{ - auto pos = pd->getPos(access::location::gpu, access::mode::read); - auto posGroupIterator = pg->getPropertyInputIterator(pos.raw(), access::location::gpu); - ps.applyCurrentOrder(posGroupIterator, sortPos.begin(), numberParticles, st); - } - NeighbourListData nl; - nl.neighbourList = thrust::raw_pointer_cast(neighbourList.data()); - nl.numberNeighbours = thrust::raw_pointer_cast(numberNeighbours.data()); - nl.particleStride = StrideIterator(CountingIterator(0), - NeighbourListOffsetFunctor(maxNeighboursPerParticle, - ps.getSortedIndexArray(numberParticles))); - return nl; - } - - bool needsRebuild(Box box, real3 cutOff){ - if(force_next_update) return true; - if(cutOff.x != currentCutOff.x) return true; - if(cutOff.y != currentCutOff.y) return true; - if(cutOff.z != currentCutOff.z) return true; - if(box != grid.box) return true; - return false; - } - - void updateNeighbourList(Grid in_grid, real3 cutOff, cudaStream_t st = 0){ - sys->log("[CellList] Updating list"); - if(!isGridAndCutOffValid(in_grid, cutOff)) - throw std::runtime_error("CellList encountered an invalid grid and/or cutoff"); - if(needsRebuild(in_grid.box, cutOff) == false) - return; - else - currentCutOff = cutOff; - pd->hintSortByHash(in_grid.box, cutOff); - force_next_update = false; - this->grid = in_grid; - sys->log("[CellList] Using %d %d %d cells", grid.cellDim.x, grid.cellDim.y, grid.cellDim.z); - resizeCellListToCurrentGrid(); - updateCurrentValidCell(); - updateOrderAndStoreInSortPos(st); - fillCellList(st); - CudaCheckError(); - rebuildNlist = true; - } - - void updateNeighbourList(Box box, real cutOff, cudaStream_t st = 0){ - updateNeighbourList(box, make_real3(cutOff), st); - } - - void updateNeighbourList(Box box, real3 cutOff, cudaStream_t st = 0){ - Grid a_grid = Grid(box, cutOff); - int3 cellDim = a_grid.cellDim; - if(cellDim.x < 3) cellDim.x = 3; - if(cellDim.y < 3) cellDim.y = 3; - if(box.boxSize.z > real(0.0) && cellDim.z < 3) cellDim.z = 3; - a_grid = Grid(box, cellDim); - updateNeighbourList(a_grid, cutOff, st); - } - - //This accesor function is part of CellList only, not part of the NeighbourList interface - //They allow to obtain a reference to the cell list structures to use them outside - struct CellListData{ - //[all particles in cell 0, all particles in cell 1,..., all particles in cell ncells] - //cellStart[i] stores the index of the first particle in cell i (in internal index) - //cellEnd[i] stores the last particle in cell i (in internal index) - //So the number of particles in cell i is cellEnd[i]-cellStart[i] - const uint * cellStart; - const int * cellEnd; - const real4 *sortPos; //Particle positions in internal index - const int* groupIndex; //Transformation between internal indexes and group indexes - Grid grid; - uint VALID_CELL; - }; - CellListData getCellList(){ - this->updateNeighbourList(grid, currentCutOff); - CellListData cl; - try{ - cl.cellStart = thrust::raw_pointer_cast(cellStart.data()); - cl.cellEnd = thrust::raw_pointer_cast(cellEnd.data()); - cl.sortPos = thrust::raw_pointer_cast(sortPos.data()); - } - catch(thrust::system_error &e){ - sys->log("[CellList] Thrust could not access cellList arrays with error: %s", e.what()); - } - int numberParticles = pg->getNumberParticles(); - cl.groupIndex = ps.getSortedIndexArray(numberParticles); - cl.grid = grid; - cl.VALID_CELL = currentValidCell; - return cl; - } - - class NeighbourContainer; //forward declaration for befriending - private: - class NeighbourIterator; //forward declaration for befriending - - //Neighbour is a small accesor for NeighbourIterator - //Represents a particle, you can ask for its index and position - struct Neighbour{ - __device__ Neighbour(const Neighbour &other): - internal_i(other.internal_i){ - groupIndex = other.groupIndex; - sortPos = other.sortPos; - } - //Index in the internal sorted index of the cell list - __device__ int getInternalIndex(){return internal_i;} - //Index in the particle group - __device__ int getGroupIndex(){return groupIndex[internal_i];} - __device__ real4 getPos(){return cub::ThreadLoad(sortPos+internal_i);} - - private: - int internal_i; - const int* groupIndex; - const real4* sortPos; - friend class NeighbourIterator; - __device__ Neighbour(int i, const int* gi, const real4* sp): - internal_i(i), groupIndex(gi), sortPos(sp){} - }; - - //This forward iterator must be constructed by NeighbourContainer, - class NeighbourIterator: - public thrust::iterator_adaptor< - NeighbourIterator, - int, Neighbour, - thrust::any_system_tag, - thrust::forward_device_iterator_tag, - Neighbour, int - >{ - friend class thrust::iterator_core_access; - - int j; //Current neighbour index - CellListData nl; - - int ci; //Current cell - - int3 celli; //Cell of particle i - int lastParticle; //Index of last particle in current cell - - uint VALID_CELL; - //Take j to the start of the next cell and return true, if no more cells remain then return false - __device__ bool nextcell(){ - const bool is2D = nl.grid.cellDim.z<=1; - if(ci >= (is2D?9:27)) return false; - bool empty = true; - do{ - int3 cellj = celli; - cellj.x += ci%3-1; - cellj.y += (ci/3)%3-1; - cellj.z = is2D?0:(celli.z+ci/9-1); - cellj = nl.grid.pbc_cell(cellj); - const bool isPeriodicCellInNonPeriodicBox = (!nl.grid.box.isPeriodicX() and abs(cellj.x-celli.x)>1) or - (!nl.grid.box.isPeriodicY() and abs(cellj.y-celli.y)>1) or - (!nl.grid.box.isPeriodicZ() and abs(cellj.z-celli.z)>1); - if(!isPeriodicCellInNonPeriodicBox){ - const int icellj = nl.grid.getCellIndex(cellj); - const uint cs = nl.cellStart[icellj]; - empty = cs= (is2D?9:27)) return !empty; - }while(empty); - return true; - } - - //Take j to the next neighbour - __device__ void increment(){ - if(++j == lastParticle) j = nextcell()?j:-1; - } - - __device__ Neighbour dereference() const{ - return Neighbour(j, nl.groupIndex, nl.sortPos); - } - - public: - //j==-1 means there are no more neighbours and the iterator is invalidated - __device__ operator bool(){ return j!= -1;} - - private: - friend class NeighbourContainer; - __device__ NeighbourIterator(int i, CellListData nl, bool begin): - j(-2), nl(nl), ci(0), lastParticle(-1), VALID_CELL(nl.VALID_CELL){ - if(begin){ - celli = nl.grid.getCell(make_real3(cub::ThreadLoad(nl.sortPos+i))); - increment(); - } - else j = -1; - } - //enables searching the neighbors of a given point: probe - __device__ NeighbourIterator(real3 probe, CellListData nl, bool begin): - j(-2), nl(nl), ci(0), lastParticle(-1), VALID_CELL(nl.VALID_CELL){ - if(begin){ - celli = nl.grid.getCell(probe); - increment(); - } - else j = -1; - } - }; - - public: - //This is a pseudocontainer which only purpose is to provide begin() and end() NeighbourIterators for a certain particle - struct NeighbourContainer{ - int my_i = -1; - CellListData nl; - NeighbourContainer(CellListData nl): nl(nl){} - __device__ void set(int i){this->my_i = i;} - __device__ NeighbourIterator begin(){return NeighbourIterator(my_i, nl, true);} - //to search neighbors for a given spatial point, e.g. a porbe - __device__ NeighbourIterator begin(real3 probe){return NeighbourIterator(probe, nl, true);} - __device__ NeighbourIterator end(){ return NeighbourIterator(my_i, nl, false);} - }; - - NeighbourContainer getNeighbourContainer(){ - auto nl = getCellList(); - return NeighbourContainer(nl); - } - - const real4* getPositionIterator(){ - return thrust::raw_pointer_cast(sortPos.data()); - } - const int* getGroupIndexIterator(){ - auto nl = getCellList(); - return nl.groupIndex; - } - - private: - - bool isGridAndCutOffValid(Grid in_grid, real3 cutOff){ - if(in_grid.cellDim.x < 3 or - in_grid.cellDim.y < 3 or - (in_grid.cellDim.z < 3 and in_grid.box.boxSize.z != real(0.0))){ - sys->log("[CellList] I cannot work with less than 3 cells per dimension!"); - return false; - } - //In the case of 3 cells per direction all the particles are checked anyway - if(in_grid.cellDim.x != 3 or - in_grid.cellDim.y != 3 or - (in_grid.cellDim.z != 3 and in_grid.box.boxSize.z != real(0.0))){ - - if(in_grid.cellSize.x < cutOff.x or - in_grid.cellSize.y < cutOff.y or - (in_grid.cellSize.z < cutOff.z and in_grid.cellSize.z>1)){ - sys->log("[CellList] The cell size cannot be smaller than the cut off."); - return false; - } - } - return true; - } - - void tryToResizeCellListToCurrentGrid(){ - const int ncells = grid.getNumberCells(); - if(cellStart.size()!= ncells){ - currentValidCell_counter = -1; - cellStart.resize(ncells); - } - if(cellEnd.size()!= ncells) cellEnd.resize(ncells); - CudaCheckError(); - } - - void resizeCellListToCurrentGrid(){ - try{ - tryToResizeCellListToCurrentGrid(); - } - catch(...){ - sys->log("[CellList] Raised exception at cell list resize"); - throw; - } - } - - void updateCurrentValidCell(){ - const int numberParticles = pg->getNumberParticles(); - const bool isCounterUninitialized = (currentValidCell_counter < 0); - const ullint nextStepMaximumValue = ullint(numberParticles)*(currentValidCell_counter+2); - constexpr ullint maximumStorableValue = ullint(std::numeric_limits::max())-1ull; - const bool nextStepOverflows = (nextStepMaximumValue >= maximumStorableValue); - if(isCounterUninitialized or nextStepOverflows){ - currentValidCell = numberParticles; - currentValidCell_counter = 1; - const int ncells = grid.getNumberCells(); - const int Nthreads = 512; - const int Nblocks= ncells/Nthreads + ((ncells%Nthreads)?1:0); - auto it = thrust::make_counting_iterator(0); - fillWithGPU<<>>(thrust::raw_pointer_cast(cellStart.data()), - it, 0, ncells); - CudaCheckError(); - } - else{ - currentValidCell_counter++; - currentValidCell = uint(numberParticles)*currentValidCell_counter; - } - } - - void updateOrderAndStoreInSortPos(cudaStream_t st){ - const int numberParticles = pg->getNumberParticles(); - auto pos = pd->getPos(access::location::gpu, access::mode::read); - auto posGroupIterator = pg->getPropertyInputIterator(pos.begin(), access::location::gpu); - ps.updateOrderByCellHash(posGroupIterator, - numberParticles, - grid.box, grid.cellDim, st); - CudaCheckError(); - sortPos.resize(numberParticles); - ps.applyCurrentOrder(posGroupIterator, thrust::raw_pointer_cast(sortPos.data()), numberParticles, st); - CudaCheckError(); - } - - void fillCellList(cudaStream_t st){ - const int numberParticles = pg->getNumberParticles(); - const int Nthreads = 512; - int Nblocks = numberParticles/Nthreads + ((numberParticles%Nthreads)?1:0); - sys->log("[CellList] fill Cell List, currentValidCell: %d", currentValidCell); - int h_errorFlag = 0; - errorFlags.resize(1); - int *d_errorFlag = thrust::raw_pointer_cast(errorFlags.data()); - CudaSafeCall(cudaMemcpyAsync(d_errorFlag, &h_errorFlag, sizeof(int), cudaMemcpyHostToDevice, st)); - CellList_ns::fillCellList<<>>(thrust::raw_pointer_cast(sortPos.data()), - thrust::raw_pointer_cast(cellStart.data()), - thrust::raw_pointer_cast(cellEnd.data()), - currentValidCell, - d_errorFlag, - numberParticles, - grid); - CudaSafeCall(cudaMemcpyAsync(&h_errorFlag, d_errorFlag, sizeof(int), cudaMemcpyDeviceToHost, st)); - CudaSafeCall(cudaEventRecord(event, st)); - CudaSafeCall(cudaEventSynchronize(event)); - if(h_errorFlag > 0){ - sys->log("[CellList] NaN positions found during construction"); - throw std::overflow_error("CellList encountered NaN positions"); - } - CudaCheckError(); - } - - void rebuildNeighbourList(cudaStream_t st){ - const int numberParticles = pg->getNumberParticles(); - neighbourList.resize(numberParticles*maxNeighboursPerParticle); - numberNeighbours.resize(numberParticles); - rebuildNlist = false; - int Nthreads=128; - int Nblocks=numberParticles/Nthreads + ((numberParticles%Nthreads)?1:0); - int flag; - do{ - flag = 0; - auto neighbourList_ptr = thrust::raw_pointer_cast(neighbourList.data()); - auto numberNeighbours_ptr = thrust::raw_pointer_cast(numberNeighbours.data()); - errorFlags.resize(1); - int* d_tooManyNeighboursFlag = thrust::raw_pointer_cast(errorFlags.data()); - sys->log("[CellList] fill Neighbour List"); - CellList_ns::fillNeighbourList<<>>(thrust::raw_pointer_cast(sortPos.data()), - this->getGroupIndexIterator(), - this->getNeighbourContainer(), - neighbourList_ptr, numberNeighbours_ptr, - maxNeighboursPerParticle, - currentCutOff.x*currentCutOff.x, - numberParticles, grid.box, - d_tooManyNeighboursFlag); - CudaSafeCall(cudaMemcpyAsync(&flag, d_tooManyNeighboursFlag, sizeof(int), cudaMemcpyDeviceToHost, st)); - CudaSafeCall(cudaEventRecord(event, st)); - CudaSafeCall(cudaEventSynchronize(event)); - if(flag != 0){ - this->maxNeighboursPerParticle += 32; - sys->log("[CellList] Resizing list to %d neighbours per particle", - maxNeighboursPerParticle); - int zero = 0; - CudaSafeCall(cudaMemcpyAsync(d_tooManyNeighboursFlag, &zero, sizeof(int), cudaMemcpyHostToDevice, st)); - neighbourList.resize(numberParticles*maxNeighboursPerParticle); - } - }while(flag!=0); - } - - }; -} -#endif - - diff --git a/gpuSPHINXsys/src/GPUUtils.cuh b/gpuSPHINXsys/src/GPUUtils.cuh deleted file mode 100644 index ab53170e0b..0000000000 --- a/gpuSPHINXsys/src/GPUUtils.cuh +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef GPUUTILS_CUH -#define GPUUTILS_CUH - - -namespace gpu{ - - //Fill any iterator with the same value. It is much faster than cudaMemset - template - __global__ void fillWithGPU(OutputIterator array, T value, int N){ - int id = blockIdx.x*blockDim.x + threadIdx.x; - if(id>=N) return; - - array[id] = value; - } - - template - __global__ void fillWithGPU(OutputIterator array, Iterator indexIterator, T value, int N){ - int id = blockIdx.x*blockDim.x + threadIdx.x; - if(id>=N) return; - int i = indexIterator[id]; - array[i] = value; - } - - - template - __global__ void copyGPU(InputIterator d_in, OutputIterator d_out, int N){ - int id = blockIdx.x*blockDim.x + threadIdx.x; - if(id>=N) return; - - d_out[id] = d_in[id]; - } - - -} - - - -#endif diff --git a/gpuSPHINXsys/src/Grid.cuh b/gpuSPHINXsys/src/Grid.cuh deleted file mode 100644 index 91f2820a04..0000000000 --- a/gpuSPHINXsys/src/Grid.cuh +++ /dev/null @@ -1,167 +0,0 @@ -#ifndef GRID_CUH -#define GRID_CUH - -#include "Box.cuh" -#include "vector.cuh" -#include -namespace gpu{ - - struct Grid{ - int3 gridPos2CellIndex; - - int3 cellDim; - real3 cellSize; - real3 invCellSize; - Box box; - real cellVolume; - Grid(): Grid(Box(), make_int3(0,0,0)){} - - Grid(Box box, real3 minCellSize): - Grid(box, make_int3(box.boxSize/minCellSize)){} - Grid(Box box, real minCellSize): - Grid(box, make_real3(minCellSize)){} - - Grid(Box box, int3 in_cellDim): - box(box), - cellDim(in_cellDim){ - - if(cellDim.z == 0) cellDim.z = 1; - cellSize = box.boxSize/make_real3(cellDim); - invCellSize = 1.0/cellSize; - if(box.boxSize.z == real(0.0)) invCellSize.z = 0; - - gridPos2CellIndex = make_int3( 1, - cellDim.x, - cellDim.x*cellDim.y); - cellVolume = cellSize.x*cellSize.y; - if(cellDim.z > 1) cellVolume *= cellSize.z; - } - template - inline __host__ __device__ int3 getCell(const VecType &r) const{ - // return int( (p+0.5L)/cellSize ) - int3 cell = make_int3((box.apply_pbc(make_real3(r)) + real(0.5)*box.boxSize)*invCellSize); - //Anti-Traquinazo guard, you need to explicitly handle the case where a particle - // is exactly at the box limit, AKA -L/2. This is due to the precision loss when - // casting int from floats, which gives non-correct results very near the cell borders. - // This is completly neglegible in all cases, except with the cell 0, that goes to the cell - // cellDim, which is catastrophic. - //Doing the previous operation in double precision (by changing 0.5f to 0.5) also works, but it is a bit of a hack and the performance appears to be the same as this. - //TODO: Maybe this can be skipped if the code is in double precision mode - if(cell.x==cellDim.x) cell.x = 0; - if(cell.y==cellDim.y) cell.y = 0; - if(cell.z==cellDim.z) cell.z = 0; - return cell; - } - - inline __host__ __device__ int getCellIndex(const int3 &cell) const{ - return dot(cell, gridPos2CellIndex); - } - - inline __host__ __device__ int getCellIndex(const int2 &cell) const{ - return dot(cell, make_int2(gridPos2CellIndex)); - } - - inline __host__ __device__ int3 pbc_cell(const int3 &cell) const{ - int3 cellPBC; - cellPBC.x = pbc_cell_coord<0>(cell.x); - cellPBC.y = pbc_cell_coord<1>(cell.y); - cellPBC.z = pbc_cell_coord<2>(cell.z); - return cellPBC; - } - - template - inline __host__ __device__ int pbc_cell_coord(int cell) const{ - int ncells = 0; - if(coordinate == 0){ - ncells = cellDim.x; - } - if(coordinate == 1){ - ncells = cellDim.y; - } - - if(coordinate == 2){ - ncells = cellDim.z; - } - - if(cell <= -1) cell += ncells; - else if(cell >= ncells) cell -= ncells; - return cell; - } - - inline __host__ __device__ int getNumberCells() const{ return cellDim.x*cellDim.y*cellDim.z;} - - inline __host__ __device__ real getCellVolume(int3 cell) const{ return getCellVolume();} - - inline __host__ __device__ real getCellVolume() const{ return cellVolume;} - - inline __host__ __device__ real3 getCellSize(int3 cell) const{return getCellSize();} - - inline __host__ __device__ real3 getCellSize() const{return cellSize;} - - inline __host__ __device__ real3 distanceToCellCenter(real3 pos, int3 cell) const{ - return box.apply_pbc(pos + box.boxSize*real(0.5) - cellSize*(make_real3(cell)+real(0.5))); - } - - inline __host__ __device__ real3 distanceToCellUpperLeftCorner(real3 pos, int3 cell) const{ - return box.apply_pbc(pos + box.boxSize*real(0.5) - cellSize*make_real3(cell)); - } - - }; - - //Looks for the closest (equal or greater) number of nodes of the form 2^a*3^b*5^c*7^d*11^e - int3 nextFFTWiseSize3D(int3 size){ - int* cdim = &size.x; - - int max_dim = std::max({size.x, size.y, size.z}); - - int n= 14; - int n5 = 6; //number higher than this are not reasonable... - int n7 = 5; - int n11 = 4; - auto powint = [](uint64_t base, uint64_t exp){if(exp==0) return uint64_t(1); uint64_t res = base; fori(0,exp-1) res*=base; return res;}; - - std::vector tmp(n*n*n5*n7*n11, 0); - do{ - tmp.resize(n*n*n5*n7*n11, 0); - fori(0,n)forj(0,n) - for(int k=0; k4 or k7>5 or k>6) continue; - - uint64_t id = i+n*j+n*n*k+n*n*n5*k7+n*n*n5*n7*k11; - tmp[id] = 0; - //Current fft wise size - uint64_t number = uint64_t(powint(2,i))*powint(3,j)*powint(5,k)*powint(7, k7); - //This is to prevent overflow - if(!(i==n-1 and j==n-1 and k==n5-1 and k7==n7-1 and k110 && (i==0))) continue; - tmp[id] = number; - } - n++; - /*Sort this array in ascending order*/ - std::sort(tmp.begin(), tmp.end()); - }while(tmp.back()=powint(2,31)) set = -1; - cdim[j] = set; - break; - } - } - return size; - } -} - -#endif diff --git a/gpuSPHINXsys/src/Kernel.cuh b/gpuSPHINXsys/src/Kernel.cuh deleted file mode 100644 index 1ab41491cf..0000000000 --- a/gpuSPHINXsys/src/Kernel.cuh +++ /dev/null @@ -1,121 +0,0 @@ -#ifndef KERNEL_CUH -#define KERNEL_CUH - -#include"defines.h" - -namespace gpu{ - namespace KernelFunction{ - struct M4CubicSpline{ - inline __device__ __host__ real operator()(real3 r12, real h, real boxZ){ - real r2 = dot(r12, r12); - real r = sqrtf(r2); - - real q = abs(r)/h; - - if(q >= real(2.0)) return real(0.0); - - real twomq = real(2.0)-q; - - real W = twomq*twomq*twomq; - if(q <= real(1.0)){ - real onemq = real(1.0) - q; - real onemq3 = onemq*onemq*onemq; - W -= real(4.0)*onemq3; - } - - W *= real(1.0)/(h*h*h*real(4.0)*real(M_PI)); - return W; - } - inline __device__ __host__ real3 gradient(real3 r12, real h, real boxZ){ - - real r2 = dot(r12, r12); - real r = sqrtf(r2); - - real invh = real(1.0)/h; - - real q = r*invh; - if(q >= real(2.0) ) return make_real3(0.0); - - real invh3 = invh*invh*invh; - - real3 gradW = -invh3*invh3*real(3.0)*r12/(real(4.0)*real(M_PI)); - - - if(q <= real(1.0)){ - gradW *= (real(-4.0)*h + real(3.0)*r); - } - else if(q <= real(2.0) ){ - real f = (real(2.0)*h - r); - gradW *= f*f; - - } - return gradW; - } - - static inline __device__ __host__ real getCutOff(real h){ - return real(2.0)*h; - } - - }; - - struct Wendland_C4{ - inline __device__ __host__ real operator()(real3 r12, real h, real boxZ){ - real r2 = dot(r12, r12); - real r = sqrtf(r2); - - real q = abs(r)/h; - real C = real(); - real val = real(); - - - if(boxZ == real(0.0)) - C = real(1.0)/real(M_PI)/(h*h)*real(7./4.);//for 2D - else - C = real(1.0)/real(M_PI)/(h*h*h)*real(21./16.);//for 3D - - // (1-q/2)^4 - real factor = (real(1.0)-real(0.5)*q)*(real(1.0)-real(0.5)*q) - *(real(1.0)-real(0.5)*q)*(real(1.0)-real(0.5)*q); - - if(q < real(2.0)) - val = C*factor*(real(1.0) + real(2.0)*q); - else - val = real(0.0); - - return val; - } - inline __device__ __host__ real gradient(real3 r12, real h, real boxZ){ - - real r2 = dot(r12, r12); - real r = sqrtf(r2); - - real q = abs(r)/h; - real C = real(); - real val = real(); - - if(boxZ == real(0.0)) - C = real(1.0)/real(M_PI)/(h*h)*real(7./4.);//for 2D - else - C = real(1.0)/real(M_PI)/(h*h*h)*real(21./16.);//for 3D - - // (1-q/2)^3 - real factor = (real(1.0)-real(0.5)*q)*(real(1.0)-real(0.5)*q) - *(real(1.0)-real(0.5)*q); - - if(q < real(2.0)) - val = -real(5.0)*C*factor*q/h/r; - else - val = real(0.0); - - return val; - } - - static inline __device__ __host__ real getCutOff(real h){ - return real(2.0)*h; - } - - }; - } -} - -#endif diff --git a/gpuSPHINXsys/src/Log.h b/gpuSPHINXsys/src/Log.h deleted file mode 100644 index 957f01ef18..0000000000 --- a/gpuSPHINXsys/src/Log.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef LOG_H -#define LOG_H -#include -#include -#include -#include -namespace gpu{ - namespace Logging{ - using ElementType = std::tuple; - enum LogLevel{CRITICAL=0, ERROR, EXCEPTION, WARNING, MESSAGE, STDERR, STDOUT, - DEBUG, DEBUG1, DEBUG2, DEBUG3, DEBUG4, DEBUG5, DEBUG6, DEBUG7}; - -#ifdef MAXLOGLEVEL - constexpr int maxLogLevel = MAXLOGLEVEL; -#else - constexpr int maxLogLevel = 6; -#endif - - ElementType getLogLevelInfo(int level){ - static const std::map printMap{ - {CRITICAL , std::make_tuple(stderr, "\e[101m[CRITICAL] ", "\e[0m\n")}, - {ERROR , std::make_tuple(stderr, "\e[91m[ERROR] \e[0m", "\n")}, - {EXCEPTION , std::make_tuple(stderr, "\e[1m\e[91m[EXCEPTION] \e[0m", "\n")}, - {WARNING , std::make_tuple(stderr, "\e[93m[WARNING] \e[0m", "\n")}, - {MESSAGE , std::make_tuple(stderr, "\e[92m[MESSAGE] \e[0m", "\n")}, - {STDERR , std::make_tuple(stderr, " ", "\n")}, - {STDOUT , std::make_tuple(stdout, " ", "\n")}, - {DEBUG , std::make_tuple(stderr, "\e[96m[ DEBUG ] \e[0m", "\n")}, - {DEBUG1 , std::make_tuple(stderr, "\e[96m[ DEBUG ] \e[0m", "\n")}, - {DEBUG2 , std::make_tuple(stderr, "\e[96m[ DEBUG ] \e[0m", "\n")}, - {DEBUG3 , std::make_tuple(stderr, "\e[96m[ DEBUG ] \e[0m", "\n")}, - {DEBUG4 , std::make_tuple(stderr, "\e[96m[ DEBUG ] \e[0m", "\n")}, - {DEBUG5 , std::make_tuple(stderr, "\e[96m[ DEBUG ] \e[0m", "\n")}, - {DEBUG6 , std::make_tuple(stderr, "\e[96m[ DEBUG ] \e[0m", "\n")}, - {DEBUG7 , std::make_tuple(stderr, "\e[96m[ DEBUG ] \e[0m", "\n")} - }; - return printMap.at(level); - } - - template - static inline void log(char const *fmt, ...){ - if(level<=maxLogLevel){ - const auto currentLevelInfo = getLogLevelInfo(level); - auto stream = std::get<0>(currentLevelInfo); - auto prefix = std::get<1>(currentLevelInfo); - auto suffix = std::get<2>(currentLevelInfo); - va_list args; - va_start(args, fmt); - fprintf(stream, "%s", prefix); - vfprintf(stream, fmt, args); - fprintf(stream, "%s", suffix); - va_end(args); - } - } - - template - static inline void log(const std::string &msg){ - log("%s", msg.c_str()); - } - - } -} -#endif diff --git a/gpuSPHINXsys/src/ParticleData.cuh b/gpuSPHINXsys/src/ParticleData.cuh deleted file mode 100644 index a3d33eb02b..0000000000 --- a/gpuSPHINXsys/src/ParticleData.cuh +++ /dev/null @@ -1,318 +0,0 @@ -#ifndef PARTICLEDATA_CUH -#define PARTICLEDATA_CUH -#include"System.h" - -#include"Property.cuh" -#include"ParticleSorter.cuh" -#include"vector.cuh" - -#include -#include -#include -#include -#include -#include -#include - -//List here all the properties with this syntax: -/* ((PropertyName, propertyName, TYPE)) \ */ -//The preprocessor ensures that they are included wherever is needed -#define ALL_PROPERTIES_LIST ((Pos, pos, real4)) \ - ((Id, id, int)) \ - ((Mass, mass, real)) \ - ((Vol, vol, real)) \ - ((Force, force, real4)) \ - ((Energy, energy, real)) \ - ((Vel, vel, real3)) \ - ((Vel_tv, vel_tv, real3)) \ - ((F_Pb, f_Pb, real3)) \ - ((Rho, rho, real)) \ - ((Rho0, rho0, real)) \ - ((Drho, drho, real)) \ - ((Sigma0, sigma0, real)) \ - ((Pressure, pressure, real)) - -namespace gpu{ - - template - using signal = typename nod::unsafe_signal; - - using connection = nod::connection; - - //Get the Name (first letter capital) from a tuple in the property list -#define PROPNAME_CAPS(tuple) BOOST_PP_TUPLE_ELEM(3, 0 ,tuple) - //Get the name (no capital) from a tuple in the property list -#define PROPNAME(tuple) BOOST_PP_TUPLE_ELEM(3, 1 ,tuple) - //Get the type from a tuple in the property list -#define PROPTYPE(tuple) BOOST_PP_TUPLE_ELEM(3, 2 ,tuple) - -//This macro iterates through all properties applying some macro -#define PROPERTY_LOOP(macro) BOOST_PP_SEQ_FOR_EACH(macro, _, ALL_PROPERTIES_LIST) - - - - class ParticleData{ - public: - //Hints to ParticleData about how to perform different task. Mainly how to sort the particles. - struct Hints{ - bool orderByHash = false; - Box hash_box = Box(make_real3(128)); - real3 hash_cutOff = make_real3(10.0); - bool orderByType = false; - - }; - - private: - shared_ptr sys; -#define DECLARE_PROPERTIES_T(type, name) Property name; -#define DECLARE_PROPERTIES(r,data, tuple) DECLARE_PROPERTIES_T(PROPTYPE(tuple), PROPNAME(tuple)) - - //Declare all property containers - PROPERTY_LOOP(DECLARE_PROPERTIES) - - int numberParticles; - shared_ptr> reorderSignal = std::make_shared>(); - shared_ptr> numParticlesChangedSignal = std::make_shared>(); - -//Declare write access signals for all properties -#define DECLARE_SIGNAL_PROPERTIES_T(type, name) shared_ptr> BOOST_PP_CAT(name,WriteRequestedSignal = std::make_shared>();) -#define DECLARE_SIGNAL_PROPERTIES(r,data, tuple) DECLARE_SIGNAL_PROPERTIES_T(PROPTYPE(tuple), PROPNAME(tuple)) - //Declare all property write signals - PROPERTY_LOOP(DECLARE_SIGNAL_PROPERTIES) - - - - std::shared_ptr particle_sorter; - thrust::host_vector originalOrderIndexCPU; - bool originalOrderIndexCPUNeedsUpdate; - Hints hints; - - public: - ParticleData() = delete; - ParticleData(int numberParticles, shared_ptr sys); - ~ParticleData(){ - sys->log("[ParticleData] Destroyed"); - } - - - //Generate getters for all properties except ID -#define GET_PROPERTY_T(Name,name) GET_PROPERTY_R(Name,name) -#define GET_PROPERTY_R(Name, name) \ - inline auto get ## Name(access::location dev, access::mode mode) -> decltype(name.data(dev,mode)){ \ - if(!name.isAllocated()) name.resize(numberParticles); \ - if(!name.isAllocated() or mode==access::mode::write or mode==access::mode::readwrite){ \ - (*name ## WriteRequestedSignal)(); \ - } \ - return name.data(dev,mode); \ - } \ - -#define GET_PROPERTY(r, data, tuple) GET_PROPERTY_T(PROPNAME_CAPS(tuple), PROPNAME(tuple)) - - //Define getProperty() functions for all properties in list - PROPERTY_LOOP(GET_PROPERTY) - - //Generate getters for all properties except ID -#define GET_PROPERTY_IF_ALLOC_T(Name,name) GET_PROPERTY_IF_ALLOC_R(Name,name) -#define GET_PROPERTY_IF_ALLOC_R(Name, name) \ - inline auto get ## Name ## IfAllocated(access::location dev, access::mode mode) -> decltype(name.data(dev,mode)){ \ - if(!name.isAllocated()){ \ - decltype(name.data(dev,mode)) tmp; \ - return tmp; \ - } \ - return this->get ## Name(dev,mode); \ - } \ - -#define GET_PROPERTY_IF_ALLOC(r, data, tuple) GET_PROPERTY_IF_ALLOC_T(PROPNAME_CAPS(tuple), PROPNAME(tuple)) - - //Define getProperty() functions for all properties in list - PROPERTY_LOOP(GET_PROPERTY_IF_ALLOC) - - //Generate isPropAllocated for all properties -#define IS_ALLOCATED_T(Name, name) IS_ALLOCATED_R(Name, name) -#define IS_ALLOCATED_R(Name, name) \ - inline bool is##Name##Allocated(){return name.isAllocated();} \ - -#define IS_ALLOCATED(r, data, tuple) IS_ALLOCATED_T(PROPNAME_CAPS(tuple), PROPNAME(tuple)) - - PROPERTY_LOOP(IS_ALLOCATED) - - void sortParticles(); - - const int * getIdOrderedIndices(access::location dev){ - sys->log("[ParticleData] Id order requested for %d (0=cpu, 1=gpu)", dev); - auto id = getId(access::location::gpu, access::mode::read); - int *sortedIndex = particle_sorter->getIndexArrayById(id.raw(), numberParticles); - sys->log("[ParticleData] Id reorder completed."); - if(dev == access::location::gpu){ - return sortedIndex; - } - else{ - if(originalOrderIndexCPUNeedsUpdate){ - sys->log("[ParticleData] Updating CPU original order array"); - originalOrderIndexCPU.resize(numberParticles); - int * sortedIndexCPU = thrust::raw_pointer_cast(originalOrderIndexCPU.data()); - CudaSafeCall(cudaMemcpy(sortedIndexCPU, - sortedIndex, - numberParticles*sizeof(int), - cudaMemcpyDeviceToHost)); - originalOrderIndexCPUNeedsUpdate = false; - return sortedIndexCPU; - } - else{ - return thrust::raw_pointer_cast(originalOrderIndexCPU.data()); - } - } - } - - template - void applyCurrentOrder(InputIterator in, OutputIterator out, int numElements){ - particle_sorter->applyCurrentOrder(in, out, numElements); - } - - const int * getCurrentOrderIndexArray(){ - return particle_sorter->getSortedIndexArray(numberParticles); - } - - void changeNumParticles(int Nnew); - - int getNumParticles(){ return this->numberParticles;} - - shared_ptr> getReorderSignal(){ - sys->log("[ParticleData] Reorder signal requested"); - return this->reorderSignal; - } - -#define GET_PROPERTY_SIGNAL_T(Name,name) GET_PROPERTY_SIGNAL_R(Name,name) -#define GET_PROPERTY_SIGNAL_R(Name, name) \ - inline shared_ptr> get ## Name ## WriteRequestedSignal(){ \ - return this->name ## WriteRequestedSignal; \ - } -#define GET_PROPERTY_SIGNAL(r, data, tuple) GET_PROPERTY_SIGNAL_T(PROPNAME_CAPS(tuple), PROPNAME(tuple)) - PROPERTY_LOOP(GET_PROPERTY_SIGNAL) - - void emitReorder(){ - sys->log("[ParticleData] Emitting reorder signal..."); - (*this->reorderSignal)(); - } - - shared_ptr> getNumParticlesChangedSignal(){ - return this->numParticlesChangedSignal; - } - - - void hintSortByHash(Box hash_box, real3 hash_cutOff){ - hints.orderByHash = true; - hints.hash_box = hash_box; - hints.hash_cutOff = hash_cutOff; - - } - - private: - - void emitNumParticlesChanged(int Nnew){ - (*numParticlesChangedSignal)(Nnew); - } - - }; - - -#define INIT_PROPERTIES_T(NAME, name) , name(BOOST_PP_STRINGIZE(NAME), sys) -#define INIT_PROPERTIES(r,data, tuple) INIT_PROPERTIES_T(PROPNAME_CAPS(tuple), PROPNAME(tuple)) - - ParticleData::ParticleData(int numberParticles, shared_ptr sys): - numberParticles(numberParticles), - originalOrderIndexCPUNeedsUpdate(true), - sys(sys) - PROPERTY_LOOP(INIT_PROPERTIES) - { - sys->log("[ParticleData] Created with %d particles.", numberParticles); - - id.resize(numberParticles); - CudaCheckError(); - - auto id_prop = id.data(access::location::gpu, access::mode::write); - - cub::CountingInputIterator ci(0); - thrust::copy(thrust::cuda::par, - ci, ci + numberParticles, - id_prop.begin()); - - particle_sorter = std::make_shared(sys); - } - - //Sort the particles to improve a certain kind of access pattern. - void ParticleData::sortParticles(){ - sys->log("[ParticleData] Sorting particles..."); - - { - auto posPtr = pos.data(access::gpu, access::read); - if(hints.orderByHash || !hints.orderByType){ - int3 cellDim = make_int3(hints.hash_box.boxSize/hints.hash_cutOff); - particle_sorter->updateOrderByCellHash(posPtr.raw(), numberParticles, hints.hash_box, cellDim); - } - - } - //This macro reorders to the newest order a property given its name -#define APPLY_CURRENT_ORDER(r, data, tuple) APPLY_CURRENT_ORDER_R(PROPNAME(tuple)) -#define APPLY_CURRENT_ORDER_R(name) { \ - if(name.isAllocated()){ \ - auto devicePtr = name.data(access::gpu, access::write); \ - auto device_altPtr = name.getAltGPUBuffer(); \ - particle_sorter->applyCurrentOrder(devicePtr.raw(), device_altPtr, numberParticles); \ - name.swapInternalBuffers(); \ - } \ - } - //Apply current order to all allocated properties. See APPLY_CURRENT_ORDER macro - PROPERTY_LOOP(APPLY_CURRENT_ORDER) - - originalOrderIndexCPUNeedsUpdate = true; - this->emitReorder(); - } - - - - void ParticleData::changeNumParticles(int Nnew){ - sys->log("[ParticleData] CHANGE PARTICLES FUNCTIONALITY NOT IMPLEMENTED YET!!!"); - sys->log("[ParticleData] Adding/Removing particles..."); - this->numberParticles = Nnew; - pos.resize(Nnew); -#define RESIZE_PROPERTY_R(name) {if(this->name.isAllocated()){this->name.resize(this->numberParticles);}} -#define RESIZE_PROPERTY(r, data, tuple) RESIZE_PROPERTY_R(PROPNAME(tuple)) - - PROPERTY_LOOP(RESIZE_PROPERTY) - - originalOrderIndexCPUNeedsUpdate = true; - this->emitNumParticlesChanged(Nnew); - } -} - -#undef ALL_PROPERTIES_LIST -#undef PROPNAME_CAPS -#undef PROPNAME -#undef PROPTYPE -#undef PROPERTY_LOOP -#undef DECLARE_PROPERTIES_T -#undef DECLARE_PROPERTIES -#undef DECLARE_SIGNAL_PROPERTIES_T -#undef DECLARE_SIGNAL_PROPERTIES -#undef GET_PROPERTY_T -#undef GET_PROPERTY_R -#undef GET_PROPERTY -#undef GET_PROPERTY_SIGNAL_T -#undef GET_PROPERTY_SIGNAL_R -#undef GET_PROPERTY_SIGNAL -#undef IS_ALLOCATED_T -#undef IS_ALLOCATED_R -#undef IS_ALLOCATED -#undef GET_PROPERTY_IF_ALLOC -#undef GET_PROPERTY_IF_ALLOC_T -#undef GET_PROPERTY_IF_ALLOC_R -#undef APPLY_CURRENT_ORDER -#undef APPLY_CURRENT_ORDER_R -#undef RESIZE_PROPERTY_R -#undef RESIZE_PROPERTY - - - - -#endif diff --git a/gpuSPHINXsys/src/ParticleGroup.cuh b/gpuSPHINXsys/src/ParticleGroup.cuh deleted file mode 100644 index b383704a6f..0000000000 --- a/gpuSPHINXsys/src/ParticleGroup.cuh +++ /dev/null @@ -1,471 +0,0 @@ -#ifndef PARTICLEGROUP_CUH -#define PARTICLEGROUP_CUH - -#include"System.h" -#include"ParticleData.cuh" -#include -#include -#include - -namespace gpu{ - /*Small structs that encode different ways of selecting a certain set of particles, - i.e by type, spatial location, ID... - A particle selector must have an isSelected method that takes a particle index (not ID) and - a ParticleData reference. - It will be called for all particles except when not needed (i.e with All).*/ - namespace particle_selector{ - //Select all the particles - class All{ - public: - All(){} - static constexpr bool isSelected(int particleIndex, shared_ptr &pd){ - return true; - } - }; - - class None{ - public: - None(){} - static constexpr bool isSelected(int particleIndex, shared_ptr &pd){ - return false; - } - }; - - //Select particles with ID in a certain range - class IDRange{ - int firstID, lastID; - public: - IDRange(int first, int last): - firstID(first), - lastID(last){ - } - - bool isSelected(int particleIndex, shared_ptr &pd){ - int particleID = (pd->getId(access::cpu, access::read).raw())[particleIndex]; - return particleID>=firstID && particleID<=lastID; - } - - }; - - //Select particles inside a certain rectangular region of the simulation box. - class Domain{ - Box domain, simulationBox; - real3 origin; - public: - Domain(real3 origin, Box domain, Box simulationBox): - origin(origin), - domain(domain), - simulationBox(simulationBox){ - - } - bool isSelected(int particleIndex, shared_ptr &pd){ - real3 pos = make_real3(pd->getPos(access::cpu, access::read).raw()[particleIndex]); - pos = simulationBox.apply_pbc(pos); - pos += origin; - return domain.isInside(pos); - } - }; - - //Select particles by type (pos.w) - class Type{ - std::vector typesToSelect; - public: - Type(int type): typesToSelect({type}){ - } - - Type(std::vector typesToSelect): typesToSelect(typesToSelect){ - } - bool isSelected(int particleIndex, shared_ptr &pd){ - int type_i = int(pd->getPos(access::cpu, access::read).raw()[particleIndex].w); - for(auto type: typesToSelect){ - if(type_i==type) return true; - } - return false; - } - - - - - }; - }; - - namespace ParticleGroup_ns{ - //Updates the indices of the particles in a group using pd->getIdOrderedIndices() - __global__ void updateGroupIndices(//An array that stores the indices of the particles in the group per id. - const int * __restrict__ id2index, - //Out: the current ParticleData indices of the particles in the group - int * __restrict__ particlesIndices, - //In: Ids of the particle sin the group - const int * __restrict__ particlesIds, - int numberParticles - ){ - int tid = blockIdx.x*blockDim.x + threadIdx.x; - if(tid >= numberParticles) return; - int id = particlesIds[tid]; - int index = id2index[id]; - particlesIndices[tid] = index; - } - - } - // Keeps track of a certain subset of particles in a ParticleData entity - // You can ask ParticleGroup to return you: - // -The particle IDs of its members. - // -The current indices of its members in the ParticleData arrays. - - // You can ask for the indices as a raw memory pointer or as a custom iterator. - // Asking for the raw memory is a risky bussiness, as this array may not even exists (i.e if all particles in the system are in the group, it might decide to not create it, as it would be unnecessary). In this case, you will get a nullptr. - - class ParticleGroup{ - shared_ptr pd; - shared_ptr sys; - cudaStream_t st = 0; - //A list of the particle indices and ids of the group (updated to current order) - thrust::device_vector myParticlesIndicesGPU, myParticlesIdsGPU; - thrust::host_vector myParticlesIndicesCPU; - - bool updateHostVector = true; - bool needsIndexListUpdate = false; - - //number of particles in group and in all system (pd) - int numberParticles, totalParticles; - - bool allParticlesInGroup = false; - - std::string name; - - connection reorderConnection; - - public: - /*Defaults to all particles in group*/ - ParticleGroup(shared_ptr pd, shared_ptr sys, std::string name = std::string("noName")); - - /*Create the group from a selector*/ - template - ParticleGroup(ParticleSelector selector, - shared_ptr pd, shared_ptr sys, std::string name = std::string("noName")); - - /*Create the group from a list of particle IDs*/ - template - ParticleGroup(InputIterator begin, InputIterator end, - shared_ptr pd, shared_ptr sys, - std::string name = std::string("noName")); - - ~ParticleGroup(){ - sys->log("[ParticleGroup] Group %s destroyed", name.c_str()); - CudaCheckError(); - reorderConnection.disconnect(); - if(st) CudaSafeCall(cudaStreamDestroy(st)); - } - - //Remove all particles from the group - void clear(){ - this->numberParticles = 0; - } - //Add particles to the group via an array with ids - void addParticlesById(access::location loc, const int *ids, int N); - //Add particles to the group via an array with the current indices of the particles in pd (faster) - void addParticlesByCurrentIndex(access::location loc, const int *indices, int N); - //Update index list if needed - void computeIndexList(bool forceUpdate = false); - - void handleReorder(){ - sys->log("[ParticleGroup] Handling reorder signal in group %s", this->name.c_str()); - if(!allParticlesInGroup && numberParticles > 0){ - needsIndexListUpdate = true; - } - } - - //Access the index array only if it is not a nullptr (AKA if the group does not contain all particles) - struct IndexAccess{ - IndexAccess(const int * indices):indices(indices){} - inline __host__ __device__ int operator()(const int &i) const{ - if(!indices) return i; - else return indices[i]; - } - private: - const int * indices; - }; - - //Transform sequential indexing to indices of particle sin group - using IndexIterator = cub::TransformInputIterator>; - - static IndexIterator make_index_iterator(const int *indices){ - return IndexIterator(cub::CountingInputIterator(0), IndexAccess(indices)); - } - - //Get a raw memory pointer to the index list if it exists - inline const int * getIndicesRawPtr(access::location loc){ - if(this->allParticlesInGroup || numberParticles == 0 ) return nullptr; - this->computeIndexList(); - int *ptr; - switch(loc){ - case access::location::cpu: - if(updateHostVector){ - myParticlesIndicesCPU = myParticlesIndicesGPU; - updateHostVector = false; - } - ptr = thrust::raw_pointer_cast(myParticlesIndicesCPU.data()); - break; - case access::location::gpu: - ptr = thrust::raw_pointer_cast(myParticlesIndicesGPU.data()); - break; - default: - ptr = nullptr; - } - return ptr; - } - - //Get an iterator with the indices of particles in this group - inline IndexIterator getIndexIterator(access::location loc){ - auto ptr = getIndicesRawPtr(loc); - return make_index_iterator(ptr); - } - - //Simply reads an iterator, optionally a cub cache mode can be selected - template - struct TransformIndex{ - TransformIndex(const Iterator &it):it(it){} - using value_type = typename std::iterator_traits::value_type; - inline __host__ __device__ value_type operator()(const int &i) const{ - return it[i]; - } - private: - cub::CacheModifiedInputIterator it; - }; - - //Reads an iterator transforming sequential indexing to indices of the particles in the group - template - using accessIterator = cub::TransformInputIterator::value_type, - TransformIndex, - IndexIterator>; - private: - template - accessIterator make_access_iterator(const Iterator &it, access::location loc){ - return accessIterator(this->getIndexIterator(loc), - TransformIndex(it)); - } - - public: - - //Returns an iterator that will have size pg->getNumberParticles() and will iterate over the - // particles in the group. - //For example, If a group contains only the particle with id=10, passing pd->getPos(...).raw() to this function - // will return an iterator so that iterator[0] = pos[10]; and it will take into account any possible reordering of the pos array. - template - accessIterator getPropertyInputIterator(const Iterator & property, - access::location loc){ - return this->make_access_iterator(property, loc); - } - - int getNumberParticles(){ - return this->numberParticles; - } - - std::string getName(){ return this->name;} - }; - - - - template - ParticleGroup::ParticleGroup(ParticleSelector selector, - shared_ptr pd, shared_ptr sys, std::string name): - pd(pd), sys(sys), name(name){ - sys->log("[ParticleGroup] Group %s", name.c_str()); - totalParticles = pd->getNumParticles(); - /*Create ID list in CPU*/ - std::vector ids; - for(int i=0;ilog("[ParticleGroup] Group %s contains %d particles.", - name.c_str(), numberParticles); - /*Handle the case in which all particles belong to the group*/ - if(numberParticles==totalParticles){ - allParticlesInGroup = true; - } - else{ - myParticlesIdsGPU = ids; - - //Connect to reorder signal, index list needs to be updated each time a reorder occurs - reorderConnection = pd->getReorderSignal()->connect([this](){this->handleReorder();}); - //Allocate - myParticlesIndicesGPU.resize(numberParticles); - //Force update (creation) of the index list) - this->computeIndexList(true); - } - } - - //Specialization of a particle group with an All selector - template<> - ParticleGroup::ParticleGroup(particle_selector::All selector, - shared_ptr pd, shared_ptr sys, - std::string name): - pd(pd), sys(sys), name(name){ - sys->log("[ParticleGroup] Group %s created with All selector",name.c_str()); - this->allParticlesInGroup = true; - this->totalParticles = pd->getNumParticles(); - this->numberParticles = totalParticles; - - sys->log("[ParticleGroup] Group %s contains %d particles.", - name.c_str(), numberParticles); - } - //Specialization of an empty particle group - template<> - ParticleGroup::ParticleGroup(particle_selector::None selector, - shared_ptr pd, shared_ptr sys, - std::string name): - pd(pd), sys(sys), name(name){ - this->allParticlesInGroup = false; - this->totalParticles = pd->getNumParticles(); - this->numberParticles = 0; - reorderConnection = pd->getReorderSignal()->connect([this](){this->handleReorder();}); - } - - - //Constructor of ParticleGroup when an ID list is provided - template - ParticleGroup::ParticleGroup(InputIterator begin, InputIterator end, - shared_ptr pd, shared_ptr sys, - std::string name): - pd(pd), sys(sys), name(name){ - sys->log("[ParticleGroup] Group %s created from ID list.", name.c_str()); - numberParticles = std::distance(begin, end); - sys->log("[ParticleGroup] Group %s contains %d particles.", name.c_str(), numberParticles); - this->totalParticles = pd->getNumParticles(); - - if(numberParticles == totalParticles){ - this->allParticlesInGroup = true; - } - else{ - reorderConnection = pd->getReorderSignal()->connect([this](){this->handleReorder();}); - - //Create ID list in CPU - myParticlesIdsGPU.assign(begin, end); - myParticlesIndicesGPU.resize(numberParticles); - /*Force update (creation) of the index list)*/ - this->computeIndexList(true); - } - } - - - - //If no selector is provided, All is assumed - ParticleGroup::ParticleGroup(shared_ptr pd, shared_ptr sys, - std::string name): - ParticleGroup(particle_selector::All(), pd, sys, name){} - - - //This is trivial with pd->getIdOrderedIndices()! - //Handle a reordering of the particles (which invalids the previous relation between IDs and indices) - void ParticleGroup::computeIndexList(bool forceUpdate){ - - if(numberParticles==0) return; - if(this->needsIndexListUpdate || forceUpdate){//Update only if needed - sys->log("[ParticleGroup] Updating group %s after last particle sorting", name.c_str()); - - - const int *id2index = pd->getIdOrderedIndices(access::location::gpu); - - int Nthreads=(numberParticles>=512)?512:numberParticles; - int Nblocks=numberParticles/Nthreads + ((numberParticles%Nthreads)?1:0); - - int *myParticlesIndicesGPU_ptr = thrust::raw_pointer_cast(myParticlesIndicesGPU.data()); - int *myParticlesIdsGPU_ptr = thrust::raw_pointer_cast(myParticlesIdsGPU.data()); - ParticleGroup_ns::updateGroupIndices<<>>(id2index, - myParticlesIndicesGPU_ptr, - myParticlesIdsGPU_ptr, - numberParticles); - this->needsIndexListUpdate = false; - updateHostVector = true; - sys->log("[ParticleGroup] Updating group %s DONE!", name.c_str()); - } - } - //Add particles to the group via an array with ids - void ParticleGroup::addParticlesById(access::location loc, const int *ids, int N){ - sys->log("[ParticleGroup] Adding %d particles to group %s via ids!", N, name.c_str()); - int numberParticlesPrev = numberParticles; - numberParticles += N; - myParticlesIndicesGPU.resize(numberParticles); - myParticlesIdsGPU.resize(numberParticles); - - const int *id2index = pd->getIdOrderedIndices(access::location::gpu); - int Nthreads=(N>=128)?128:N; - int Nblocks=N/Nthreads + ((N%Nthreads)?1:0); - - int *myParticlesIndicesGPU_ptr = thrust::raw_pointer_cast(myParticlesIndicesGPU.data()); - int *myParticlesIdsGPU_ptr = thrust::raw_pointer_cast(myParticlesIdsGPU.data()); - if(!st) CudaSafeCall(cudaStreamCreate(&st)); - - auto copyKind = cudaMemcpyDeviceToDevice; - if(loc==access::location::cpu) copyKind = cudaMemcpyHostToDevice; - - CudaSafeCall(cudaMemcpyAsync(myParticlesIdsGPU_ptr+numberParticlesPrev, ids, - N*sizeof(int), copyKind, st)); - - const int *d_ids = ids; - cudaStream_t upSt = st; - if(loc==access::location::cpu){ - d_ids = myParticlesIdsGPU_ptr + numberParticlesPrev; - upSt = 0; - } - - ParticleGroup_ns::updateGroupIndices<<>>(id2index, - myParticlesIndicesGPU_ptr + numberParticlesPrev, - d_ids, - N); - CudaSafeCall(cudaStreamSynchronize(st)); - - } - - namespace ParticleGroup_ns{ - __global__ void IdsFromIndices(const int *indices, const int *index2Id, int* groupParticleIds, int N){ - int tid = blockIdx.x*blockDim.x + threadIdx.x; - if(tid>=N) return; - int index = indices[tid]; - int id = index2Id[index]; - groupParticleIds[tid] = id; - } - - } - //Add particles to the group via an array with the current indices of the particles in pd (faster) - void ParticleGroup::addParticlesByCurrentIndex(access::location loc, const int *indices, int N){ - sys->log("[ParticleGroup] Adding %d particles to group %s via indices!", N, name.c_str()); - if(N==0) return; - int numberParticlesPrev = numberParticles; - numberParticles += N; - myParticlesIndicesGPU.resize(numberParticles); - myParticlesIdsGPU.resize(numberParticles); - - const int *id2index = pd->getIdOrderedIndices(access::location::gpu); - int Nthreads=(N>=128)?128:N; - int Nblocks=N/Nthreads + ((N%Nthreads)?1:0); - - int *myParticlesIndicesGPU_ptr = thrust::raw_pointer_cast(myParticlesIndicesGPU.data()); - int *myParticlesIdsGPU_ptr = thrust::raw_pointer_cast(myParticlesIdsGPU.data()); - - if(!st) CudaSafeCall(cudaStreamCreate(&st)); - auto copyKind = cudaMemcpyDeviceToDevice; - if(loc==access::location::cpu) copyKind = cudaMemcpyHostToDevice; - - CudaSafeCall(cudaMemcpyAsync(myParticlesIndicesGPU_ptr+numberParticlesPrev, indices, - N*sizeof(int), copyKind, st)); - auto index2id = pd->getId(access::location::gpu, access::mode::read); - - const int *d_indices = indices; - cudaStream_t upSt = st; - if(loc == access::location::cpu){ - d_indices = myParticlesIndicesGPU_ptr+numberParticlesPrev; - upSt = 0; - } - ParticleGroup_ns::IdsFromIndices<<>>(d_indices, - index2id.raw(), - myParticlesIdsGPU_ptr+numberParticlesPrev, - N); - CudaSafeCall(cudaStreamSynchronize(st)); - } - -} -#endif diff --git a/gpuSPHINXsys/src/ParticleSorter.cuh b/gpuSPHINXsys/src/ParticleSorter.cuh deleted file mode 100644 index 953ca18a1b..0000000000 --- a/gpuSPHINXsys/src/ParticleSorter.cuh +++ /dev/null @@ -1,297 +0,0 @@ -#ifndef PARTICLESORTER_CUH -#define PARTICLESORTER_CUH - -#include"Box.cuh" -#include"Grid.cuh" -#include"System.h" -#include"debugTools.cuh" -#include - -namespace gpu{ - - namespace Sorter{ - - struct MortonHash{ - Grid grid; - MortonHash(Grid grid): grid(grid){} - //Interleave a 10 bit number in 32 bits, fill one bit and leave the other 2 as zeros. See [1] - inline __host__ __device__ uint encodeMorton(const uint &i) const{ - uint x = i; - x &= 0x3ff; - x = (x | x << 16) & 0x30000ff; - x = (x | x << 8) & 0x300f00f; - x = (x | x << 4) & 0x30c30c3; - x = (x | x << 2) & 0x9249249; - return x; - } - /*Fuse three 10 bit numbers in 32 bits, producing a Z order Morton hash*/ - inline __host__ __device__ uint hash(int3 cell) const{ - return encodeMorton(cell.x) | (encodeMorton(cell.y) << 1) | (encodeMorton(cell.z) << 2); - } - - inline __host__ __device__ uint operator()(real4 pos) const{ - const int3 cell = grid.getCell(pos); - return hash(cell); - } - - }; - //The hash is the cell 1D index, this pattern is better than random for neighbour transverse, but worse than Morton - struct CellIndexHash{ - Grid grid; - CellIndexHash(Grid grid): grid(grid){} - inline __device__ __host__ uint hash(int3 cell) const{ - return grid.getCellIndex(cell); - } - inline __host__ __device__ uint operator()(real4 pos) const{ - const int3 cell = grid.getCell(pos); - return hash(cell); - } - - }; - - static int _ffs(int i) - { - int bit; - - if (0 == i) - return 0; - - for (bit = 1; !(i & 1); ++bit) - i >>= 1; - return bit; - } - - int clz(uint n){ - n |= (n >> 1); - n |= (n >> 2); - n |= (n >> 4); - n |= (n >> 8); - n |= (n >> 16); - return 32-_ffs(n - (n >> 1)); - } - - template - __global__ void assignHash(HashIterator hasher, - int* __restrict__ index, - uint* __restrict__ hash , int N){ - const int i = blockIdx.x*blockDim.x + threadIdx.x; - if(i >= N) return; - const uint ihash = hasher[i]; - index[i] = i; - hash[i] = ihash; - } - - template - __global__ void reorderArray(const InputIterator old, - OutputIterator sorted, - int* __restrict__ pindex, int N){ - int i = blockIdx.x*blockDim.x + threadIdx.x; - if(i>=N) return; - sorted[i] = old[pindex[i]]; - } - - } - - class ParticleSorter{ - public: - ParticleSorter() = delete; - ParticleSorter(std::shared_ptr sys):sys(sys){}; - - template - void sortByKey(cub::DoubleBuffer &index, - cub::DoubleBuffer &hash, - int N, cudaStream_t st = 0, int end_bit = sizeof(uint)*8){ - if(N > maxRequestedElements){ - maxRequestedElements = N; - cub_temp_storage_bytes = 0; - CudaSafeCall(cub::DeviceRadixSort::SortPairs(nullptr, cub_temp_storage_bytes, - hash, - index, - N, - 0, end_bit, - st)); - } - auto alloc = sys->getTemporaryDeviceAllocator(); - std::shared_ptr d_temp_storage(alloc.allocate(cub_temp_storage_bytes), - [=](char* ptr){ alloc.deallocate(ptr);}); - void* d_temp_storage_ptr = d_temp_storage.get(); - CudaSafeCall(cub::DeviceRadixSort::SortPairs(d_temp_storage_ptr, cub_temp_storage_bytes, - hash, - index, - N, - 0, end_bit, - st)); - } - //Return the most significant bit of an unsigned integral type - template inline int msb(T n){ - static_assert(std::is_integral::value && !std::is_signed::value, - "msb(): T must be an unsigned integral type."); - for(T i = std::numeric_limits::digits - 1, mask = 1 << i; - i >= 0; - --i, mask >>= 1){ - if((n & mask) != 0) return i; - } - return 0; - } - - template - void updateOrderWithCustomHash(HashIterator &hasher, - uint N, uint maxHash = std::numeric_limits::max(), - cudaStream_t st = 0){ - sys->log("[ParticleSorter] Updating with custom hash iterator"); - sys->log("[ParticleSorter] Assigning hash to %d elements", N); - sys->log("[ParticleSorter] Maximum hash 0x%x ( dec: %u ) last bit: %d", - maxHash, maxHash, 32-Sorter::clz(maxHash) ); - try{ - tryToUpdateOrderWithCustomHash(hasher, N, maxHash, st); - } - catch(...){ - sys->log("ParticleSorter raised an exception in updateOrderWithCustomHash"); - throw; - } - } - - template - void updateOrderByCellHash(InputIterator pos, uint N, Box box, int3 cellDim, cudaStream_t st = 0){ - Grid grid(box, cellDim); - CellHasher hasher(grid); - auto hashIterator = thrust::make_transform_iterator(pos, hasher); - auto maxHash = hasher.hash(cellDim-1); - this->updateOrderWithCustomHash(hashIterator, N, maxHash, st); - } - - void updateOrderById(int *id, int N, cudaStream_t st = 0){ - try{ - tryToUpdateOrderById(id, N, st); - } - catch(...){ - sys->log("Exception raised in ParticleSorter::updateOrderById"); - throw; - } - } - - //WARNING: _unsorted and _sorted cannot be aliased! - template - void applyCurrentOrder(InputIterator d_property_unsorted, OutputIterator d_property_sorted, - int N, cudaStream_t st = 0){ - int Nthreads=128; - int Nblocks=N/Nthreads + ((N%Nthreads)?1:0); - Sorter::reorderArray<<>>(d_property_unsorted, - d_property_sorted, - thrust::raw_pointer_cast(index.data()), - N); - CudaCheckError(); - } - - int * getSortedIndexArray(int N){ - try{ - return tryToGetSortedIndexArray(N); - } - catch(...){ - sys->log("Exception raised in ParticleSorter::getSortedIndexArray"); - throw; - } - } - - uint * getSortedHashes(){ - return thrust::raw_pointer_cast(hash.data()); - } - - int * getIndexArrayById(int * id, int N, cudaStream_t st = 0){ - try{ - return tryToGetIndexArrayById(id, N, st); - } - catch(...){ - sys->log("Exception raised in ParticleSorter::getIndexArrayById"); - throw; - } - } - - private: - bool init = false; - bool originalOrderNeedsUpdate = true; - - int maxRequestedElements = 0; - size_t cub_temp_storage_bytes = 0; - - thrust::device_vector original_index; - thrust::device_vector index, index_alt; - thrust::device_vector hash, hash_alt; - - std::shared_ptr sys; - - void tryToUpdateOrderById(int *id, int N, cudaStream_t st){ - original_index.resize(N); - index_alt.resize(N); - hash.resize(N); - hash_alt.resize(N); - cub::CountingInputIterator ci(0); - thrust::copy(thrust::cuda::par, ci, ci+N, original_index.begin()); - int* d_hash = (int*)thrust::raw_pointer_cast(hash.data()); - CudaSafeCall(cudaMemcpyAsync(d_hash, id, N*sizeof(int), cudaMemcpyDeviceToDevice,st)); - auto db_index = cub::DoubleBuffer(thrust::raw_pointer_cast(original_index.data()), - thrust::raw_pointer_cast(index_alt.data())); - auto db_hash = cub::DoubleBuffer(d_hash, (int*)thrust::raw_pointer_cast(hash_alt.data())); - this->sortByKey(db_index, db_hash, N, st); - if(db_index.selector) - original_index.swap(index_alt); - } - - template - void tryToUpdateOrderWithCustomHash(HashIterator &hasher, uint N, uint maxHash, cudaStream_t st){ - init = true; - hash.resize(N); - index.resize(N); - int Nthreads=128; - int Nblocks=N/Nthreads + ((N%Nthreads)?1:0); - Sorter::assignHash<<>>(hasher, - thrust::raw_pointer_cast(index.data()), - thrust::raw_pointer_cast(hash.data()), - N); - CudaCheckError(); - hash_alt.resize(N); - index_alt.resize(N); - auto db_index = cub::DoubleBuffer(thrust::raw_pointer_cast(index.data()), - thrust::raw_pointer_cast(index_alt.data())); - auto db_hash = cub::DoubleBuffer(thrust::raw_pointer_cast(hash.data()), - thrust::raw_pointer_cast(hash_alt.data())); - //Cub just needs this endbit at least - int maxbit = 32-Sorter::clz(maxHash); - maxbit = std::min(maxbit, 32); - this->sortByKey(db_index, db_hash, N, st, maxbit); - CudaCheckError(); - //Sometimes CUB will not swap the references in the DoubleBuffer - if(db_index.selector) - index.swap(index_alt); - if(db_hash.selector) - hash.swap(hash_alt); - originalOrderNeedsUpdate = true; - } - - int * tryToGetSortedIndexArray(int N){ - int lastN = index.size(); - if(lastN != N){ - cub::CountingInputIterator ci(lastN); - index.resize(N); - thrust::copy(thrust::cuda::par, ci, ci+(N-lastN), index.begin()+lastN); - } - return thrust::raw_pointer_cast(index.data()); - } - - int * tryToGetIndexArrayById(int * id, int N, cudaStream_t st = 0){ - if(!init) return id; - if(originalOrderNeedsUpdate){ - this->updateOrderById(id, N, st); - originalOrderNeedsUpdate = false; - } - int lastN = original_index.size(); - if(lastN != N){ - original_index.resize(N); - thrust::copy(thrust::cuda::par, id, id+(N-lastN), original_index.begin()+lastN); - } - return thrust::raw_pointer_cast(original_index.data()); - } - - }; -} -#endif diff --git a/gpuSPHINXsys/src/Property.cuh b/gpuSPHINXsys/src/Property.cuh deleted file mode 100644 index 24c3725e13..0000000000 --- a/gpuSPHINXsys/src/Property.cuh +++ /dev/null @@ -1,278 +0,0 @@ -#ifndef PROPERTY_CUH -#define PROPERTY_CUH - -#include -#include"System.h" -#include"GPUUtils.cuh" -#include"debugTools.cuh" -#include"vector.cuh" -#include -#include - -namespace gpu{ - //Forward declaration for friend attribute - class ParticleData; - template struct Property; - - template - class property_ptr: - public thrust::iterator_adaptor, T*> - { - - public: - using Iterator = T*; - using super_t = thrust::iterator_adaptor, Iterator>; - private: - T *ptr; - size_t m_size; - bool *isBeingRead, *isBeingWritten; - bool isCopy = false; //true if this instance was created when passed to a cuda kernel - friend class thrust::iterator_core_access; - - void unlockProperty(){ - *isBeingWritten = false; - *isBeingRead = false; - } - - public: - - property_ptr(): - super_t(nullptr), - ptr(nullptr), - m_size(0), - isBeingRead(nullptr), isBeingWritten(nullptr) - {} - - property_ptr(T* ptr, - bool *isBeingWritten, bool *isBeingRead, - size_t in_size): - super_t(ptr), - ptr(ptr), - m_size(in_size), - isBeingWritten(isBeingWritten), - isBeingRead(isBeingRead) - {} - - __host__ __device__ property_ptr(const property_ptr& _orig ):super_t(_orig.ptr) { *this = _orig; isCopy = true; } - - __host__ __device__ ~property_ptr(){ -#ifdef __CUDA_ARCH__ - return; -#else - if(isCopy) return; - if(ptr) - unlockProperty(); -#endif - } - - __host__ __device__ T* raw() const { return ptr;} - - __host__ __device__ T* get() const { return raw();} - - __host__ __device__ Iterator end() const{ - if(ptr) - return begin()+size(); - else - return nullptr; - } - - __host__ __device__ Iterator begin() const{ return get();} - - __host__ __device__ size_t size() const{ return m_size;} - }; - - struct illegal_property_access: public std::runtime_error{ - using std::runtime_error::runtime_error; - }; - - template - struct Property{ - friend class ParticleData; - public: - using valueType = T; - using iterator = property_ptr; - Property(): Property(0, "noName", nullptr){} - Property(std::string name, shared_ptr sys): Property(0, name, sys){} - Property(int N, std::string name, shared_ptr sys):N(N), name(name), sys(sys) - { - sys->log("[Property] Property %s created with size %d", name.c_str(), N); - CudaCheckError(); - } - ~Property() = default; - - void resize(int Nnew){ - N = Nnew; - } - - void swapInternalBuffers(){ - try{ - tryToResizeAndSwapInternalContainers(); - } - catch(...){ - sys->log("[Property] Exception raised during internal container swap"); - throw; - } - } - - void swapCPUContainer(std::vector &outsideHostVector){ - sys->log("[Property] Swapping internal CPU container of property (%s)", name.c_str()); - swapWithExternalContainer(hostVector, outsideHostVector); - forceUpdate(access::location::gpu); - } - - void swapGPUContainer(thrust::device_vector &outsideDeviceVector){ - sys->log("[Property] Swapping internal CPU container of property (%s)", name.c_str()); - swapWithExternalContainer(deviceVector, outsideDeviceVector); - forceUpdate(access::location::cpu); - } - - iterator data(access::location dev, access::mode mode){ - sys->log("[Property] %s requested from %d (0=cpu, 1=gpu, 2=managed) with access %d (0=r, 1=w, 2=rw)", - name.c_str(), dev, mode); - try{ - return tryToGetData(dev,mode); - } - catch(...){ - sys->log("[Property] Exception raised in data request for property "+name); - throw; - } - } - - iterator begin(access::location dev, access::mode mode){ - return data(dev, mode); - } - - void forceUpdate(access::location dev){ - switch(dev){ - case access::location::cpu: - this->hostVectorNeedsUpdate = true; - break; - case access::location::gpu: - this->deviceVectorNeedsUpdate = true; - break; - } - } - - std::string getName() const{ return this->name;} - - int size() const{ return this->N;} - - bool isAllocated() const{ return this->N>0;} - - private: - - thrust::device_vector deviceVector, deviceVector_alt; - std::vector hostVector; - - uint N = 0; - bool deviceVectorNeedsUpdate = false, hostVectorNeedsUpdate = false; - string name; - bool isBeingWritten = false, isBeingRead= false; - shared_ptr sys; - - T* getAltGPUBuffer(){ - deviceVector_alt.resize(N); - return thrust::raw_pointer_cast(deviceVector_alt.data()); - } - - property_ptr tryToGetData(access::location dev, access::mode mode){ - const bool requestedForWriting = (mode==access::mode::write or mode==access::mode::readwrite); - throwIfIllegalDataRequest(mode); - lockIfNecesary(mode); - switch(dev){ - case access::location::cpu: - updateHostData(); - if(requestedForWriting) - deviceVectorNeedsUpdate=true; - return property_ptr(hostVector.data(), &this->isBeingWritten, &this->isBeingRead, size()); - case access::location::gpu: - updateDeviceData(); - if(requestedForWriting) - hostVectorNeedsUpdate=true; - return property_ptr(thrust::raw_pointer_cast(deviceVector.data()), - &this->isBeingWritten, &this->isBeingRead, size()); - - default: - throw std::runtime_error("[Property] Invalid location requested"); - } - } - - void throwIfIllegalDataRequest(access::mode mode){ - const bool requestedForWriting = (mode==access::mode::write or mode==access::mode::readwrite); - const bool requestedForReading = (mode==access::mode::read); - { - const bool isIllegalRequestForWriting = (this->isBeingWritten or this->isBeingRead) and requestedForWriting; - const bool isIllegalRequestForReading = (this->isBeingWritten and requestedForReading); - if(isIllegalRequestForWriting or isIllegalRequestForReading){ - sys->log("[Property] You cant request " + name + " property for " + - (this->isBeingWritten?"writing":"reading") + " while its locked!"); - throw illegal_property_access("Property "+name+" requested while locked"); - } - } - } - - void lockIfNecesary(access::mode request_mode){ - const bool requestedForWritting = (request_mode==access::mode::write or request_mode==access::mode::readwrite); - const bool requestedForReading = (request_mode==access::mode::read); - if(requestedForWritting) - this->isBeingWritten = true; - if(requestedForReading) - this->isBeingRead = true; - } - void updateHostData(){ - if(hostVector.size()!= N){ - sys->log("[Property] Resizing host version of " + name + " to " + std::to_string(N)+ " elements"); - hostVector.resize(N); - } - if(hostVectorNeedsUpdate){ - sys->log("Updating host version of %s", name.c_str()); - hostVector.resize(N); - CudaSafeCall(cudaMemcpy(hostVector.data(), - thrust::raw_pointer_cast(deviceVector.data()), - N*sizeof(T), cudaMemcpyDeviceToHost)); - hostVectorNeedsUpdate=false; - } - } - - void updateDeviceData(){ - if(deviceVector.size()!= N){ - sys->log("[Property] Resizing device version of " + name + " to " + std::to_string(N)+ " elements"); - deviceVector.resize(N); - } - if(deviceVectorNeedsUpdate){ - sys->log("Updating device version of %s", name.c_str()); - deviceVector = hostVector; - deviceVectorNeedsUpdate=false; - } - } - - void tryToResizeAndSwapInternalContainers(){ - - sys->log("[Property] Swapping internal device references of %s", name.c_str()); - deviceVector_alt.resize(N); - hostVectorNeedsUpdate = true; - } - - template - void tryToSwapWithExternalContainer(InternalContainer &myContainer, ExternalContainer &outsideContainer){ - throwIfnotInSwappableState(); - if(outsideContainer.size() != N) { - sys->log("[Property] Resizing input container, had %d elements, should have %d", - outsideContainer.size(), N); - outsideContainer.resize(N); - } - myContainer.swap(outsideContainer); - } - - void throwIfnotInSwappableState(){ - if(this->isBeingRead || this->isBeingWritten){ - sys->log("[Property] Cannot swap property %s while it is locked for writing/reading", - name.c_str()); - throw illegal_property_access("Property "+name+" requested while locked"); - } - } - - }; - -} -#endif diff --git a/gpuSPHINXsys/src/System.h b/gpuSPHINXsys/src/System.h deleted file mode 100644 index 8399a637b8..0000000000 --- a/gpuSPHINXsys/src/System.h +++ /dev/null @@ -1,89 +0,0 @@ -#ifndef SYSTEM_H -#define SYSTEM_H - -#include"defines.h" -#include"debugTools.cuh" -#include"Log.h" -#include -#include -#include -#include -#include"allocator.h" -#include - -namespace gpu{ - - using std::shared_ptr; - using std::string; - - struct access{ - enum location{cpu, gpu, nodevice}; - enum mode{read, write, readwrite, nomode}; - }; - - class insuficient_compute_capability_exception: public std::exception{ - const char* what() const noexcept { - return "Insuficient compute capability"; - } - }; - - class System{ - public: - - using resource = gpu::device_memory_resource; - using device_temporary_memory_resource = gpu::pool_memory_resource_adaptor; - - template - using allocator = gpu::polymorphic_allocator; - - enum LogLevel{CRITICAL=0, ERROR, EXCEPTION, WARNING, MESSAGE, STDERR, STDOUT, - DEBUG, DEBUG1, DEBUG2, DEBUG3, DEBUG4, DEBUG5, DEBUG6, DEBUG7}; - - private: - - void initializeCUDA(){ - try{ - CudaSafeCall(cudaFree(0)); - CudaSafeCall(cudaDeviceSynchronize()); - CudaCheckError(); - } - catch(...){ - log("[System] Exception raised at CUDA initialization"); - throw; - } - log("[System] CUDA initialized"); - } - - public: - System(){ - this->initializeCUDA(); - CudaCheckError(); - } - - void finish(){ - log("[System] finish"); - CudaSafeCall(cudaDeviceSynchronize()); - CudaCheckError(); - } - - template - static inline void log(char const *fmt, T... args){ - Logging::log(fmt, args...); - if(level == CRITICAL){ - throw std::runtime_error("System encountered an unrecoverable error"); - } - } - template - static inline void log(const std::string &msg){ - log("%s", msg.c_str()); - } - - template - static allocator getTemporaryDeviceAllocator(){ - return allocator(); - } - - }; - -} -#endif diff --git a/gpuSPHINXsys/src/allocator.h b/gpuSPHINXsys/src/allocator.h deleted file mode 100644 index d910e2e318..0000000000 --- a/gpuSPHINXsys/src/allocator.h +++ /dev/null @@ -1,211 +0,0 @@ -#ifndef ALLOCATOR_H -#define ALLOCATOR_H -#include -#include -#include"debugTools.cuh" -#include - -namespace gpu{ - - namespace detail{ - template using cuda_ptr = thrust::device_ptr; - template - class memory_resource{ - public: - using pointer = T; - using max_align_t = long double; //This C++11 alias is not available in std with g++-4.8.5 - pointer allocate(std::size_t bytes, std::size_t alignment = alignof(max_align_t)){ - return do_allocate(bytes, alignment); - } - - void deallocate(pointer p, std::size_t bytes, std::size_t alignment = alignof(max_align_t)){ - return do_deallocate(p, bytes, alignment); - } - - bool is_equal(const memory_resource &other) const noexcept{ - return do_is_equal(other); - } - - virtual pointer do_allocate(std::size_t bytes, std::size_t alignment) = 0; - - virtual void do_deallocate(pointer p, std::size_t bytes, std::size_t alignment) = 0; - - virtual bool do_is_equal(const memory_resource &other) const noexcept{ - return this == &other; - } - - }; - - template - MR* get_default_resource(){ - static MR default_resource; - return &default_resource; - } - } - - //A device memory resource, with cuda raw pointers - class device_memory_resource : public detail::memory_resource{ - using super = detail::memory_resource; - public: - - using pointer = typename super::pointer; - - virtual pointer do_allocate(std::size_t bytes, std::size_t alignment) override{ - return thrust::raw_pointer_cast(thrust::cuda::malloc(bytes)); - } - - virtual void do_deallocate(pointer p, std::size_t bytes, std::size_t alignment) override{ - thrust::cuda::pointer void_ptr(p); - thrust::cuda::free(void_ptr); - } - - }; - - //A pool device memory_resource, stores previously allocated blocks in a cache - // and retrieves them fast when similar ones are allocated again (without calling malloc everytime). - template - struct pool_memory_resource_adaptor: public detail::memory_resource{ - private: - using super = detail::memory_resource; - MR* res; - public: - using pointer = typename super::pointer; - - ~pool_memory_resource_adaptor(){ - try{ - free_all(); - } - catch(...){ - } - } - - pool_memory_resource_adaptor(MR* resource): res(resource){} - pool_memory_resource_adaptor(): res(detail::get_default_resource()){} - - using FreeBlocks = std::multimap; - using AllocatedBlocks = std::map; - FreeBlocks free_blocks; - AllocatedBlocks allocated_blocks; - - virtual pointer do_allocate( std::size_t bytes, std::size_t alignment) override{ - pointer result; - std::ptrdiff_t blockSize = 0; - auto available_blocks = free_blocks.equal_range(bytes); - auto available_block = available_blocks.first; - //Look for a block of the same size - if(available_block == free_blocks.end()){ - available_block = available_blocks.second; - } - //Try to find a block greater than requested size - if(available_block != free_blocks.end() ){ - result = pointer(available_block -> second); - blockSize = available_block -> first; - free_blocks.erase(available_block); - } - else{ - result = res->do_allocate(bytes, alignment); - blockSize = bytes; - } - allocated_blocks.insert(std::make_pair(thrust::raw_pointer_cast(result), blockSize)); - return result; - } - - virtual void do_deallocate(pointer p, std::size_t bytes, std::size_t alignment) override{ - auto block = allocated_blocks.find(thrust::raw_pointer_cast(p)); - if(block == allocated_blocks.end()){ - throw std::system_error(EFAULT, std::generic_category(), "Address is not handled by this instance."); - } - std::ptrdiff_t num_bytes = block->second; - allocated_blocks.erase(block); - free_blocks.insert(std::make_pair(num_bytes, thrust::raw_pointer_cast(p))); - } - - virtual bool do_is_equal(const super &other) const noexcept override { - return res->do_is_equal(other); - } - - void free_all(){ - for(auto &i: free_blocks) res->do_deallocate(static_cast(i.second), i.first, 0); - for(auto &i: allocated_blocks) res->do_deallocate(static_cast(i.first), i.second, 0); - free_blocks.clear(); - allocated_blocks.clear(); - } - - }; - - namespace detail{ - //Takes a pointer type (including smart pointers) and returns a reference to the underlying type - template struct pointer_to_lvalue_reference{ - private: - using element_type = typename std::pointer_traits::element_type; - public: - using type = typename std::add_lvalue_reference::type; - }; - - //Specialization for special thrust pointer/reference types... - template struct pointer_to_lvalue_reference>{ - using type = thrust::system::cuda::reference; - }; - - template struct non_void_value_type{using type = T;}; - template<> struct non_void_value_type{using type = char;}; - - } - - //An allocator that can be used for any type using the same underlying memory_resource. - //pointer type can be specified to work with thrust cuda pointers - template, - class void_pointer = T*> - class polymorphic_allocator{ - MR * res; - public: - //C++17 definitions for allocator interface - using size_type = std::size_t; - - using value_type = T; - using value_size_type = typename detail::non_void_value_type::type; - - using differente_type = std::ptrdiff_t; - - //All of the traits below are deprecated in C++17, but thrust counts on them - //using void_pointer = T*; - using pointer = typename std::pointer_traits::template rebind; - - using reference = typename detail::pointer_to_lvalue_reference::type; - using const_reference = typename detail::pointer_to_lvalue_reference>::type; - - using propagate_on_container_copy_assignment = std::true_type; - using propagate_on_container_move_assignment = std::true_type; - using propagate_on_container_swap = std::true_type; - - template - polymorphic_allocator(Other other) : res(other.resource()){} - - polymorphic_allocator(MR * resource) : res(resource){} - - polymorphic_allocator() : res(detail::get_default_resource()){} - - MR* resource() const { return this->res;} - - pointer allocate (size_type n) const{ - return static_cast(static_cast(this->res->do_allocate(n * sizeof(value_size_type), - alignof(value_size_type)))); - } - - void deallocate (pointer p, size_type n = 0) const{ - return this->res->do_deallocate(thrust::raw_pointer_cast(p), - n * sizeof(value_size_type), - alignof(value_size_type)); - } - }; - - //thrust versions are not reliable atm, std::pmr is C++17 which CUDA 10.1 does not support yet and is not thrust::cuda compatible (thrust expects device allocators to return thurst::cuda::pointers) - //template using memory_resource = thrust::mr::memory_resource; - //template using polymorphic_resource_adaptor = thrust::mr::polymorphic_adaptor_resource; - //template using allocator = thrust::mr::allocator; - //template using polymorphic_allocator = thrust::mr::polymorphic_allocator; - -} - -#endif diff --git a/gpuSPHINXsys/src/debugTools.cuh b/gpuSPHINXsys/src/debugTools.cuh deleted file mode 100644 index fb5fe625e3..0000000000 --- a/gpuSPHINXsys/src/debugTools.cuh +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef DEBUGTOOLS_CUH -#define DEBUGTOOLS_CUH - -#define CUDA_ERROR_CHECK - -#define CudaSafeCall(err) __cudaSafeCall(err, __FILE__, __LINE__) -#define CudaCheckError() __cudaCheckError(__FILE__, __LINE__) - -#include - -namespace gpu{ - - class cuda_generic_error: public std::runtime_error{ - cudaError_t error_code; - public: - cuda_generic_error(std::string msg, cudaError_t err): - std::runtime_error(msg + ": " + cudaGetErrorString(err) + " - code: " + std::to_string(err)), - error_code(err){} - - cudaError_t code(){return error_code;} - }; - -} - -inline void __cudaSafeCall(cudaError err, const char *file, const int line){ - #ifdef CUDA_ERROR_CHECK - if (cudaSuccess != err){ - cudaGetLastError(); //Reset CUDA error status - throw gpu::cuda_generic_error("CudaSafeCall() failed at "+ - std::string(file) + ":" + std::to_string(line), err); - } - #endif -} - -inline void __cudaCheckError(const char *file, const int line){ - cudaError err; - err = cudaGetLastError(); - if(cudaSuccess != err){ - throw gpu::cuda_generic_error("CudaCheckError() failed at "+ - std::string(file) + ":" + std::to_string(line), err); - } -} - -#endif diff --git a/gpuSPHINXsys/src/defines.h b/gpuSPHINXsys/src/defines.h deleted file mode 100644 index 7227635b78..0000000000 --- a/gpuSPHINXsys/src/defines.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef DEFINES_H -#define DEFINES_H -#include"cuda_runtime.h" - -#ifndef DOUBLE_PRECISION -#define SINGLE_PRECISION -#endif - - -#define fori(x,y) for(int i=x; i pd, - shared_ptr nl, - shared_ptr sys, - Parameters par): - pd(pd), nl(nl), sys(sys), - stream(par.stream){ - printf("|dtSizeCalc| \tis called with dtMin: %f \n", dtMin); -} - -dtSizeCalc::~dtSizeCalc(){ - printf("|dtSizeCalc| \tcall ended! \n"); -} - -namespace dtSizeCalc_ns{ - -//function to get the magnitude of acc and vel -__global__ void magAccVel(int N, - const int* __restrict__ groupIndex, - real3* __restrict__ vel, - real4* __restrict__ force, - real* __restrict__ velMag, - real* __restrict__ AccMag){ - int i = blockIdx.x*blockDim.x+threadIdx.x; - if(i>=N) return; - const real3 veli = vel[groupIndex[i]]; - const real3 acci = make_real3(force[groupIndex[i]]); - velMag[groupIndex[i]] = sqrtf(dot(veli, veli)); - AccMag[groupIndex[i]] = sqrtf(dot(acci, acci)); -} - -}//dtSizeCalc_ns - -//function to find the max acceleration and velocity in the systems -template -real dtSizeCalc::maxAccVel() -{ - int np = pd->getNumParticles(); - int Nthreads=128; - int Nblocks=np/Nthreads + ((np%Nthreads)?1:0); - auto groupIndex = nl->getGroupIndexIterator(); -#ifndef _TRANSPORT_VELOCITY_ - auto vel = pd->getVel(access::location::gpu, access::mode::read); -#else - auto vel = pd->getVel_tv(access::location::gpu, access::mode::read); -#endif - auto force = pd->getForce(access::location::gpu, access::mode::read); - - velMag.resize(np); - AccMag.resize(np); - - auto velMag_ptr = thrust::raw_pointer_cast(velMag.data()); - auto AccMag_ptr = thrust::raw_pointer_cast(AccMag.data()); - - //TODO: this coud also be improved by templates when force and vel are of the same type (e.g. real3) - dtSizeCalc_ns::magAccVel<<>>(np, - groupIndex, - vel.raw(), - force.raw(), - velMag_ptr, - AccMag_ptr); - - //find the Max of reducedPar(acc or vel) among all particles - real *maxPar; - cudaMalloc(&maxPar, sizeof(real)); - { - size_t newSize = 0; - if(reductionOpt == reducedPar::Vel) - cub::DeviceReduce::Max(nullptr, newSize, velMag_ptr, maxPar, np); - else if(reductionOpt == reducedPar::Acc) - cub::DeviceReduce::Max(nullptr, newSize, AccMag_ptr, maxPar, np); - else - throw std::runtime_error("|dtSizeCalc| \treducedPar is not valid!"); - - - if(newSize > tempStorage.size()){ - tempStorage.resize(newSize); - } - } - size_t size = tempStorage.size(); - if(reductionOpt == reducedPar::Vel) - cub::DeviceReduce::Max((void*)thrust::raw_pointer_cast(tempStorage.data()), size, velMag_ptr, maxPar, np); - else if(reductionOpt == reducedPar::Acc) - cub::DeviceReduce::Max((void*)thrust::raw_pointer_cast(tempStorage.data()), size, AccMag_ptr, maxPar, np); - - - real max = 0; - CudaSafeCall(cudaMemcpy(&max, maxPar, sizeof(real), cudaMemcpyDeviceToHost)); - CudaSafeCall(cudaFree(maxPar)); - return max; -} - -//calculate the advection time step size -//Eq. 8 in doi.org/10.1016/j.jcp.2019.109135 -real dtSizeCalc::calcDtAdv(real h, real U_f){ - real cflAdv = 0.25; - real velMax = 0.0; - velMax = maxAccVel(); - real Umax = std::max(U_f, velMax); - //dt1 based on advection - const real dt1 = h / (Umax + 1e-6f); - //dt2 based on viscous terms (TODO) - const real dt2 = std::numeric_limits::max();// h*h/kinViscosity; - //new value of the dynamic time step. - real dtSize = cflAdv * std::min(dt1, dt2); - if(dtSize(); - //new value of the dynamic time step. - real dtSize = cflAcs * h / (c_f + velMax + 1e-6f); - if(dtSize(); - accMax = maxAccVel(); - //dt1 based on force per unit mass. - const real dtF = (accMax)?sqrtf(h/accMax):std::numeric_limits::max(); - //dt2 based on advection - const real dtAd = h/(std::max(c_f,velMax*10.f)); - //new value of the dynamic time step. - real dtSize=CFL*std::min(dtF,dtAd); - if(dtSize -#include - -namespace gpu{ -class dtSizeCalc{ - -private: - cudaStream_t stream; - real dtMin = 1.e-5f; - - enum reducedPar{Acc, Vel}; //reduced Parameter: acc or vel - - thrust::device_vector tempStorage; //temporary storage - cub::Reduce - thrust::device_vector velMag, AccMag; - - shared_ptr pd; - shared_ptr nl; - shared_ptr sys; - -public: - struct Parameters{ - cudaStream_t stream; - }; - - dtSizeCalc(shared_ptr pd, - shared_ptr nl, - shared_ptr sys, - Parameters par); - ~dtSizeCalc(); - - template - real maxAccVel(); - real calcDt(real h, real c_f); - real calcDtAdv(real h, real U_f); - real calcDtAcs(real h, real c_f); - -}; -}//namespace gpu - -#include"dtSizeCalc.cu" -#endif - diff --git a/gpuSPHINXsys/src/fluidDynamics.cu b/gpuSPHINXsys/src/fluidDynamics.cu deleted file mode 100644 index 6e75194eb4..0000000000 --- a/gpuSPHINXsys/src/fluidDynamics.cu +++ /dev/null @@ -1,686 +0,0 @@ -/* - * Massoud Rezavand 2019. - * Technical University of Munich - * gpuSPHinxsys - An SPH solver for CUDA enabled GPUs - * - * This module collects all the functions required for - * the fluid dynamics related calculations - */ - -#include"fluidDynamics.cuh" -#include"Kernel.cuh" - -using Kernel = gpu::KernelFunction::Wendland_C4; - -namespace gpu{ - -fluidDynamics::fluidDynamics(shared_ptr pd, - shared_ptr nl, - shared_ptr sys, - Parameters par): - pd(pd), nl(nl), sys(sys), - c_f(par.c_f), rho0_f(par.rho0_f), bodyForce(par.bodyForce), - stream(par.stream), box(par.box){ - printf("|fluidDynamics| \tis called with c_f = %.1f, rho0_f = %.1f \n", c_f, rho0_f); -} - -fluidDynamics::~fluidDynamics(){ - printf("|fluidDynamics| \tcall ended! \n"); -} - -namespace fluidDynamics_ns{ - -//Kernel to calculate drho/dt by Riemann Solvers (Continuity Eq.) -template -__global__ void calcDensityRiemann_ker(NeighbourContainer ni, - Kernel kernel, - const real4* __restrict__ sortPos, - const int* __restrict__ groupIndex, - int np, Box box, - real3* __restrict__ vel, - real* __restrict__ rho, - real* __restrict__ mass, - real* __restrict__ p, real h, real dt, - real c_f, - const real* __restrict__ rho0, - real* __restrict__ vol){ - int i = blockIdx.x*blockDim.x + threadIdx.x; - if(i >= np) return; - //only on LIQUID particles - const real4 posi = cub::ThreadLoad(sortPos + i); - if(posi.w != WALL){ - //Set ni to provide iterators for particle i - ni.set(i); - - const real3 ri = make_real3(cub::ThreadLoad(sortPos + i)); - const real3 veli = vel[groupIndex[i]]; - const real rhoi = rho[groupIndex[i]]; - const real pi = p[groupIndex[i]]; - real drho = real(); - - auto it = ni.begin(); //Iterator to the first neighbour of particle i - - while(it){ - auto neigh = *it++; - if(neigh.getGroupIndex() == groupIndex[i]) continue; //skip if same particle - - const real3 rj = make_real3(neigh.getPos()); - const int typej = neigh.getPos().w; - const real3 velj = vel[neigh.getGroupIndex()]; - const real rhoj = rho[neigh.getGroupIndex()]; - const real massj = mass[neigh.getGroupIndex()]; - const real volj = vol[neigh.getGroupIndex()]; - const real3 rij = box.apply_pbc(ri-rj); - - //low dissipation Riemann problem - real r2 = dot(rij, rij); - real dist = sqrtf(r2); - real3 _rij = rj - ri; - real pj = p[neigh.getGroupIndex()]; - real3 e_ij = _rij*1/(dist + 1.0e-15); - real ul = dot(e_ij, veli); - real ur = dot(e_ij, velj); - real v_star = (rhoi*ul+rhoj*ur+(pi-pj)/c_f)/(rhoi+rhoj); - real aw = kernel.gradient(rij, h, box.boxSize.z); - //only volume of wall particles into account - if (typej == WALL) - drho += 2.0*rhoi*volj*(v_star-ul)*aw*dist; - else - drho += 2.0*rhoi*massj/rhoj*(v_star-ul)*aw*dist; - } - rho[groupIndex[i]] += drho*dt; - //get the volume according to rho - vol[groupIndex[i]] = mass[groupIndex[i]]/rho[groupIndex[i]]; - // pressure calculation via the linear EoS - p[groupIndex[i]] = c_f*c_f*(rho[groupIndex[i]] - rho0[groupIndex[i]]); -// printf("rho = %f and rho0 = %f \n",rho[groupIndex[i]], rho0[groupIndex[i]] ); - - } -} - -//Kernel to calculate drho/dt using Artificial viscosity (Continuity Eq.) -template -__global__ void calcDensityArtificial_ker(NeighbourContainer ni, - Kernel kernel, - const real4* __restrict__ sortPos, - const int* __restrict__ groupIndex, - int np, Box box, - real3* __restrict__ vel, - real* __restrict__ rho, - real* __restrict__ mass, - real* __restrict__ p, - real h, real dt, - real c_f, - const real* __restrict__ rho0){ - int i = blockIdx.x*blockDim.x + threadIdx.x; - if(i >= np) return; - //only on LIQUID and gas particles - const real4 posi = cub::ThreadLoad(sortPos + i); - if(posi.w != WALL){ - //Set ni to provide iterators for particle i - ni.set(i); - - const real3 ri = make_real3(cub::ThreadLoad(sortPos + i)); - const real3 veli = vel[groupIndex[i]]; - const real rhoi = rho[groupIndex[i]]; - real drho = real(); - - auto it = ni.begin(); //Iterator to the first neighbour of particle i - - while(it){ - auto neigh = *it++; - if(neigh.getGroupIndex() == groupIndex[i]) continue; //skip if same particle - - const real3 rj = make_real3(neigh.getPos()); - const real3 velj = vel[neigh.getGroupIndex()]; - const real rhoj = rho[neigh.getGroupIndex()]; - const real massj = mass[neigh.getGroupIndex()]; - - const real3 rij = box.apply_pbc(ri-rj); - //Artificial viscosity - const real3 velij = veli - velj; - //TODO - //in this AV implemetaion we should use the Cubic kernel which indludes rij in there - const real3 kernel_grad = /*rij**/rij*kernel.gradient(rij, h, box.boxSize.z); - drho += rhoi*massj/rhoj*dot(kernel_grad, velij); - } - rho[groupIndex[i]] += drho*dt; - // pressure calculation via the linear EoS - p[groupIndex[i]] = c_f*c_f*(rho[groupIndex[i]] - rho0[groupIndex[i]]); - } -} - -//Kernel to calculate initail number density sigma0 -template -__global__ void calcInitNumDensity_ker(NeighbourContainer ni, - Kernel kernel, - const real4* __restrict__ sortPos, - const int* __restrict__ groupIndex, - real* __restrict__ sigma0, - int np, Box box, - real h, real rho0_f){ - int i = blockIdx.x*blockDim.x + threadIdx.x; - if(i >= np) return; - ni.set(i); - const real3 ri = make_real3(cub::ThreadLoad(sortPos + i)); - real sum0 = real(); - auto it = ni.begin(); - while(it){ - auto neigh = *it++; - const real3 rj = make_real3(neigh.getPos()); - const real3 rij = box.apply_pbc(ri-rj); - sum0 += kernel(rij, h, box.boxSize.z); - } - sigma0[groupIndex[i]] = sum0; -} - -//Kernel to update density using summation for free surface cases -template -__global__ void densitySumFreeSurface_ker(NeighbourContainer ni, - Kernel kernel, - const real4* __restrict__ sortPos, - const int* __restrict__ groupIndex, - real* __restrict__ sigma0, - real* __restrict__ rho, - int np, Box box, - real h, - const real* __restrict__ rho0){ - int i = blockIdx.x*blockDim.x + threadIdx.x; - if(i >= np) return; - const real4 posi = cub::ThreadLoad(sortPos + i); - if(posi.w != WALL){ - ni.set(i); - const real3 ri = make_real3(cub::ThreadLoad(sortPos + i)); - const real sigma0i = sigma0[groupIndex[i]]; - real rhoi = rho[groupIndex[i]]; - real sigma = real(); - auto it = ni.begin(); - while(it){ - auto neigh = *it++; - const real4 posj = neigh.getPos(); - // include only Fluid neighboring particles - if((posi.w ==LIQUID and posj.w == THIRDBODY) /*or - (posi.w == THIRDBODY and posj.w ==LIQUID) or - (posi.w == THIRDBODY and posj.w ==THIRDBODY)*/) continue; - const real3 rj = make_real3(neigh.getPos()); - const real3 rij = box.apply_pbc(ri-rj); - sigma += kernel(rij, h, box.boxSize.z); - } - real rhoSum = sigma * rho0[groupIndex[i]] / sigma0i; - rho[groupIndex[i]] = rhoSum + fmax(0.0f, (rhoi - rhoSum)) * rho0[groupIndex[i]] / rhoi; - } -} - -//Kernel to update density using summation for the lighter phase -template -__global__ void densitySumLightPhase_ker(NeighbourContainer ni, - Kernel kernel, - const real4* __restrict__ sortPos, - const int* __restrict__ groupIndex, - real* __restrict__ sigma0, - real* __restrict__ rho, - int np, Box box, real h, - const real* __restrict__ rho0, - real* __restrict__ p, - const real c_f, - real* __restrict__ vol, - const real* __restrict__ mass){ - int i = blockIdx.x*blockDim.x + threadIdx.x; - if(i >= np) return; - //only on gas particles - const real4 posi = cub::ThreadLoad(sortPos + i); - if(posi.w == THIRDBODY){ - ni.set(i); - const real3 ri = make_real3(cub::ThreadLoad(sortPos + i)); - - const real rho0i = rho0[groupIndex[i]]; - - real sum = real(); - auto it = ni.begin(); - while(it){ - auto neigh = *it++; - const real3 rj = make_real3(neigh.getPos()); - const real3 rij = box.apply_pbc(ri-rj); - - sum += kernel(rij, h, box.boxSize.z); - /*or in a total Lagrangian form: */ - //const real rho0j = rho0[neigh.getGroupIndex()]; - //sum += kernel(rij, h, box.boxSize.z)*2.f*rho0i/(rho0i+rho0j); - } - rho[groupIndex[i]] = sum * mass[groupIndex[i]]; - /*or in a total Lagrangian form: */ - //const real sigma0i = sigma0[groupIndex[i]]; - //rho[groupIndex[i]] = sum * rho0i / sigma0i; - - //get the volume according to rho - vol[groupIndex[i]] = mass[groupIndex[i]]/rho[groupIndex[i]]; - // pressure calculation via the linear EoS - p[groupIndex[i]] = c_f*c_f*(rho[groupIndex[i]] - rho0i); - } - -} - - -//Kernel to calculate pressure for wall particles -template -__global__ void calcPressureBC_ker(NeighbourContainer ni, - Kernel kernel, - const real4* __restrict__ sortPos, - const int* __restrict__ groupIndex, - int np, Box box, - real* __restrict__ rho, - real* __restrict__ p, - real h, real c_f, - const real* __restrict__ rho0, - real3 bodyForce){ - int i = blockIdx.x*blockDim.x + threadIdx.x; - if(i >= np) return; - //only on Wall particles - const real4 posi = cub::ThreadLoad(sortPos + i); - if(posi.w == WALL){ - //Set ni to provide iterators for particle i - ni.set(i); - - const real3 ri = make_real3(cub::ThreadLoad(sortPos + i)); - real sum0 = real(); - real sum1 = real(); - real3 sum2 = real3(); - real3 aw = real3(); - //gravity - //in case coordinates are needed ow. directly can be used - const real3 body_force = bodyForce; - - auto it = ni.begin(); //Iterator to the first neighbour of particle i - - while(it){ - auto neigh = *it++; - if(neigh.getGroupIndex() == groupIndex[i]) continue; //skip if same particle - const real4 posj = neigh.getPos(); - // include only Fluid neighboring particles - if(posj.w == WALL) continue; - - const real3 rj = make_real3(neigh.getPos()); - const real rhoj = rho[neigh.getGroupIndex()]; - const real pj = p[neigh.getGroupIndex()]; - const real3 rij = box.apply_pbc(ri-rj); - const real wij = kernel(rij, h, box.boxSize.z); -// printf("rhoj gas = %f \trho0j gas = %f \n", rho[neigh.getGroupIndex()], rho0[neigh.getGroupIndex()]); - - //fraction devided by rho_j to get pressure from the lighter fluid - sum0 += wij/rhoj; - sum1 += pj*wij/rhoj; - sum2 += rij*wij*rhoj/rhoj; - - } - aw = body_force;// - aw; //for later developments - real tmp = real(); - tmp = dot(aw, sum2); - p[groupIndex[i]] = (sum1+tmp)/(sum0+1.e-20); - //get density for wall particles - rho[groupIndex[i]] = p[groupIndex[i]]/(c_f*c_f) + rho0[groupIndex[i]]; -// printf("rho gas = %f \trho0 gas = %f \n", rho[groupIndex[i]], rho0[groupIndex[i]]); - } -} - -//Kernel to calculate pressure and viscosity related forces via RiemannSolvers -template -__global__ void calcForceRiemann_ker(NeighbourContainer ni, - Kernel kernel, - const real4* __restrict__ sortPos, - const int* __restrict__ groupIndex, - int np, Box box, - real4* __restrict__ force, - real3* __restrict__ vel, - real* __restrict__ rho, - real* __restrict__ mass, - real* __restrict__ p, - real3* __restrict__ vel_tv, - real3* __restrict__ F_Pb, real P_b, - real h, real c_f, real3 bodyForce, - real physicalTime, - real* __restrict__ vol){ - int i = blockIdx.x*blockDim.x + threadIdx.x; - if(i >= np) return; - //only on Fluid particles - const real4 posi = cub::ThreadLoad(sortPos + i); - if(posi.w != WALL){ - //gravity -#ifdef _TIMEDEPENDENT_BODYFORCE_ //for sloshing tank - const real4 body_force = make_real4(bodyForce.x*sin(2.*M_PI*0.496*physicalTime) - ,bodyForce.y, 0., 0.); -#else - const real4 body_force = make_real4(bodyForce, 0.); -#endif - - //Set ni to provide iterators for particle i - ni.set(i); - - const real3 ri = make_real3(cub::ThreadLoad(sortPos + i)); - const real rhoi = rho[groupIndex[i]]; - const real pi = p[groupIndex[i]]; - const real3 veli = vel[groupIndex[i]]; - real3 F1 = real3(); - - auto it = ni.begin(); - - while(it){ - auto neigh = *it++; - - if(neigh.getGroupIndex() == groupIndex[i]) continue; //skip if same particle - - const real3 rj = make_real3(neigh.getPos()); - const real rhoj = rho[neigh.getGroupIndex()]; - const real pj = p[neigh.getGroupIndex()]; - const real massj = mass[neigh.getGroupIndex()]; - const int typej = neigh.getPos().w; - real volj; - //only volume of wall particles into account - if(typej != WALL) - volj = massj/rhoj; - else - volj = vol[neigh.getGroupIndex()]; - const real3 velj = vel[neigh.getGroupIndex()]; - const real3 rij = box.apply_pbc(ri-rj); - //low dissipation Riemann problem - const real3 kernel_grad = rij*kernel.gradient(rij, h, box.boxSize.z); - const real4 posj = neigh.getPos(); - if(posj.w != WALL){ - real r2 = dot(rij, rij); - real dist = sqrtf(r2); - real3 _rij = rj - ri; - real3 e_ij = _rij*1/(dist + 1.0e-15); - real ul = dot(e_ij, veli); - real ur = dot(e_ij, velj); - real p_star = (rhoi*pj+rhoj*pi+rhoi*rhoj*c_f*(ul-ur)* - fmin(real(3.0)*fmax((ul-ur)/c_f, real(0.0)), real(1.0)))/(rhoi+rhoj); - real temp1 = -2.0*p_star*volj/rhoi; - F1 += temp1*kernel_grad; - }else{ - //exclude the second term only when Fluid-Wall interaction - real p_star = (rhoj*pi + rhoi*pj)/(rhoi + rhoj); - real temp1 = -2.0*p_star*volj/rhoi; - F1 += temp1*kernel_grad; - } - //transport velocity formulation -#ifdef _TRANSPORT_VELOCITY_ - if(posi.w == THIRDBODY){ - const real3 v_tv_i = vel_tv[groupIndex[i]]; - const real3 v_tv_j = vel_tv[neigh.getGroupIndex()]; - const real massi = mass[groupIndex[i]]; - const real voli = massi/rhoi; - const real coef = real(1.)/massi*(voli*voli + volj*volj); - // artificial stress tensor Aij (Adami et al. 2013) - real3 A_ij = real3(); - // x component - real3 Ax_i = (v_tv_i - veli) * rhoi * veli.x; - real3 Ax_j = (v_tv_j - velj) * rhoj * velj.x; - A_ij.x = real(0.5)*dot((Ax_i+Ax_j), kernel_grad); - // y component - real3 Ay_i = (v_tv_i - veli) * rhoi * veli.y; - real3 Ay_j = (v_tv_j - velj) * rhoj * velj.y; - A_ij.y = real(0.5)*dot((Ay_i+Ay_j), kernel_grad); - // z component - real3 Az_i = (v_tv_i - veli) * rhoi * veli.z; - real3 Az_j = (v_tv_j - velj) * rhoj * velj.z; - A_ij.z = real(0.5)*dot((Az_i+Az_j), kernel_grad); - - real3 dF_AS = A_ij * coef; - F1 += dF_AS; - // background pressure force - real P_b1 = 5.*0.001*c_f*c_f; - real temp3 = real(-2.)*P_b1*volj/rhoi; - real3 dF_Pb = kernel_grad * temp3; - F_Pb[groupIndex[i]] += dF_Pb; - } -#endif - } - force[groupIndex[i]] = make_real4(F1, 0); -// if(posi.w == LIQUID) - force[groupIndex[i]] += body_force; - } -} - -//Kernel to calculate pressure and Artificial viscosity forces -template -__global__ void calcForceArtificial_ker(NeighbourContainer ni, - Kernel kernel, - const real4* __restrict__ sortPos, - const int* __restrict__ groupIndex, - int np, Box box, - real4* __restrict__ force, - real3* __restrict__ vel, - real* __restrict__ rho, - real* __restrict__ mass, - real* __restrict__ p, - real h, real c_f, real3 bodyForce){ - int i = blockIdx.x*blockDim.x + threadIdx.x; - if(i >= np) return; - //only on Fluid particles - const real4 posi = cub::ThreadLoad(sortPos + i); - if(posi.w == LIQUID){ - //gravity - const real4 body_force = make_real4(bodyForce, 0.); - - //Set ni to provide iterators for particle i - ni.set(i); - - const real3 ri = make_real3(cub::ThreadLoad(sortPos + i)); - const real rhoi = rho[groupIndex[i]]; - const real pi = p[groupIndex[i]]; - const real massi = mass[groupIndex[i]]; - const real voli = massi/rhoi; - const real3 veli = vel[groupIndex[i]]; - - real3 F1 = real3(); - real3 F2 = real3(); - - auto it = ni.begin(); - - while(it){ - auto neigh = *it++; - - if(neigh.getGroupIndex() == groupIndex[i]) continue; //skip if same particle - - const real3 rj = make_real3(neigh.getPos()); - const real rhoj = rho[neigh.getGroupIndex()]; - const real pj = p[neigh.getGroupIndex()]; - const real massj = mass[neigh.getGroupIndex()]; - const real volj = massj/rhoj; - const real3 velj = vel[neigh.getGroupIndex()]; - const real3 rij = box.apply_pbc(ri-rj); - //Artificial viscosity - //TODO - //in this AV implemetaion we should use the Cubic kernel which indludes rij in there - const real3 kernel_grad = /*rij**/rij*kernel.gradient(rij, h, box.boxSize.z); - const real temp0 = 1./massi*(voli*voli + volj*volj); - const real pij = (rhoj*pi + rhoi*pj)/(rhoi + rhoj); - const real temp1 = -1.*pij*temp0; - F1 += temp1*kernel_grad; - - const real4 posj = neigh.getPos(); - //TODO - //if(posj.w == LIQUID){ //free-slip - const real alpha = 0.1; - const real epsilon = 0.001; - const real rhoij = (rhoi + rhoj)/2.; - const real3 velij = veli - velj; - const real vij_dr = dot(velij, rij)/(dot(rij, rij)+epsilon*h*h); - const real visc = -massj*alpha*c_f*h*vij_dr/rhoij; - F2 += visc*kernel_grad; - //} - } - force[groupIndex[i]] = make_real4(F1+F2, 0); - force[groupIndex[i]] += body_force; - } -} - -}//namspace fluidDynamics_ns - - -//calculate density -template -void fluidDynamics::calcDensity(real Dt, real h){ - int np = pd->getNumParticles(); - Kernel kernel; - // get a NeighborContainer - auto ni = nl->getNeighbourContainer(); - auto sortPos = nl->getPositionIterator(); - auto groupIndex = nl->getGroupIndexIterator(); - auto vel = pd->getVel(access::location::gpu, access::mode::readwrite).raw(); - //If mass is not allocated assume all masses are 1 - real *mass = nullptr; - if(pd->isMassAllocated()) - mass = pd->getMass(access::location::gpu, access::mode::read).raw(); - auto rho = pd->getRho(access::location::gpu, access::mode::readwrite).raw(); - auto rho0 = pd->getRho0(access::location::gpu, access::mode::readwrite).raw(); - auto pressure = pd->getPressure(access::location::gpu, access::mode::readwrite).raw(); - auto vol = pd->getVol(access::location::gpu, access::mode::readwrite).raw(); - - if(densityOpt==densityOption::RiemannDensity){ - fluidDynamics_ns::calcDensityRiemann_ker<<>>(ni, kernel, - sortPos, groupIndex, - np, box, - vel, rho, - mass, pressure, - h, Dt, c_f, rho0, vol); - }else if(densityOpt==densityOption::Continuity){ - fluidDynamics_ns::calcDensityArtificial_ker<<>>(ni, kernel, - sortPos, groupIndex, - np, box, - vel, rho, - mass, pressure, - h, Dt, c_f, rho0); - }else { - throw std::runtime_error("|fluidDynamics| \tdensityOpt is not valid!"); - } -} - -//calculate initial number density: sigma0 -void fluidDynamics::calcInitNumDensity(real h){ - int np = pd->getNumParticles(); - Kernel kernel; - // get a NeighborContainer - auto ni = nl->getNeighbourContainer(); - auto sortPos = nl->getPositionIterator(); - auto groupIndex = nl->getGroupIndexIterator(); - auto sigma0 = pd->getSigma0(access::location::gpu, access::mode::readwrite).raw(); - - fluidDynamics_ns::calcInitNumDensity_ker<<>>(ni, kernel, sortPos, - groupIndex, sigma0, - np, box, h, rho0_f); -} - -//density calculation using summation for free surface cases -void fluidDynamics::densitySumFreeSurface(real h){ - int np = pd->getNumParticles(); - Kernel kernel; - // get a NeighborContainer - auto ni = nl->getNeighbourContainer(); - auto sortPos = nl->getPositionIterator(); - auto groupIndex = nl->getGroupIndexIterator(); - auto sigma0 = pd->getSigma0(access::location::gpu, access::mode::readwrite).raw(); - auto density = pd->getRho(access::location::gpu, access::mode::readwrite).raw(); - auto rho0 = pd->getRho0(access::location::gpu, access::mode::readwrite).raw(); - - fluidDynamics_ns::densitySumFreeSurface_ker<<>>(ni, kernel, sortPos, - groupIndex, sigma0, - density, - np, box, - h, rho0); -} - -//density calculation using summation for gas phase -void fluidDynamics::densitySumLightPhase(real h){ - int np = pd->getNumParticles(); - Kernel kernel; - // get a NeighborContainer - auto ni = nl->getNeighbourContainer(); - auto sortPos = nl->getPositionIterator(); - auto groupIndex = nl->getGroupIndexIterator(); - auto sigma0 = pd->getSigma0(access::location::gpu, access::mode::readwrite).raw(); - auto density = pd->getRho(access::location::gpu, access::mode::readwrite).raw(); - auto rho0 = pd->getRho0(access::location::gpu, access::mode::readwrite).raw(); - auto pressure = pd->getPressure(access::location::gpu, access::mode::readwrite).raw(); - auto vol = pd->getVol(access::location::gpu, access::mode::readwrite).raw(); - auto mass = pd->getMass(access::location::gpu, access::mode::readwrite).raw(); - - fluidDynamics_ns::densitySumLightPhase_ker<<>>(ni, kernel, sortPos, - groupIndex, sigma0, - density, - np, box, - h, rho0, - pressure, c_f, - vol, mass); -} - -//calculate pressure for wall particles -void fluidDynamics::calcPressureBC(real h){ - int np = pd->getNumParticles(); - Kernel kernel; - // get a NeighborContainer - auto ni = nl->getNeighbourContainer(); - auto sortPos = nl->getPositionIterator(); - auto groupIndex = nl->getGroupIndexIterator(); - auto d_density = pd->getRho(access::location::gpu, access::mode::readwrite).raw(); - auto pressure = pd->getPressure(access::location::gpu, access::mode::readwrite).raw(); - auto rho0 = pd->getRho0(access::location::gpu, access::mode::readwrite).raw(); - - fluidDynamics_ns::calcPressureBC_ker<<>>(ni, kernel, sortPos, - groupIndex, np, box, - d_density, pressure, - h, c_f, rho0, bodyForce); -} - -//calculate forces -template -void fluidDynamics::calcForce(real h, real physicalTime){ - int np = pd->getNumParticles(); - Kernel kernel; - // get a NeighborContainer - auto ni = nl->getNeighbourContainer(); - auto sortPos = nl->getPositionIterator(); - auto groupIndex = nl->getGroupIndexIterator(); - auto vel = pd->getVel(access::location::gpu, access::mode::readwrite).raw(); - //If mass is not allocated assume all masses are 1 - real *d_mass = nullptr; - if(pd->isMassAllocated()) - d_mass = pd->getMass(access::location::gpu, access::mode::read).raw(); - auto rho = pd->getRho(access::location::gpu, access::mode::readwrite).raw(); - auto pressure = pd->getPressure(access::location::gpu, access::mode::readwrite).raw(); - auto force = pd->getForce(access::location::gpu, access::mode::readwrite).raw(); - auto vol = pd->getVol(access::location::gpu, access::mode::readwrite).raw(); -#ifdef _TRANSPORT_VELOCITY_ - real P_b = 5.0f*1.f*c_f*c_f; //the background pressure - auto vel_tv = pd->getVel_tv(access::location::gpu, access::mode::readwrite).raw(); - auto F_Pb = pd->getF_Pb(access::location::gpu, access::mode::readwrite).raw(); -#else - real P_b = 0.0f; - auto vel_tv = nullptr; - auto F_Pb = nullptr; -#endif - - if(forceOpt==forceOption::RiemannForce){ - fluidDynamics_ns::calcForceRiemann_ker<<>>(ni, kernel, sortPos, - groupIndex, np, box, - force, vel, - rho, d_mass, - pressure, - vel_tv, - F_Pb, P_b, - h, c_f, bodyForce, - physicalTime, - vol); - }else if(forceOpt==forceOption::Artificial){ - fluidDynamics_ns::calcForceArtificial_ker<<>>(ni, kernel, sortPos, - groupIndex, np, box, - force, vel, - rho, d_mass, - pressure, - h, c_f, bodyForce); - }else { - throw std::runtime_error("|fluidDynamics| \tforceOpt is not valid!"); - } -} - - -}//namespace gpu diff --git a/gpuSPHINXsys/src/fluidDynamics.cuh b/gpuSPHINXsys/src/fluidDynamics.cuh deleted file mode 100644 index 2afdcce409..0000000000 --- a/gpuSPHINXsys/src/fluidDynamics.cuh +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Massoud Rezavand 2019. - * Technical University of Munich - * gpuSPHinxsys - An SPH solver for CUDA enabled GPUs - * - * This module collects all the functions required for - * the fluid dynamics related calculations - */ - -#ifndef FLUIDDYNAMICS_CUH -#define FLUIDDYNAMICS_CUH - -#include"System.h" -#include"ParticleData.cuh" -#include -#include - -namespace gpu{ -class fluidDynamics{ - -private: - cudaStream_t stream; - - shared_ptr pd; - shared_ptr nl; - shared_ptr sys; - - real c_f, rho0_f; - real3 bodyForce; - Box box; - -public: - struct Parameters{ - cudaStream_t stream; - real c_f, rho0_f; - real3 bodyForce; - Box box; - }; - - fluidDynamics(shared_ptr pd, - shared_ptr nl, - shared_ptr sys, - Parameters par); - ~fluidDynamics(); - - enum densityOption{RiemannDensity, Continuity}; - enum forceOption{RiemannForce, Artificial}; - - template - void calcDensity(real Dt, real h); - void calcPressureBC(real h); - template - void calcForce(real h, real physicalTime); - void calcInitNumDensity(real h); - void densitySumFreeSurface(real h); - void densitySumLightPhase(real h); - -}; - -}//namespace gpu - -#include"fluidDynamics.cu" -#endif - diff --git a/gpuSPHINXsys/src/inOut.cu b/gpuSPHINXsys/src/inOut.cu deleted file mode 100644 index d16e46808a..0000000000 --- a/gpuSPHINXsys/src/inOut.cu +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Massoud Rezavand 2019. - * Technical University of Munich - * gpuSPHinxsys - An SPH solver for CUDA enabled GPUs - * - * This module writes the particle data on an external file - * for post-processing porpuses - * Two output formats are available, VTU as well as PLT - */ - -#include"inOut.cuh" -#include"Kernel.cuh" - -using Kernel = gpu::KernelFunction::Wendland_C4; - -namespace gpu{ - -inOut::inOut(shared_ptr pd, - shared_ptr nl, - Box box): - pd(pd), nl(nl){ - printf("|inOut| \tis called \n"); - if (fs::exists(output_folder) || fs::exists(observer_folder)){ - fs::remove_all(output_folder); - fs::remove_all(observer_folder); - } - if (!fs::exists(output_folder) || !fs::exists(observer_folder)){ - fs::create_directory(output_folder); - fs::create_directory(observer_folder); - printf("|inOut| \toutput folders created! \n"); - } -} - -inOut::~inOut(){ - printf("|inOut| \tcall ended! \n"); -} - -namespace inOut_ns{ - -//kernel to get the physical signal at the probe -template -__global__ void calcPressureSignal(NeighbourContainer ni, - Box box, - real h, - Kernel kernel, - real3 probe, - real* __restrict__ probeSignal, - real* __restrict__ mass, - real* __restrict__ rho, - real* __restrict__ p){ - real sum0 = real(); - real sum1 = real(); - auto it = ni.begin(probe); - - while(it){ - auto neigh = *it++; - const real4 posj = neigh.getPos(); - // include only Fluid neighboring particles - if(posj.w == WALL) continue; - const real3 rj = make_real3(neigh.getPos()); - const real rhoj = rho[neigh.getGroupIndex()]; - const real pj = p[neigh.getGroupIndex()]; - const real massj = mass[neigh.getGroupIndex()]; - const real3 rij = box.apply_pbc(probe-rj); - sum0 += kernel(rij, h, box.boxSize.z)*massj/rhoj; - sum1 += pj*kernel(rij, h, box.boxSize.z)*massj/rhoj; - } - *probeSignal = sum1/fmax(sum0, Eps); -} - -}//inOut_ns - - -template -inline void inOut::outputToFile(int outputCount){ - - const int np = pd->getNumParticles(); - const real4 *posType = pd->getPos(access::location::cpu, access::mode::read).raw(); - const real3 *vel = pd->getVel(access::location::cpu, access::mode::read).raw(); - const real *rho = pd->getRho(access::location::cpu, access::mode::read).raw(); - const real *press = pd->getPressure(access::location::cpu, access::mode::read).raw(); - - //seperate particles of different types - if(bodyOpt==Fluid){ - posTypeFluid.clear(); - velFluid.clear(); - rhoFluid.clear(); - pressFluid.clear(); - for (size_t i = 0; i < np; i++){ - if (posType[i].w==LIQUID){ - posTypeFluid.push_back(posType[i]); - velFluid.push_back(vel[i]); - rhoFluid.push_back(rho[i]); - pressFluid.push_back(press[i]); - } - } - posTypeBody = posTypeFluid; - velBody = velFluid; - rhoBody = rhoFluid; - pressBody = pressFluid; - bodyName = "Fluid_"; - npBody = posTypeBody.size(); - }else if(bodyOpt==Wall){ - posTypeWall.clear(); - velWall.clear(); - rhoWall.clear(); - pressWall.clear(); - for (size_t i = 0; i < np; i++){ - if (posType[i].w==WALL){ - posTypeWall.push_back(posType[i]); - velWall.push_back(vel[i]); - rhoWall.push_back(rho[i]); - pressWall.push_back(press[i]); - } - } - posTypeBody = posTypeWall; - velBody = velWall; - rhoBody = rhoWall; - pressBody = pressWall; - bodyName = "Wall_"; - npBody = posTypeBody.size(); - }else if(bodyOpt==thirdBody){ - posTypeThirdBody.clear(); - velThirdBody.clear(); - rhoThirdBody.clear(); - pressThirdBody.clear(); - for (size_t i = 0; i < np; i++){ - if (posType[i].w==THIRDBODY){ - posTypeThirdBody.push_back(posType[i]); - velThirdBody.push_back(vel[i]); - rhoThirdBody.push_back(rho[i]); - pressThirdBody.push_back(press[i]); - } - } - posTypeBody = posTypeThirdBody; - velBody = velThirdBody; - rhoBody = rhoThirdBody; - pressBody = pressThirdBody; - bodyName = "ThirdBody_"; - npBody = posTypeBody.size(); - }else{ - throw std::runtime_error("|inOut| \tBody option to output is not valid!"); - } - - // write in VTU format to use in Paraview - if(outputOpt==VTU) - { - std::string filefullpath = output_folder + "/" + bodyName + std::to_string(outputCount) + ".vtu"; - std::ofstream out(filefullpath.c_str(), std::ios::trunc); - - //beginning of the XML file - out << "\n"; - out << "\n"; - out << " \n"; - out << " \n"; - - //write position of particles - out << " \n"; - out << " \n"; - out << " "; - for (size_t i = 0; i < npBody; i++) { - out << posTypeBody[i].x << " " << posTypeBody[i].y << " " << posTypeBody[i].z << " "; - } - out << std::endl; - out << " \n"; - out << " \n"; - - //Particles data set - out << " \n"; - //wrtie density - out << " \n"; - out << " "; - for (size_t i = 0; i < npBody; i++) { - out << rhoBody[i] << " "; - } - out << std::endl; - out << " \n"; - - //wrtie type - out << " \n"; - out << " "; - for (size_t i = 0; i < npBody; i++) { - out << posTypeBody[i].w << " "; - } - out << std::endl; - out << " \n"; - - //wrtie id - out << " \n"; - out << " "; - for (size_t i = 0; i < npBody; i++) { - // out << id[i] << " "; - out << i << " "; - } - out << std::endl; - out << " \n"; - - //write pressure - out << " \n"; - out << " "; - for (size_t i = 0; i < npBody; i++) { - out << pressBody[i] << " "; - } - out << std::endl; - out << " \n"; - - //write velocity - out << " \n"; - out << " "; - for (size_t i = 0; i < npBody; i++) { - out << velBody[i].x << " " << velBody[i].y << " " << velBody[i].z << " "; - } - out << std::endl; - out << " \n"; - - //Particles data set ended - out << " \n"; - - //cells connectivity - out << " \n"; - out << " \n"; - out << " \n"; - out << " \n"; - out << " \n"; - out << " \n"; - out << " \n"; - out << " \n"; - - out << " \n"; - out << " \n"; - out << "\n"; - - out.close(); - } - - // write in PLT format to use in TecPlot - else if (outputOpt==PLT) { - std::string filefullpath = output_folder + "/" + bodyName + std::to_string(outputCount) + ".plt"; - std::ofstream out(filefullpath.c_str(), std::ios::trunc); - - out<<"VARIABLES = \"x\",\"y\",\"z\",\"type\",\"vx\",\"vy\",\"vz\",\"rho\",\"p\"\n"; - for (int i = 0; i < npBody; i++){ - out << posTypeBody[i].x << " " - << posTypeBody[i].y << " " - << posTypeBody[i].z << " " - << posTypeBody[i].w << " " - << velBody[i].x << " " << velBody[i].y << " " << velBody[i].z << " " - << rhoBody[i] << " " - << pressBody[i] << "\n"; - } - out.close(); - } - - else - { - throw std::runtime_error("|inOut| \tOutput format is not valid!"); - } -} - -//function to output the probe signal at a given probe point into a plt file -inline void inOut::probeSignalToFile(real h, real3 probe, std::string probeID, real physicalTime, int counter){ - - Kernel kernel; - - real *probeSignalTmp; - cudaMalloc(&probeSignalTmp, sizeof(real)); - - // get a NeighborContainer - auto ni = nl->getNeighbourContainer(); - - real *mass = nullptr; - if(pd->isMassAllocated()) - mass = pd->getMass(access::location::gpu, access::mode::read).raw(); - auto density = pd->getRho(access::location::gpu, access::mode::read).raw(); - auto pressure = pd->getPressure(access::location::gpu, access::mode::read).raw(); - - inOut_ns::calcPressureSignal<<<1, 1>>>(ni, box, h, kernel, probe, - probeSignalTmp, mass, density, pressure); - - real probeSignal = real(0.0); - CudaSafeCall(cudaMemcpy(&probeSignal, probeSignalTmp, sizeof(real), cudaMemcpyDeviceToHost)); - CudaSafeCall(cudaFree(probeSignalTmp)); - - std::string filefullpath = observer_folder + "/" + probeID + ".plt"; - std::ofstream out(filefullpath.c_str(), std::ios::app); - - if (counter == 1){ - out<<"VARIABLES=\"time\",\"probeSignal\"\n"; - } - out < -#include -#include -#include -#include -namespace fs = std::experimental::filesystem; - -namespace gpu{ -class inOut{ - -private: - - shared_ptr pd; - shared_ptr nl; - Box box; - - std::string output_folder = "./output_gpu"; - std::string observer_folder = "./observer_data"; - std::string bodyName; - size_t npBody; - - std::vector posTypeFluid, posTypeWall, posTypeThirdBody, posTypeBody; - std::vector velFluid, velWall, velThirdBody, velBody; - std::vector rhoFluid, rhoWall, rhoThirdBody, rhoBody, pressFluid, pressWall, pressThirdBody, pressBody; - -public: - - inOut(shared_ptr pd, shared_ptr nl, Box box); - ~inOut(); - - enum outputOption{VTU, PLT}; - enum bodyOption{Fluid, Wall, thirdBody}; - - template - void outputToFile(int outputCount); - - void probeSignalToFile(real h, real3 probe, std::string probeID, real physicalTime, int counter); - -}; - -}//namespace gpu - -#include"inOut.cu" -#endif - diff --git a/gpuSPHINXsys/src/include/nod/README.md b/gpuSPHINXsys/src/include/nod/README.md deleted file mode 100644 index 0cf74f1d4f..0000000000 --- a/gpuSPHINXsys/src/include/nod/README.md +++ /dev/null @@ -1,257 +0,0 @@ -# Nod -[![Build Status](https://travis-ci.org/fr00b0/nod.svg?branch=master)](https://travis-ci.org/fr00b0/nod) -[![GitHub tag](https://img.shields.io/github/tag/fr00b0/nod.svg?label=version)](https://github.com/fr00b0/nod/releases) - -Dependency free, header only signals and slot library implemented with C++11. - -## Usage - -### Simple usage -The following example creates a signal and then connects a lambda as a slot. - -```cpp -// Create a signal which accepts slots with no arguments and void return value. -nod::signal signal; -// Connect a lambda slot that writes "Hello, World!" to stdout -signal.connect([](){ - std::cout << "Hello, World!" << std::endl; - }); -// Call the slots -signal(); -``` - -### Connecting multiple slots -If multiple slots are connected to the same signal, all of the slots will be -called when the signal is invoked. The slots will be called in the same order -as they where connected. - -```cpp -void endline() { - std::cout << std::endl; -} - -// Create a signal -nod::signal signal; -// Connect a lambda that prints a message -signal.connect([](){ - std::cout << "Message without endline!"; - }); -// Connect a function that prints a endline -signal.connect(endline); - -// Call the slots -signal(); -``` - -#### Slot type -The signal types in the library support connection of the same types that is -supported by `std::function`. - -### Slot arguments -When a signal calls it's connected slots, any arguments passed to the signal -are propagated to the slots. To make this work, we do need to specify the -signature of the signal to accept the arguments. - -```cpp -void print_sum( int x, int y ) { - std::cout << x << "+" << y << "=" << (x+y) << std::endl; -} -void print_product( int x, int y ) { - std::cout << x << "*" << y << "=" << (x*y) << std::endl; -} - - -// We create a signal with two integer arguments. -nod::signal signal; -// Let's connect our slot -signal.connect( print_sum ); -signal.connect( print_product ); - -// Call the slots -signal(10, 15); -signal(-5, 7); - -``` - -### Disconnecting slots -There are many circumstances where the programmer needs to diconnect a slot that -no longer want to recieve events from the signal. This can be really important -if the lifetime of the slots are shorter than the lifetime of the signal. That -could cause the signal to call slots that have been destroyed but not -disconnected, leading to undefined behaviour and probably segmentation faults. - -When a slot is connected, the return value from the `connect` method returns -an instance of the class `nod::connection`, that can be used to disconnect -that slot. - -```cpp -// Let's create a signal -nod::signal signal; -// Connect a slot, and save the connection -nod::connection connection = signal.connect([](){ - std::cout << "I'm connected!" << std::endl; - }); -// Triggering the signal will call the slot -signal(); -// Now we disconnect the slot -connection.disconnect(); -// Triggering the signal will no longer call the slot -signal(); -``` - -### Scoped connections -To assist in disconnecting slots, one can use the class `nod::scoped_connection` -to capture a slot connection. A scoped connection will automatically disconnect -the slot when the connection object goes out of scope. - -```cpp -// We create a signal -nod::signal signal; -// Let's use a scope to control lifetime -{ - // Let's save the connection in a scoped_connection - nod::scoped_connection connection = - signal.connect([](){ - std::cout << "This message should only be emitted once!" << std::endl; - }); - // If we trigger the signal, the slot will be called - signal(); -} // Our scoped connection is destructed, and disconnects the slot -// Triggering the signal now will not call the slot -signal(); -``` - -### Slot return values - -#### Accumulation of return values -It is possible for slots to have a return value. The return values can be -returned from the signal using a *accumulator*, which is a function object that -acts as a proxy object that processes the slot return values. When triggering a -signal through a accumulator, the accumulator gets called for each slot return -value, does the desired accumulation and then return the result to the code -triggering the signal. The accumulator is designed to work in a similar way as -the STL numerical algorithm `std::accumulate`. - -```cpp -// We create a singal with slots that return a value -nod::signal signal; -// Then we connect some signals -signal.connect( std::plus{} ); -signal.connect( std::multiplies{} ); -signal.connect( std::minus{} ); -// Let's say we want to calculate the sum of all the slot return values -// when triggering the singal with the parameters 10 and 100. -// We do this by accumulating the return values with the initial value 0 -// and a plus function object, like so: -std::cout << "Sum: " << signal.accumulate(0, std::plus{})(10,100) << std::endl; -// Or accumulate by multiplying (this needs 1 as initial value): -std::cout << "Product: " << signal.accumulate(1, std::multiplies{})(10,100) << std::endl; -// If we instead want to build a vector with all the return values -// we can accumulate them this way (start with a empty vector and add each value): -auto vec = signal.accumulate( std::vector{}, []( std::vector result, int value ) { - result.push_back( value ); - return result; - })(10,100); - -std::cout << "Vector: "; -for( auto const& element : vec ) { - std::cout << element << " "; -} -std::cout << std::endl; -``` -#### Aggregation -As we can see from the previous example, we can use the `accumulate` method if -we want to aggregate all the return values of the slots. Doing the aggregation -that way is not very optimal. It is both a inefficient algorithm for doing -aggreagtion to a container, and it obscures the call site as the caller needs to -express the aggregation using the verb *accumulate*. To remedy these -shortcomings we can turn to the method `aggregate` instead. This is a template -method, taking the type of container to aggregate to as a template parameter. - -```cpp -// We create a singal -nod::signal signal; -// Let's connect some slots -signal.connect( std::plus{} ); -signal.connect( std::multiplies{} ); -signal.connect( std::minus{} ); -// We can now trigger the signal and aggregate the slot return values -auto vec = signal.aggregate>(10,100); - -std::cout << "Result: "; -for( auto const& element : vec ) { - std::cout << element << " "; -} -std::cout << std::endl; -``` - -## Thread safety -There are two types of signals in the library. The first is `nod::signal` -which is safe to use in a multi threaded environment. Multiple threads can read, -write, connect slots and disconnect slots simultaneously, and the signal will -provide the nessesary synchronization. When triggering a slignal, all the -registered slots will be called and executed by the thread that triggered the -signal. - -The second type of signal is `nod::unsafe_signal` which is **not** safe to -use in a multi threaded environment. No syncronization will be performed on the -internal state of the signal. Instances of the signal should theoretically be -safe to read from multiple thread simultaneously, as long as no thread is -writing to the same object at the same time. There can be a performance gain -involved in using the unsafe version of a signal, since no syncronization -primitives will be used. - -`nod::connection` and `nod::scoped_connection` are thread safe for reading from -multiple threads, as long as no thread is writing to the same object. Writing in -this context means calling any non const member function, including destructing -the object. If an object is being written by one thread, then all reads and -writes to that object from the same or other threads needs to be prevented. -This basically means that a connection is only allowed to be disconnected from -one thread, and you should not check connection status or reassign the -connection while it is being disconnected. - -## Building the tests -The test project uses [premake5](https://premake.github.io/download.html) to -generate make files or similiar. - -### Linux -To build and run the tests using gcc and gmake on linux, execute the following -from the test directory: -```bash -premake5 gmake -make -C build/gmake -bin/gmake/debug/nod_tests -``` - -### Visual Studio 2013 -To build and run the tests, execute the following from the test directory: - -```batchfile -REM Adjust paths to suite your environment -c:\path\to\premake\premake5.exe vs2013 -"c:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\vsvars32.bat" -msbuild /m build\vs2013\nod_tests.sln -bin\vs2013\debug\nod_tests.exe -``` - -## The MIT License (MIT) - -Copyright (c) 2015 Fredrik Berggren - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/gpuSPHINXsys/src/include/nod/nod.hpp b/gpuSPHINXsys/src/include/nod/nod.hpp deleted file mode 100644 index 72190094ad..0000000000 --- a/gpuSPHINXsys/src/include/nod/nod.hpp +++ /dev/null @@ -1,631 +0,0 @@ -#ifndef IG_NOD_INCLUDE_NOD_HPP -#define IG_NOD_INCLUDE_NOD_HPP - -#include // std::vector -#include // std::function -#include // std::mutex, std::lock_guard -#include // std::shared_ptr, std::weak_ptr -#include // std::find_if() -#include // assert() -#include // std::this_thread::yield() -#include // std::is_same -#include // std::back_inserter - -namespace nod { - // implementational details - namespace detail { - /// Interface for type erasure when disconnecting slots - struct disconnector { - virtual void operator()( std::size_t index ) const = 0; - }; - /// Deleter that doesn't delete - inline void no_delete(disconnector*){ - }; - } // namespace detail - - /// Base template for the signal class - template - class signal_type; - - - /// Connection class. - /// - /// This is used to be able to disconnect slots after they have been connected. - /// Used as return type for the connect method of the signals. - /// - /// Connections are default constructible. - /// Connections are not copy constructible or copy assignable. - /// Connections are move constructible and move assignable. - /// - class connection { - public: - /// Default constructor - connection() : - _index() - {} - - // Connection are not copy constructible or copy assignable - connection( connection const& ) = delete; - connection& operator=( connection const& ) = delete; - - /// Move constructor - /// @param other The instance to move from. - connection( connection&& other ) : - _weak_disconnector( std::move(other._weak_disconnector) ), - _index( other._index ) - {} - - /// Move assign operator. - /// @param other The instance to move from. - connection& operator=( connection&& other ) { - _weak_disconnector = std::move( other._weak_disconnector ); - _index = other._index; - return *this; - } - - /// @returns `true` if the connection is connected to a signal object, - /// and `false` otherwise. - bool connected() const { - return !_weak_disconnector.expired(); - } - - /// Disconnect the slot from the connection. - /// - /// If the connection represents a slot that is connected to a signal object, calling - /// this method will disconnect the slot from that object. The result of this operation - /// is that the slot will stop receiving calls when the signal is invoked. - void disconnect(); - - private: - /// The signal template is a friend of the connection, since it is the - /// only one allowed to create instances using the meaningful constructor. - template friend class signal_type; - - /// Create a connection. - /// @param shared_disconnector Disconnector instance that will be used to disconnect - /// the connection when the time comes. A weak pointer - /// to the disconnector will be held within the connection - /// object. - /// @param index The slot index of the connection. - connection( std::shared_ptr const& shared_disconnector, std::size_t index ) : - _weak_disconnector( shared_disconnector ), - _index( index ) - {} - - /// Weak pointer to the current disconnector functor. - std::weak_ptr _weak_disconnector; - /// Slot index of the connected slot. - std::size_t _index; - }; - - /// Scoped connection class. - /// - /// This type of connection is automatically disconnected when - /// the connection object is destructed. - /// - class scoped_connection - { - public: - /// Scoped are default constructible - scoped_connection() = default; - /// Scoped connections are not copy constructible - scoped_connection( scoped_connection const& ) = delete; - /// Scoped connections are not copy assingable - scoped_connection& operator=( scoped_connection const& ) = delete; - - /// Move constructor - scoped_connection( scoped_connection&& other ) : - _connection( std::move(other._connection) ) - {} - - /// Move assign operator. - /// @param other The instance to move from. - scoped_connection& operator=( scoped_connection&& other ) { - reset( std::move( other._connection ) ); - return *this; - } - - /// Construct a scoped connection from a connection object - /// @param connection The connection object to manage - scoped_connection( connection&& c ) : - _connection( std::forward(c) ) - {} - - /// destructor - ~scoped_connection() { - disconnect(); - } - - /// Assignment operator moving a new connection into the instance. - /// @note If the scoped_connection instance already contains a - /// connection, that connection will be disconnected as if - /// the scoped_connection was destroyed. - /// @param c New connection to manage - scoped_connection& operator=( connection&& c ) { - reset( std::forward(c) ); - return *this; - } - - /// Reset the underlying connection to another connection. - /// @note The connection currently managed by the scoped_connection - /// instance will be disconnected when resetting. - /// @param c New connection to manage - void reset( connection&& c = {} ) { - disconnect(); - _connection = std::move(c); - } - - /// Release the underlying connection, without disconnecting it. - /// @returns The newly released connection instance is returned. - connection release() { - connection c = std::move(_connection); - _connection = connection{}; - return c; - } - - /// - /// @returns `true` if the connection is connected to a signal object, - /// and `false` otherwise. - bool connected() const { - return _connection.connected(); - } - - /// Disconnect the slot from the connection. - /// - /// If the connection represents a slot that is connected to a signal object, calling - /// this method will disconnect the slot from that object. The result of this operation - /// is that the slot will stop receiving calls when the signal is invoked. - void disconnect() { - _connection.disconnect(); - } - - private: - /// Underlying connection object - connection _connection; - }; - - /// Policy for multi threaded use of signals. - /// - /// This policy provides mutex and lock types for use in - /// a multithreaded environment, where signals and slots - /// may exists in different threads. - /// - /// This policy is used in the `nod::signal` type provided - /// by the library. - struct multithread_policy - { - using mutex_type = std::mutex; - using mutex_lock_type = std::lock_guard; - /// Function that yields the current thread, allowing - /// the OS to reschedule. - static void yield_thread() { - std::this_thread::yield(); - } - }; - - /// Policy for single threaded use of signals. - /// - /// This policy provides dummy implementations for mutex - /// and lock types, resulting in that no synchronization - /// will take place. - /// - /// This policy is used in the `nod::unsafe_signal` type - /// provided by the library. - struct singlethread_policy - { - /// Dummy mutex type that doesn't do anything - struct mutex_type{}; - /// Dummy lock type, that doesn't do any locking. - struct mutex_lock_type - { - /// A lock type must be constructible from a - /// mutex type from the same thread policy. - explicit mutex_lock_type( mutex_type const& ) { - } - }; - /// Dummy implementation of thread yielding, that - /// doesn't do any actual yielding. - static void yield_thread() { - } - }; - - /// Signal accumulator class template. - /// - /// This acts sort of as a proxy for triggering a signal and - /// accumulating the slot return values. - /// - /// This class is not really intended to instantiate by client code. - /// Instances are aquired as return values of the method `accumulate()` - /// called on signals. - /// - /// @tparam S Type of signal. The signal_accumulator acts - /// as a type of proxy for a signal instance of - /// this type. - /// @tparam T Type of initial value of the accumulate algorithm. - /// This type must meet the requirements of `CopyAssignable` - /// and `CopyConstructible` - /// @tparam F Type of accumulation function. - /// @tparam A... Argument types of the underlying signal type. - /// - template - class signal_accumulator - { - public: - /// Result type when calling the accumulating function operator. - using result_type = typename std::result_of::type; - - /// Construct a signal_accumulator as a proxy to a given signal - // - /// @param signal Signal instance. - /// @param init Initial value of the accumulate algorithm. - /// @param func Binary operation function object that will be - /// applied to all slot return values. - /// The signature of the function should be - /// equivalent of the following: - /// `R func( T1 const& a, T2 const& b )` - /// - The signature does not need to have `const&`. - /// - The initial value, type `T`, must be implicitly - /// convertible to `R` - /// - The return type `R` must be implicitly convertible - /// to type `T1`. - /// - The type `R` must be `CopyAssignable`. - /// - The type `S::slot_type::result_type` (return type of - /// the signals slots) must be implicitly convertible to - /// type `T2`. - signal_accumulator( S const& signal, T init, F func ) : - _signal( signal ), - _init( init ), - _func( func ) - {} - - /// Function call operator. - /// - /// Calling this will trigger the underlying signal and accumulate - /// all of the connected slots return values with the current - /// initial value and accumulator function. - /// - /// When called, this will invoke the accumulator function will - /// be called for each return value of the slots. The semantics - /// are similar to the `std::accumulate` algorithm. - /// - /// @param args Arguments to propagate to the slots of the - /// underlying when triggering the signal. - result_type operator()( A const& ... args ) const { - return _signal.trigger_with_accumulator( _init, _func, args... ); - } - - private: - - /// Reference to the underlying signal to proxy. - S const& _signal; - /// Initial value of the accumulate algorithm. - T _init; - /// Accumulator function. - F _func; - - }; - - /// Signal template specialization. - /// - /// This is the main signal implementation, and it is used to - /// implement the observer pattern whithout the overhead - /// boilerplate code that typically comes with it. - /// - /// Any function or function object is considered a slot, and - /// can be connected to a signal instance, as long as the signature - /// of the slot matches the signature of the signal. - /// - /// @tparam P Threading policy for the signal. - /// A threading policy must provide two type definitions: - /// - P::mutex_type, this type will be used as a mutex - /// in the signal_type class template. - /// - P::mutex_lock_type, this type must implement a - /// constructor that takes a P::mutex_type as a parameter, - /// and it must have the semantics of a scoped mutex lock - /// like std::lock_guard, i.e. locking in the constructor - /// and unlocking in the destructor. - /// - /// @tparam R Return value type of the slots connected to the signal. - /// @tparam A... Argument types of the slots connected to the signal. - template - class signal_type - { - public: - /// signals are not copy constructible - signal_type( signal_type const& ) = delete; - /// signals are not copy assignable - signal_type& operator=( signal_type const& ) = delete; - - /// signals are default constructible - signal_type() : - _slot_count(0) - {} - - // Destruct the signal object. - ~signal_type() { - invalidate_disconnector(); - } - - /// Type that will be used to store the slots for this signal type. - using slot_type = std::function; - /// Type that is used for counting the slots connected to this signal. - using size_type = typename std::vector::size_type; - - - /// Connect a new slot to the signal. - /// - /// The connected slot will be called every time the signal - /// is triggered. - /// @param slot The slot to connect. This must be a callable with - /// the same signature as the signal itself. - /// @return A connection object is returned, and can be used to - /// disconnect the slot. - template - connection connect( T&& slot ) { - mutex_lock_type lock{ _mutex }; - _slots.push_back( std::forward(slot) ); - std::size_t index = _slots.size()-1; - if( _shared_disconnector == nullptr ) { - _disconnector = disconnector{ this }; - _shared_disconnector = std::shared_ptr{&_disconnector, detail::no_delete}; - } - ++_slot_count; - return connection{ _shared_disconnector, index }; - } - - /// Function call operator. - /// - /// Calling this is how the signal is triggered and the - /// connected slots are called. - /// - /// @note The slots will be called in the order they were - /// connected to the signal. - /// - /// @param args Arguments that will be propagated to the - /// connected slots when they are called. - void operator()( A const&... args ) const { - for( auto const& slot : copy_slots() ) { - if( slot ) { - slot( args... ); - } - } - } - - /// Construct a accumulator proxy object for the signal. - /// - /// The intended purpose of this function is to create a function - /// object that can be used to trigger the signal and accumulate - /// all the slot return values. - /// - /// The algorithm used to accumulate slot return values is similar - /// to `std::accumulate`. A given binary function is called for - /// each return value with the parameters consisting of the - /// return value of the accumulator function applied to the - /// previous slots return value, and the current slots return value. - /// A initial value must be provided for the first slot return type. - /// - /// @note This can only be used on signals that have slots with - /// non-void return types, since we can't accumulate void - /// values. - /// - /// @tparam T The type of the initial value given to the accumulator. - /// @tparam F The accumulator function type. - /// @param init Initial value given to the accumulator. - /// @param op Binary operator function object to apply by the accumulator. - /// The signature of the function should be - /// equivalent of the following: - /// `R func( T1 const& a, T2 const& b )` - /// - The signature does not need to have `const&`. - /// - The initial value, type `T`, must be implicitly - /// convertible to `R` - /// - The return type `R` must be implicitly convertible - /// to type `T1`. - /// - The type `R` must be `CopyAssignable`. - /// - The type `S::slot_type::result_type` (return type of - /// the signals slots) must be implicitly convertible to - /// type `T2`. - template - signal_accumulator accumulate( T init, F op ) const { - static_assert( std::is_same::value == false, "Unable to accumulate slot return values with 'void' as return type." ); - return { *this, init, op }; - } - - - /// Trigger the signal, calling the slots and aggregate all - /// the slot return values into a container. - /// - /// @tparam C The type of container. This type must be - /// `DefaultConstructible`, and usable with - /// `std::back_insert_iterator`. Additionally it - /// must be either copyable or moveable. - /// @param args The arguments to propagate to the slots. - template - C aggregate( A const&... args ) const { - static_assert( std::is_same::value == false, "Unable to aggregate slot return values with 'void' as return type." ); - C container; - auto iterator = std::back_inserter( container ); - for( auto const& slot : copy_slots() ) { - if( slot ) { - (*iterator) = slot( args... ); - } - } - return container; - } - - /// Count the number of slots connected to this signal - /// @returns The number of connected slots - size_type slot_count() const { - return _slot_count; - } - - /// Determine if the signal is empty, i.e. no slots are connected - /// to it. - /// @returns `true` is returned if the signal has no connected - /// slots, and `false` otherwise. - bool empty() const { - return slot_count() == 0; - } - - /// Disconnects all slots - /// @note This operation invalidates all scoped_connection objects - void disconnect_all_slots() { - mutex_lock_type lock{ _mutex }; - _slots.clear(); - _slot_count = 0; - invalidate_disconnector(); - } - - private: - template friend class signal_accumulator; - /// Thread policy currently in use - using thread_policy = P; - /// Type of mutex, provided by threading policy - using mutex_type = typename thread_policy::mutex_type; - /// Type of mutex lock, provided by threading policy - using mutex_lock_type = typename thread_policy::mutex_lock_type; - - /// Invalidate the internal disconnector object in a way - /// that is safe according to the current thread policy. - /// - /// This will effectively make all current connection objects to - /// to this signal incapable of disconnecting, since they keep a - /// weak pointer to the shared disconnector object. - void invalidate_disconnector() { - // If we are unlucky, some of the connected slots - // might be in the process of disconnecting from other threads. - // If this happens, we are risking to destruct the disconnector - // object managed by our shared pointer before they are done - // disconnecting. This would be bad. To solve this problem, we - // discard the shared pointer (that is pointing to the disconnector - // object within our own instance), but keep a weak pointer to that - // instance. We then stall the destruction until all other weak - // pointers have released their "lock" (indicated by the fact that - // we will get a nullptr when locking our weak pointer). - std::weak_ptr weak{_shared_disconnector}; - _shared_disconnector.reset(); - while( weak.lock() != nullptr ) { - // we just yield here, allowing the OS to reschedule. We do - // this until all threads has released the disconnector object. - thread_policy::yield_thread(); - } - } - - /// Retrieve a copy of the current slots - /// - /// It's useful and necessary to copy the slots so we don't need - /// to hold the lock while calling the slots. If we hold the lock - /// we prevent the called slots from modifying the slots vector. - /// This simple "double buffering" will allow slots to disconnect - /// themself or other slots and connect new slots. - std::vector copy_slots() const - { - mutex_lock_type lock{ _mutex }; - return _slots; - } - - /// Implementation of the signal accumulator function call - template - typename signal_accumulator::result_type trigger_with_accumulator( T value, F& func, A const&... args ) const { - for( auto const& slot : copy_slots() ) { - if( slot ) { - value = func( value, slot( args... ) ); - } - } - return value; - } - - /// Implementation of the disconnection operation. - /// - /// This is private, and only called by the connection - /// objects created when connecting slots to this signal. - /// @param index The slot index of the slot that should - /// be disconnected. - void disconnect( std::size_t index ) { - mutex_lock_type lock( _mutex ); - assert( _slots.size() > index ); - if( _slots[ index ] != nullptr ) { - --_slot_count; - } - _slots[ index ] = slot_type{}; - while( _slots.size()>0 && !_slots.back() ) { - _slots.pop_back(); - } - } - - /// Implementation of the shared disconnection state - /// used by all connection created by signal instances. - /// - /// This inherits the @ref detail::disconnector interface - /// for type erasure. - struct disconnector : - detail::disconnector - { - /// Default constructor, resulting in a no-op disconnector. - disconnector() : - _ptr(nullptr) - {} - - /// Create a disconnector that works with a given signal instance. - /// @param ptr Pointer to the signal instance that the disconnector - /// should work with. - disconnector( signal_type* ptr ) : - _ptr( ptr ) - {} - - /// Disconnect a given slot on the current signal instance. - /// @note If the instance is default constructed, or created - /// with `nullptr` as signal pointer this operation will - /// effectively be a no-op. - /// @param index The index of the slot to disconnect. - void operator()( std::size_t index ) const override { - if( _ptr ) { - _ptr->disconnect( index ); - } - } - - /// Pointer to the current signal. - signal_type* _ptr; - }; - - /// Mutex to synchronize access to the slot vector - mutable mutex_type _mutex; - /// Vector of all connected slots - std::vector _slots; - /// Number of connected slots - size_type _slot_count; - /// Disconnector operation, used for executing disconnection in a - /// type erased manner. - disconnector _disconnector; - /// Shared pointer to the disconnector. All connection objects has a - /// weak pointer to this pointer for performing disconnections. - std::shared_ptr _shared_disconnector; - }; - - // Implementation of the disconnect operation of the connection class - inline void connection::disconnect() { - auto ptr = _weak_disconnector.lock(); - if( ptr ) { - (*ptr)( _index ); - } - _weak_disconnector.reset(); - } - - /// Signal type that is safe to use in multithreaded environments, - /// where the signal and slots exists in different threads. - /// The multithreaded policy provides mutexes and locks to synchronize - /// access to the signals internals. - /// - /// This is the recommended signal type, even for single threaded - /// environments. - template using signal = signal_type; - - /// Signal type that is unsafe in multithreaded environments. - /// No synchronizations are provided to the signal_type for accessing - /// the internals. - /// - /// Only use this signal type if you are sure that your environment is - /// single threaded and performance is of importance. - template using unsafe_signal = signal_type; -} // namespace nod - -#endif // IG_NOD_INCLUDE_NOD_HPP diff --git a/gpuSPHINXsys/src/timeStepping.cu b/gpuSPHINXsys/src/timeStepping.cu deleted file mode 100644 index aef181e9be..0000000000 --- a/gpuSPHINXsys/src/timeStepping.cu +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Massoud Rezavand 2019. - * Technical University of Munich - * gpuSPHinxsys - An SPH solver for CUDA enabled GPUs - * - * This module integrates the particles dynamics using - * different time marching algorithms - */ - -#include"timeStepping.cuh" -#include"dtSizeCalc.cuh" -#include"fluidDynamics.cuh" -#include"inOut.cuh" -#include - - -namespace gpu{ - -timeStepping::timeStepping(shared_ptr pd, - shared_ptr nl, - shared_ptr sys, - timeStepping::Parameters par): - pd(pd), nl(nl), sys(sys), U_f(par.U_f), - box(par.box), h(par.h), c_f(par.c_f), probe1(par.probe1), - probe2(par.probe2), rho0_f(par.rho0_f), bodyForce(par.bodyForce){ - printf("|timeStepping| \tis called with c_f = %.1f, rho0_f = %.1f \n", c_f, rho0_f); - CudaSafeCall(cudaStreamCreate(&stream)); -} - -timeStepping::~timeStepping(){ - cudaStreamDestroy(stream); - printf("|timeStepping| \tcall ended! \n"); -} - -namespace timeStepping_ns{ -//Kernel for time stepping -template -__global__ void integration_ker(real4* __restrict__ pos, - real3* __restrict__ vel, - real4* __restrict__ force, - real3* __restrict__ F_Pb, - real3* __restrict__ vel_tv, - const int* __restrict__ groupIndex, - int np, - real dt){ - int i = blockIdx.x*blockDim.x+threadIdx.x; - if(i>=np) return; - - vel[groupIndex[i]] += make_real3(force[groupIndex[i]])*dt*real(0.5); - - //updat positions at the first half step - if(halfSteps==timeStepping::firstHalf){ -#ifndef _TRANSPORT_VELOCITY_ - real3 newPos = make_real3(pos[groupIndex[i]]) + vel[groupIndex[i]]*dt; - pos[groupIndex[i]] = make_real4(newPos, pos[groupIndex[i]].w); -#else - vel_tv[groupIndex[i]] = vel[groupIndex[i]] + F_Pb[groupIndex[i]]*dt*real(0.5); - real3 newPos = make_real3(pos[groupIndex[i]]) + vel_tv[groupIndex[i]]*dt; - pos[groupIndex[i]] = make_real4(newPos, pos[groupIndex[i]].w); - F_Pb[groupIndex[i]] = make_real3(0); -#endif - //Reset force - force[groupIndex[i]] = make_real4(0); - } -} - -}//timeStepping_ns - -//function for time integration -template -void timeStepping::integration(real currentDt){ - int np = pd->getNumParticles(); - int Nthreads=128; - int Nblocks=np/Nthreads + ((np%Nthreads)?1:0); - auto groupIndex = nl->getGroupIndexIterator(); - auto pos = pd->getPos(access::location::gpu, access::mode::readwrite); - auto vel = pd->getVel(access::location::gpu, access::mode::readwrite); - auto force = pd->getForce(access::location::gpu, access::mode::readwrite); -#ifdef _TRANSPORT_VELOCITY_ - auto F_Pb = pd->getF_Pb(access::location::gpu, access::mode::readwrite).raw(); - auto vel_tv = pd->getVel_tv(access::location::gpu, access::mode::readwrite).raw(); -#else - auto F_Pb = nullptr; - auto vel_tv = nullptr; -#endif - timeStepping_ns::integration_ker<<>>(pos.raw(), - vel.raw(), - force.raw(), - F_Pb, - vel_tv, - groupIndex, - np, - currentDt); -} - -//velocity verlet integration -//called from the main inerface to run the simulation -void timeStepping::velVerletIntg(){ - dtSizeCalc::Parameters parDT; - parDT.stream = stream; - auto dtSizeCalc_ptr = std::make_shared(pd, nl, sys, parDT); - - fluidDynamics::Parameters parFD; - parFD.stream = stream; - parFD.box = box; - parFD.rho0_f = rho0_f; - parFD.bodyForce = bodyForce; - parFD.c_f = c_f; - auto fluidDynamics_ptr = std::make_shared(pd, nl, sys, parFD); - - auto inOut_ptr = std::make_shared(pd, nl, box); - //the observer's coordinate - real3 probe1Point = make_real3(std::get<0>(probe1), - std::get<1>(probe1), - std::get<2>(probe1)); - real3 probe2Point = make_real3(std::get<0>(probe2), - std::get<1>(probe2), - std::get<2>(probe2)); - - //output the initial configuration - inOut_ptr->outputToFile(0); - inOut_ptr->outputToFile(0); - inOut_ptr->outputToFile(0); - - real rcut = Kernel::getCutOff(h); - //TODO this updateNeighbourList can be much improved - nl->updateNeighbourList(box, rcut, stream); - //calculate initial number density: sigma0 - fluidDynamics_ptr->calcInitNumDensity(h); - - auto t1 = std::chrono::high_resolution_clock::now(); - std::chrono::duration interval; - - //computation loop starts - while (physical_time < End_time){ - - nl->updateNeighbourList(box, rcut, stream); - - fluidDynamics_ptr->densitySumFreeSurface(h); - - integration(dt); - -// fluidDynamics_ptr->densitySumFreeSurface(h); - -// fluidDynamics_ptr->densitySumLightPhase(h); - - fluidDynamics_ptr->calcDensity(dt, h); - fluidDynamics_ptr->calcPressureBC(h); - fluidDynamics_ptr->calcForce(h, physical_time); - - integration(dt); - - dt = dtSizeCalc_ptr->calcDt(h, c_f); - - physical_time += dt; - - if (iter_counter % screen_interval == 0){ - printf("Step: %d \tTime: %0.3f \tdt: %f \n", iter_counter, physical_time, dt); - } - - auto t2 = std::chrono::high_resolution_clock::now(); - //write results into a file - if (output_counter < physical_time*output_interval){ - printf("|I/O| \tWriting output to disk ... file No. %d \n", output_counter+1); - //write the simulation results into a file - inOut_ptr->outputToFile(output_counter+1); - inOut_ptr->outputToFile(output_counter+1); - //write the proble signals for a givnen obsever point into a file - inOut_ptr->probeSignalToFile(h, probe1Point, std::get<3>(probe1), physical_time, output_counter+1); - inOut_ptr->probeSignalToFile(h, probe2Point, std::get<3>(probe2), physical_time, output_counter+1); - output_counter++; - } - auto t3 = std::chrono::high_resolution_clock::now(); - interval += t3 - t2; - - //resorting particles (slightly improves the performance) - if(iter_counter%500 == 0){ - pd->sortParticles(); - } - iter_counter++; - } - auto t4 = std::chrono::high_resolution_clock::now(); - std::chrono::duration tt = t4 - t1 - interval; - printf("Total wall clock time for computation: %.3f seconds \n", tt.count()); - printf("Total number of Iterations: %d \n", iter_counter); -} - -//Dual-Criteria time integration scheme -//called from the main inerface to run the simulation -void timeStepping::dualCriteriaIntg(){ - dtSizeCalc::Parameters parDT; - parDT.stream = stream; - auto dtSizeCalc_ptr = std::make_shared(pd, nl, sys, parDT); - - fluidDynamics::Parameters parFD; - parFD.stream = stream; - parFD.box = box; - parFD.rho0_f = rho0_f; - parFD.bodyForce = bodyForce; - parFD.c_f = c_f; - auto fluidDynamics_ptr = std::make_shared(pd, nl, sys, parFD); - - auto inOut_ptr = std::make_shared(pd, nl, box); - //output the initial configuration - inOut_ptr->outputToFile(0); - inOut_ptr->outputToFile(0); - - real rcut = Kernel::getCutOff(h); - //TODO this updateNeighbourList can be much improved - nl->updateNeighbourList(box, rcut, stream); - //calculate initial number density: sigma0 - fluidDynamics_ptr->calcInitNumDensity(h); - - //computation loop starts - while (physical_time < End_time){ - - nl->updateNeighbourList(box, rcut, stream); - - Dt = dtSizeCalc_ptr->calcDtAdv(h, U_f); - fluidDynamics_ptr->densitySumFreeSurface(h); - - real relaxation_time = 0.0; - while (relaxation_time < Dt){ - - - integration(dt); - - fluidDynamics_ptr->calcDensity(dt, h); - fluidDynamics_ptr->calcPressureBC(h); - fluidDynamics_ptr->calcForce(h, physical_time); - - integration(dt); - - dt = dtSizeCalc_ptr->calcDtAcs(h, c_f); - - relaxation_time += dt; - physical_time += dt; - } - - if (iter_counter % screen_interval == 0){ - printf("Step: %d \tTime: %0.3f \tDt: %f \tdt: %f \n", iter_counter, physical_time, Dt, dt); - } - - //write results to a file - if (output_counter < physical_time*output_interval){ - printf("|I/O| \tWriting output to disk ... file No. %d \n", output_counter+1); - inOut_ptr->outputToFile(output_counter+1); - output_counter++; - } - - //resorting particles - if(iter_counter%500 == 0){ - pd->sortParticles(); - } - iter_counter++; - } -} - -}//namspace gpu diff --git a/gpuSPHINXsys/src/timeStepping.cuh b/gpuSPHINXsys/src/timeStepping.cuh deleted file mode 100644 index 06c4ae81cf..0000000000 --- a/gpuSPHINXsys/src/timeStepping.cuh +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Massoud Rezavand 2019. - * Technical University of Munich - * gpuSPHinxsys - An SPH solver for CUDA enabled GPUs - * - * This module integrates the particles dynamics using - * different time marching algorithms - */ - -#ifndef TIMESTEPPING_CUH -#define TIMESTEPPING_CUH - -#include"System.h" -#include"ParticleData.cuh" -#include"CellList.cuh" -#include - -namespace gpu{ -class timeStepping{ - -private: - shared_ptr pd; - shared_ptr nl; - shared_ptr sys; - - real U_f, h, c_f, rho0_f; - real3 bodyForce; - std::tuple probe1, probe2; - Box box; - - cudaStream_t stream; - - real dt = 1.e-5f; //acoustic time step size - real Dt = 1.e-5f; //advection time step size - real End_time = 10.0; //End point of the simulation - int screen_interval = 100; //screen info output interval - int output_interval = 5; //number of outputs per second - int output_counter = 0; //output counter - int iter_counter = 0; //number of iterations - real physical_time = 0.0; //physical simulation time - - template - void integration(real currentDt); - -public: - struct Parameters{ - real U_f, h, c_f, rho0_f; - real3 bodyForce; - std::tuple probe1, probe2; - Box box; - }; - - enum updateOption{firstHalf, secondHalf}; - - timeStepping(shared_ptr pd, - shared_ptr nl, - shared_ptr sys, - Parameters par); - - ~timeStepping(); - - void velVerletIntg(); - void dualCriteriaIntg(); - - /// TODO: this funcs will be needed when no pd, via cudaMemcpy - /*templatevoid outputToFile(InOut InOut_ptr, int outputOpt, int outputCount);*/ - -}; - -}//namespace gpu - -#include"timeStepping.cu" -#endif - diff --git a/gpuSPHINXsys/src/vector.cuh b/gpuSPHINXsys/src/vector.cuh deleted file mode 100644 index 5e50085b90..0000000000 --- a/gpuSPHINXsys/src/vector.cuh +++ /dev/null @@ -1,926 +0,0 @@ -#ifndef VECTOR_OVERLOADS_H -#define VECTOR_OVERLOADS_H -//Include built in ones -#include "cuda_runtime.h" - -#include -#include"defines.h" - -typedef unsigned short ushort; -typedef unsigned int uint; -typedef unsigned long long int ullint; - -#define VECATTR inline __host__ __device__ - -/////////////////////FLOAT2/////////////////////////////// - -VECATTR int2 make_int2(float2 a){return make_int2((int)a.x, (int)a.y);} -VECATTR float2 make_float2(float a){return make_float2(a, a);} - -VECATTR float2 make_float2(int2 a){return make_float2(a.x, a.y);} -VECATTR float2 make_float2(float2 a){return make_float2(a.x, a.y);} - -VECATTR float2 operator +(const float2 &a, const float2 &b){return make_float2( - a.x + b.x, - a.y + b.y); -} -VECATTR void operator +=(float2 &a, const float2 &b){ - a.x += b.x; - a.y += b.y; -} -VECATTR float2 operator +(const float2 &a, const float &b){return make_float2( - a.x + b, - a.y + b); -} -VECATTR float2 operator +(const float &b, const float2 &a){return a+b;} -VECATTR void operator +=(float2 &a, const float &b){ - a.x += b; - a.y += b; -} - -VECATTR float2 operator -(const float2 &a, const float2 &b){return make_float2( - a.x - b.x, - a.y - b.y); -} - -VECATTR void operator -=(float2 &a, const float2 &b){ - a.x -= b.x; - a.y -= b.y; -} - -VECATTR float2 operator -(const float2 &a, const float &b){return make_float2( - a.x - b, - a.y - b); -} - -VECATTR float2 operator -(const float &b, const float2 &a){return make_float2( - b-a.x, - b-a.y); -} -VECATTR void operator -=(float2 &a, const float &b){ - a.x -= b; - a.y -= b; -} -VECATTR float2 operator *(const float2 &a, const float2 &b){ - return make_float2(a.x * b.x, - a.y * b.y); -} -VECATTR void operator *=(float2 &a, const float2 &b){ - a.x *= b.x; - a.y *= b.y; -} -VECATTR float2 operator *(const float2 &a, const float &b){ - return make_float2(a.x * b, - a.y * b); -} -VECATTR float2 operator *(const float &b, const float2 &a){ - return make_float2(a.x * b, - a.y * b); -} -VECATTR void operator *=(float2 &a, const float &b){ - a.x *= b; - a.y *= b; -} -VECATTR float2 operator /(const float2 &a, const float2 &b){ - return make_float2(a.x / b.x, - a.y / b.y); -} -VECATTR void operator /=(float2 &a, const float2 &b){ - a.x /= b.x; - a.y /= b.y; -} -VECATTR float2 operator /(const float2 &a, const float &b){ - return (1.0f/b)*a; -} -VECATTR float2 operator /(const float &b, const float2 &a){ - return make_float2(b / a.x, - b / a.y); -} -VECATTR void operator /=(float2 &a, const float &b){ - a *= 1.0f/b; -} - - - -/////////////////////FLOAT3/////////////////////////////// - -VECATTR int3 make_int3(float3 a){return make_int3((int)a.x, (int)a.y, (int)a.z);} -VECATTR float3 make_float3(float a){return make_float3(a, a, a);} - -VECATTR float3 make_float3(int3 a){return make_float3(a.x, a.y, a.z);} -VECATTR float3 make_float3(float3 a){return make_float3(a.x, a.y, a.z);} -VECATTR float3 make_float3(float4 a){return make_float3(a.x, a.y, a.z);} - -VECATTR float3 operator +(const float3 &a, const float3 &b){return make_float3( - a.x + b.x, - a.y + b.y, - a.z + b.z); -} -VECATTR void operator +=(float3 &a, const float3 &b){ - a.x += b.x; - a.y += b.y; - a.z += b.z; -} -VECATTR float3 operator +(const float3 &a, const float &b){return make_float3( - a.x + b, - a.y + b, - a.z + b); -} -VECATTR float3 operator +(const float &b, const float3 &a){return a+b;} -VECATTR void operator +=(float3 &a, const float &b){ - a.x += b; - a.y += b; - a.z += b; -} - -VECATTR float3 operator -(const float3 &a, const float3 &b){return make_float3( - a.x - b.x, - a.y - b.y, - a.z - b.z); -} - -VECATTR void operator -=(float3 &a, const float3 &b){ - a.x -= b.x; - a.y -= b.y; - a.z -= b.z; -} - -VECATTR float3 operator -(const float3 &a, const float &b){return make_float3( - a.x - b, - a.y - b, - a.z - b); -} - -VECATTR float3 operator -(const float &b, const float3 &a){return make_float3( - b-a.x, - b-a.y, - b-a.z); -} -VECATTR void operator -=(float3 &a, const float &b){ - a.x -= b; - a.y -= b; - a.z -= b; -} -VECATTR float3 operator *(const float3 &a, const float3 &b){ - return make_float3( - a.x * b.x, - a.y * b.y, - a.z * b.z - ); -} -VECATTR void operator *=(float3 &a, const float3 &b){ - a.x *= b.x; - a.y *= b.y; - a.z *= b.z; -} -VECATTR float3 operator *(const float3 &a, const float &b){ - return make_float3( - a.x * b, - a.y * b, - a.z * b - ); -} -VECATTR float3 operator *(const float &b, const float3 &a){ - return make_float3( - a.x * b, - a.y * b, - a.z * b - ); -} -VECATTR void operator *=(float3 &a, const float &b){ - a.x *= b; - a.y *= b; - a.z *= b; -} -VECATTR float3 operator /(const float3 &a, const float3 &b){ - return make_float3( - a.x / b.x, - a.y / b.y, - a.z / b.z - ); -} -VECATTR void operator /=(float3 &a, const float3 &b){ - a.x /= b.x; - a.y /= b.y; - a.z /= b.z; -} -VECATTR float3 operator /(const float3 &a, const float &b){ - return (1.0f/b)*a; -} -VECATTR float3 operator /(const float &b, const float3 &a){ - return make_float3( - b / a.x, - b / a.y, - b / a.z - ); -} -VECATTR void operator /=(float3 &a, const float &b){ - a *= 1.0f/b; -} - - -VECATTR float3 floorf(const float3 &a){return make_float3(floorf(a.x), floorf(a.y), floorf(a.z));} - -/////////////////////FLOAT4/////////////////////////////// - - -VECATTR float4 make_float4(float a){return make_float4(a,a,a,a);} - -VECATTR float4 make_float4(float3 a){return make_float4(a.x, a.y, a.z, 0);} -VECATTR float4 make_float4(float4 a){return make_float4(a.x, a.y, a.z, a.w);} - -VECATTR float4 operator +(const float4 &a, const float4 &b){return make_float4( - a.x + b.x, - a.y + b.y, - a.z + b.z, - a.w + b.w); -} -VECATTR void operator +=(float4 &a, const float4 &b){ - a.x += b.x; - a.y += b.y; - a.z += b.z; - a.w += b.w; -} -VECATTR float4 operator +(const float4 &a, const float &b){return make_float4( - a.x + b, - a.y + b, - a.z + b, - a.w + b); -} -VECATTR float4 operator +(const float &b, const float4 &a){return a+b;} -VECATTR void operator +=(float4 &a, const float &b){ - a.x += b; - a.y += b; - a.z += b; - a.w += b; -} - -VECATTR float4 operator -(const float4 &a, const float4 &b){return make_float4( - a.x - b.x, - a.y - b.y, - a.z - b.z, - a.w - b.w); -} - -VECATTR void operator -=(float4 &a, const float4 &b){ - a.x -= b.x; - a.y -= b.y; - a.z -= b.z; - a.w -= b.w; -} - -VECATTR float4 operator -(const float4 &a, const float &b){return make_float4( - a.x - b, - a.y - b, - a.z - b, - a.w - b); -} - -VECATTR float4 operator -(const float &b, const float4 &a){return make_float4( - b-a.x, - b-a.y, - b-a.z, - b-a.w); -} -VECATTR void operator -=(float4 &a, const float &b){ - a.x -= b; - a.y -= b; - a.z -= b; - a.w -= b; -} -VECATTR float4 operator *(const float4 &a, const float4 &b){ - return make_float4(a.x * b.x, - a.y * b.y, - a.z * b.z, - a.w * b.w); -} -VECATTR void operator *=(float4 &a, const float4 &b){ - a.x *= b.x; - a.y *= b.y; - a.z *= b.z; - a.w *= b.w; -} -VECATTR float4 operator *(const float4 &a, const float &b){ - return make_float4(a.x * b, - a.y * b, - a.z * b, - a.w * b); -} -VECATTR float4 operator *(const float &b, const float4 &a){ - return make_float4(a.x * b, - a.y * b, - a.z * b, - a.w * b); -} -VECATTR void operator *=(float4 &a, const float &b){ - a.x *= b; - a.y *= b; - a.z *= b; - a.w *= b; -} -VECATTR float4 operator /(const float4 &a, const float4 &b){ - return make_float4(a.x / b.x, - a.y / b.y, - a.z / b.z, - a.w / b.w); -} -VECATTR void operator /=(float4 &a, const float4 &b){ - a.x /= b.x; - a.y /= b.y; - a.z /= b.z; - a.w /= b.w; -} -VECATTR float4 operator /(const float4 &a, const float &b){ - return (1.0f/b)*a; -} -VECATTR float4 operator /(const float &b, const float4 &a){ - return make_float4(b / a.x, - b / a.y, - b / a.z, - b / a.w); -} -VECATTR void operator /=(float4 &a, const float &b){ - a *= 1.0f/b; -} - - -VECATTR float4 floorf(const float4 &a){ - return make_float4(floorf(a.x), floorf(a.y), floorf(a.z), floorf(a.w)); -} - -VECATTR float dot(float4 a, float4 b){return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w;} - - - -namespace gpu{ -/////////////////REAL4//////////////////////////////// - -VECATTR real4 make_real4(real x, real y, real z, real w){ - #ifdef SINGLE_PRECISION - return make_float4(x,y,z,w); - #else - return make_double4(x,y,z,w); - #endif -} - -VECATTR real4 make_real4(real s){return make_real4(s, s, s, s);} - VECATTR real4 make_real4(real3 a){ return make_real4(a.x, a.y, a.z, real(0.0));} -VECATTR real4 make_real4(real3 a, real w){ return make_real4(a.x, a.y, a.z, w);} - - VECATTR real4 make_real4(real2 a){ return make_real4(a.x, a.y, real(0.0), real(0.0));} -#ifdef SINGLE_PRECISION -VECATTR real4 make_real4(double3 a, real w){return make_real4(a.x, a.y, a.z, w);} -#else -VECATTR real4 make_real4(float3 a, real w){ return make_real4(a.x, a.y, a.z, w);} -#endif - -VECATTR real4 make_real4(int4 a){ return make_real4(real(a.x), real(a.y), real(a.z), real(a.w));} -VECATTR real4 make_real4(uint4 a){return make_real4(real(a.x), real(a.y), real(a.z), real(a.w));} - - -//////////////////REAL3/////////////////////////// - - -VECATTR real3 make_real3(real x, real y, real z){ -#ifdef SINGLE_PRECISION - return make_float3(x,y,z); -#else - return make_double3(x,y,z); -#endif -} - -VECATTR real3 make_real3(real s){ return make_real3(s, s, s);} -VECATTR real3 make_real3(real3 a){return make_real3(a.x, a.y, a.z);} - -#ifdef SINGLE_PRECISION -VECATTR real3 make_real3(double3 a){return make_real3(a.x, a.y, a.z);} -VECATTR real3 make_real3(double4 a){return make_real3(a.x, a.y, a.z);} -#else - template - VECATTR real3 make_real3(float2 a, T b){return make_real3(a.x, a.y, b);} -VECATTR real3 make_real3(float3 a){return make_real3(a.x, a.y, a.z);} -VECATTR real3 make_real3(float4 a){return make_real3(a.x, a.y, a.z);} - -#endif -VECATTR real3 make_real3(real4 a){ return make_real3(a.x, a.y, a.z);} - -VECATTR real3 make_real3(real2 a, real z){return make_real3(a.x, a.y, z);} -VECATTR real3 make_real3(int3 a){ return make_real3(real(a.x), real(a.y), real(a.z));} -VECATTR real3 make_real3(uint3 a){return make_real3(real(a.x), real(a.y), real(a.z));} - - - -//////////////////REAL2/////////////////////////// - - -VECATTR real2 make_real2(real x, real y){ -#ifdef SINGLE_PRECISION - return make_float2(x,y); -#else - return make_double2(x,y); -#endif -} -#ifdef SINGLE_PRECISION - VECATTR real2 make_real2(double2 a){return make_real2(a.x, a.y);} -#else - VECATTR real2 make_real2(float2 a){return make_real2(a.x, a.y);} -#endif - -VECATTR real2 make_real2(real s){ return make_real2(s, s);} -VECATTR real2 make_real2(real2 a){return make_real2(a.x, a.y);} -VECATTR real2 make_real2(real3 a){return make_real2(a.x, a.y);} -VECATTR real2 make_real2(real4 a){return make_real2(a.x, a.y);} -VECATTR real2 make_real2(int3 a){ return make_real2(real(a.x), real(a.y));} -VECATTR real2 make_real2(uint3 a){return make_real2(real(a.x), real(a.y));} - - VECATTR real dot(real2 a, real2 b){ return a.x*b.x + a.y*b.y;} - - -} -////////////////DOUBLE PRECISION////////////////////// -#ifdef SINGLE_PRECISION -VECATTR double3 make_double3(gpu::real4 a){return make_double3(a.x, a.y, a.z);} -#else -VECATTR double3 make_double3(gpu::real3 a){return make_double3(a.x, a.y, a.z);} -VECATTR double3 make_double3(gpu::real4 a){return make_double3(a.x, a.y, a.z);} -#endif -VECATTR float4 make_float4(double4 a){return make_float4(float(a.x), float(a.y), float(a.z), float(a.w));} - -VECATTR double4 make_double4(double s){ return make_double4(s, s, s, s);} -VECATTR double4 make_double4(double3 a){return make_double4(a.x, a.y, a.z, 0.0f);} -VECATTR double4 make_double4(double3 a, double w){return make_double4(a.x, a.y, a.z, w);} -VECATTR double4 make_double4(int4 a){return make_double4(double(a.x), double(a.y), double(a.z), double(a.w));} -VECATTR double4 make_double4(uint4 a){return make_double4(double(a.x), double(a.y), double(a.z), double(a.w));} -VECATTR double4 make_double4(float4 a){return make_double4(double(a.x), double(a.y), double(a.z), double(a.w));} - -//////DOUBLE4/////////////// -VECATTR double4 operator +(const double4 &a, const double4 &b){ - return make_double4(a.x + b.x, - a.y + b.y, - a.z + b.z, - a.w + b.w - ); -} -VECATTR void operator +=(double4 &a, const double4 &b){ - a.x += b.x; - a.y += b.y; - a.z += b.z; - a.w += b.w; -} -VECATTR double4 operator +(const double4 &a, const double &b){ - return make_double4( - a.x + b, - a.y + b, - a.z + b, - a.w + b - ); -} -VECATTR double4 operator +(const double &b, const double4 &a){ - return a+b; -} -VECATTR void operator +=(double4 &a, const double &b){ - a.x += b; - a.y += b; - a.z += b; - a.w += b; -} - -VECATTR double4 operator -(const double4 &a, const double4 &b){ - return make_double4( - a.x - b.x, - a.y - b.y, - a.z - b.z, - a.w - b.w - ); -} -VECATTR void operator -=(double4 &a, const double4 &b){ - a.x -= b.x; - a.y -= b.y; - a.z -= b.z; - a.w -= b.w; -} -VECATTR double4 operator -(const double4 &a, const double &b){ - return make_double4( - a.x - b, - a.y - b, - a.z - b, - a.w - b - ); -} -VECATTR double4 operator -(const double &b, const double4 &a){ - return make_double4( - b - a.x, - b - a.y, - b - a.z, - b - a.w - ); -} -VECATTR void operator -=(double4 &a, const double &b){ - a.x -= b; - a.y -= b; - a.z -= b; - a.w -= b; -} -VECATTR double4 operator *(const double4 &a, const double4 &b){ - return make_double4( - a.x * b.x, - a.y * b.y, - a.z * b.z, - a.w * b.w - ); -} -VECATTR void operator *=(double4 &a, const double4 &b){ - a.x *= b.x; - a.y *= b.y; - a.z *= b.z; - a.w *= b.w; -} -VECATTR double4 operator *(const double4 &a, const double &b){ - return make_double4( - a.x * b, - a.y * b, - a.z * b, - a.w * b - ); -} -VECATTR double4 operator *(const double &b, const double4 &a){ - return a*b; -} -VECATTR void operator *=(double4 &a, const double &b){ - a.x *= b; - a.y *= b; - a.z *= b; - a.w *= b; -} -VECATTR double4 operator /(const double4 &a, const double4 &b){ - return make_double4( - a.x / b.x, - a.y / b.y, - a.z / b.z, - a.w / b.w - ); -} -VECATTR void operator /=(double4 &a, const double4 &b){ - a.x /= b.x; - a.y /= b.y; - a.z /= b.z; - a.w /= b.w; -} -VECATTR double4 operator /(const double4 &a, const double &b){return (1.0/b)*a;} -VECATTR double4 operator /(const double &b, const double4 &a){ - return make_double4( - b / a.x, - b / a.y, - b / a.z, - b / a.w - ); -} -VECATTR void operator /=(double4 &a, const double &b){ - a *= 1.0/b; -} - -VECATTR double dot(double4 a, double4 b) -{ - return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; -} -VECATTR double length(double4 v) -{ - return sqrt(dot(v, v)); -} -VECATTR double4 normalize(double4 v) -{ - double invLen = 1.0/sqrt(dot(v, v)); - return v * invLen; -} -VECATTR double4 floorf(double4 v) -{ - return make_double4(floor(v.x), floor(v.y), floor(v.z), floor(v.w)); -} - -/////////////////////DOUBLE3/////////////////////////////// - -VECATTR int3 make_int3(double3 a){ - return make_int3((int)a.x, (int)a.y, (int)a.z); -} -VECATTR double3 make_double3(double a){ - return make_double3(a, a, a); -} - -VECATTR double3 make_double3(double2 xy, double z){ - return make_double3(xy.x, xy.y, z); -} - -VECATTR double3 make_double3(float2 xy, double z){ - return make_double3(xy.x, xy.y, z); -} -VECATTR double3 make_double3(double x, double2 yz){ - return make_double3(x, yz.x, yz.y); -} -#ifdef SINGLE_PRECISION -VECATTR double3 make_double3(double3 a){ - return a; -} -#endif - - -VECATTR double3 make_double3(int3 a){ - return make_double3(a.x, a.y, a.z); -} -VECATTR double3 make_double3(float3 a){ - return make_double3(a.x, a.y, a.z); -} - -VECATTR double3 operator +(const double3 &a, const double3 &b){ - return make_double3( - a.x + b.x, - a.y + b.y, - a.z + b.z - ); -} -VECATTR void operator +=(double3 &a, const double3 &b){ - a.x += b.x; - a.y += b.y; - a.z += b.z; -} -VECATTR double3 operator +(const double3 &a, const double &b){ - return make_double3( - a.x + b, - a.y + b, - a.z + b - ); -} -VECATTR double3 operator +(const double &b, const double3 &a){ - return a+b; -} -VECATTR void operator +=(double3 &a, const double &b){ - a.x += b; - a.y += b; - a.z += b; -} - -VECATTR double3 operator -(const double3 &a, const double3 &b){ - return make_double3( - a.x - b.x, - a.y - b.y, - a.z - b.z - ); -} -VECATTR void operator -=(double3 &a, const double3 &b){ - a.x -= b.x; - a.y -= b.y; - a.z -= b.z; -} -VECATTR double3 operator -(const double3 &a, const double &b){ - return make_double3( - a.x - b, - a.y - b, - a.z - b - ); -} -VECATTR double3 operator -(const double &b, const double3 &a){ - return make_double3( - b-a.x, - b-a.y, - b-a.z - ); -} -VECATTR void operator -=(double3 &a, const double &b){ - a.x -= b; - a.y -= b; - a.z -= b; -} -VECATTR double3 operator *(const double3 &a, const double3 &b){ - return make_double3( - a.x * b.x, - a.y * b.y, - a.z * b.z - ); -} -VECATTR void operator *=(double3 &a, const double3 &b){ - a.x *= b.x; - a.y *= b.y; - a.z *= b.z; -} -VECATTR double3 operator *(const double3 &a, const double &b){ - return make_double3( - a.x * b, - a.y * b, - a.z * b - ); -} -VECATTR double3 operator *(const double &b, const double3 &a){ - return a*b; -} -VECATTR void operator *=(double3 &a, const double &b){ - a.x *= b; - a.y *= b; - a.z *= b; -} -VECATTR double3 operator /(const double3 &a, const double3 &b){ - return make_double3( - a.x / b.x, - a.y / b.y, - a.z / b.z - ); -} -VECATTR void operator /=(double3 &a, const double3 &b){ - a.x /= b.x; - a.y /= b.y; - a.z /= b.z; -} -VECATTR double3 operator /(const double3 &a, const double &b){return (1.0/b)*a;} - -VECATTR double3 operator /(const double &b, const double3 &a){ - return make_double3( - b / a.x, - b / a.y, - b / a.z - ); -} -VECATTR void operator /=(double3 &a, const double &b){ - - a *= 1.0/b; - -} - -//DOUBLE2 - - -VECATTR double2 operator -(const double2 &a, const double2 &b){ - return make_double2( - a.x - b.x, - a.y - b.y - ); -} -VECATTR void operator -=(double2 &a, const double2 &b){ - a.x -= b.x; - a.y -= b.y; -} -VECATTR double2 operator -(const double2 &a, const double &b){ - return make_double2( - a.x - b, - a.y - b - ); -} -VECATTR double2 operator -(const double &b, const double2 &a){ - return make_double2( - b - a.x, - b - a.y - ); -} -VECATTR void operator -=(double2 &a, const double &b){a.x -= b; a.y -= b;} - - - - -VECATTR double2 operator +(const double2 &a, const double2 &b){ - return make_double2( - a.x + b.x, - a.y + b.y - ); -} -VECATTR void operator +=(double2 &a, const double2 &b){ - a.x += b.x; - a.y += b.y; -} -VECATTR double2 operator +(const double2 &a, const double &b){ - return make_double2( - a.x + b, - a.y + b - ); -} -VECATTR double2 operator +(const double &b, const double2 &a){ return a+b;} -VECATTR void operator +=(double2 &a, const double &b){a.x += b; a.y += b;} - - -VECATTR double2 operator *(const double2 &a, const double2 &b){ - return make_double2(a.x * b.x, a.y * b.y); -} -VECATTR void operator *=(double2 &a, const double2 &b){ - a.x *= b.x; - a.y *= b.y; -} -VECATTR double2 operator *(const double2 &a, const double &b){ - return make_double2(a.x * b, a.y * b); -} -VECATTR double2 operator *(const double &b, const double2 &a){ - return a*b; -} -VECATTR void operator *=(double2 &a, const double &b){ - a.x *= b; - a.y *= b; -} - - - -VECATTR double2 operator /(const double2 &a, const double2 &b){ - return make_double2(a.x / b.x, a.y / b.y); -} -VECATTR void operator /=(double2 &a, const double2 &b){ - a.x /= b.x; - a.y /= b.y; -} -VECATTR double2 operator /(const double2 &a, const double &b){ - return make_double2(a.x / b, a.y / b); -} -VECATTR double2 operator /(const double &b, const double2 &a){ - return make_double2(b/a.x, b/a.y); -} -VECATTR void operator /=(double2 &a, const double &b){ - a.x /= b; - a.y /= b; -} - - - -//////////////////////////// - -VECATTR double3 floorf(double3 v){return make_double3(floor(v.x), floor(v.y), floor(v.z));} - - - -VECATTR double dot(const double3 &a, const double3 &b){return a.x * b.x + a.y * b.y + a.z * b.z;} -VECATTR float dot(const float3 &a, const float3 &b){return a.x * b.x + a.y * b.y + a.z * b.z;} -VECATTR int dot(const int3 &a, const int3 &b){return a.x * b.x + a.y * b.y + a.z * b.z;} -VECATTR int dot(const int2 &a, const int2 &b){return a.x * b.x + a.y * b.y;} - - -VECATTR double length(double3 v){return sqrt(dot(v, v));} -VECATTR double3 normalize(double3 v) -{ - double invLen = 1.0/sqrt(dot(v, v)); - return v * invLen; -} - -VECATTR double3 cross(double3 a, double3 b){ - return make_double3(a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x); -} - - -VECATTR float3 sqrt(const float3 &a){ return {sqrt(a.x), sqrt(a.y), sqrt(a.z)};} -VECATTR double3 sqrt(const double3 &a){ return {sqrt(a.x), sqrt(a.y), sqrt(a.z)};} - - -////////////////////////////////////////////////////////// - - -/****************************************************************************************/ - - - -///////////INT2///////////////// -VECATTR int2 make_int2(int3 a){return make_int2(a.x, a.y);} - -///////////INT3///////////////// - -VECATTR int3 make_int3(int a){return make_int3(a,a,a);} -VECATTR int3 make_int3(int2 a, int b){return make_int3(a.x,a.y,b);} - -VECATTR int3 operator /(int3 a, int3 b){ - return make_int3( a.x/b.x, a.y/b.y, a.z/b.z); -} - -VECATTR int3 operator /(int3 a, int b){ - return make_int3( a.x/b, a.y/b, a.z/b); -} - -VECATTR int3 operator /(int a, int3 b){ - return make_int3( a/b.x, a/b.y, a/b.z); -} - -VECATTR int3 operator +(const int3 &a, const int3 &b){ - return make_int3(a.x + b.x, a.y + b.y, a.z + b.z); -} - -VECATTR int3 operator +(const int3 &a, const int &b){ - return make_int3(a.x + b, a.y + b, a.z + b); -} - -VECATTR int3 operator -(const int3 &a, const int3 &b){ - return make_int3(a.x - b.x, a.y - b.y, a.z - b.z); -} - -VECATTR void operator -=(int3 &a, const int3 &b){ - a.x -= b.x; a.y -= b.y; a.z -= b.z; -} - -VECATTR void operator +=(int3 &a, const int3 &b){ - a.x += b.x; a.y += b.y; a.z += b.z; -} - -VECATTR void operator /=(int3 &a, const int &b){ - a.x /= b; a.y /= b; a.z /= b; -} - -VECATTR int3 operator -(const int3 &a, const int &b){ - return make_int3(a.x - b, a.y - b, a.z - b); -} - -VECATTR int3 operator -(const int &b, const int3 &a){ - return make_int3(b - a.x, b - a.y, b - a.z); -} - -VECATTR int3 operator *(const int3 &a, const int &b){return make_int3(a.x*b, a.y*b, a.z*b);} -VECATTR int3 operator *(const int &b, const int3 &a){return a*b;} - -#endif From 5d9891cda966daad94a5efcf0c0bbce706a851f3 Mon Sep 17 00:00:00 2001 From: Xiangyu Hu Date: Fri, 30 Jul 2021 23:33:05 +0200 Subject: [PATCH 04/40] =?UTF-8?q?add=20volume=20page=20for=20two=20pub?= =?UTF-8?q?=C3=B6ications?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b45647b00b..8837191882 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Software Impacts, 6 (2020) 100033 4. Chi Zhang, Massoud Rezavand, Xiangyu Hu, "A multi-resolution SPH method for fluid-structure interactions", -Journal of Computational Physics, in press (2021) +Journal of Computational Physics, 2021(429) 110028 5. Chi Zhang, Yanji Wei, Frederic Dias, Xiangyu Hu, "An efficient fully Lagrangian solver for modeling wave interaction with oscillating wave energy converter", @@ -39,7 +39,11 @@ arXiv:2012.05323 6. Chi Zhang, Jianhang Wang, Massoud Rezavand, Dong Wu, Xiangyu Hu, "An integrative smoothed particle hydrodynamics framework for modeling cardiac function", - arXiv:2009.03759 + Computer Methods in Applied Mechanics and Engineering, 381, 113847, 2021 + + 7. Yujie Zhu, Chi Zhang, Yongchuan Yu, Xiangyu Hu, + “A CAD-compatible body-fitted particle generator for arbitrarily complex geometry and its application to wave-structure interaction”, + Journal of Hydrodynamics, 33(2), 195-206, 2021. ## Software Architecture From 7aedf5d4ef0f6079469d9470d50078cd7fcf8db6 Mon Sep 17 00:00:00 2001 From: BenceVirtonomy Date: Wed, 4 Aug 2021 16:57:59 +0000 Subject: [PATCH 05/40] Simbody artifacts instead of installation --- .github/workflows/ci.yml | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 39521d2683..dd4b544e9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,18 +37,11 @@ jobs: libtbb-dev \ libboost-all-dev \ liblapack-dev \ - wget - wget https://github.com/simbody/simbody/archive/Simbody-3.7.tar.gz - tar xvzf Simbody-3.7.tar.gz - rm Simbody-3.7.tar.gz - mkdir ./simbody-build + wget \ + unzip + wget "https://virtonomyplatformdev.blob.core.windows.net/simulation/simbody_lib/simbody.zip?sv=2019-12-12&st=2021-08-03T14%3A03%3A58Z&se=2022-08-04T14%3A03%3A00Z&sr=b&sp=r&sig=j4pjSwzRMV3OzRtKeYUv62zutZ3Td3M8khRxd3lsEYs%3D" + unzip "simbody.zip?sv=2019-12-12&st=2021-08-03T14:03:58Z&se=2022-08-04T14:03:00Z&sr=b&sp=r&sig=j4pjSwzRMV3OzRtKeYUv62zutZ3Td3M8khRxd3lsEYs=" export SIMBODY_HOME=/home/runner/simbody - mkdir $SIMBODY_HOME - cd ./simbody-build - cmake ../simbody-Simbody-3.7 -DCMAKE_INSTALL_PREFIX=$SIMBODY_HOME -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=off -DBUILD_EXAMPLES=off -DBUILD_VISUALIZER=off -DBUILD_STATIC_LIBRARIES=on - make -j$(nproc) - make -j$(nproc) install - cd .. export TBB_HOME=/usr/lib/x86_64-linux-gnu export BOOST_HOME=/usr/lib/x86_64-linux-gnu cd /usr/src/gtest From 222d207ab3cd634acdda6231940de7fc76436e4a Mon Sep 17 00:00:00 2001 From: BenceVirtonomy Date: Wed, 4 Aug 2021 17:20:04 +0000 Subject: [PATCH 06/40] simbody variable added to cmake command --- .github/workflows/ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd4b544e9e..43a856de09 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,9 +39,8 @@ jobs: liblapack-dev \ wget \ unzip - wget "https://virtonomyplatformdev.blob.core.windows.net/simulation/simbody_lib/simbody.zip?sv=2019-12-12&st=2021-08-03T14%3A03%3A58Z&se=2022-08-04T14%3A03%3A00Z&sr=b&sp=r&sig=j4pjSwzRMV3OzRtKeYUv62zutZ3Td3M8khRxd3lsEYs%3D" + cd /home/runner/ && wget "https://virtonomyplatformdev.blob.core.windows.net/simulation/simbody_lib/simbody.zip?sv=2019-12-12&st=2021-08-03T14%3A03%3A58Z&se=2022-08-04T14%3A03%3A00Z&sr=b&sp=r&sig=j4pjSwzRMV3OzRtKeYUv62zutZ3Td3M8khRxd3lsEYs%3D" unzip "simbody.zip?sv=2019-12-12&st=2021-08-03T14:03:58Z&se=2022-08-04T14:03:00Z&sr=b&sp=r&sig=j4pjSwzRMV3OzRtKeYUv62zutZ3Td3M8khRxd3lsEYs=" - export SIMBODY_HOME=/home/runner/simbody export TBB_HOME=/usr/lib/x86_64-linux-gnu export BOOST_HOME=/usr/lib/x86_64-linux-gnu cd /usr/src/gtest @@ -50,6 +49,6 @@ jobs: cd /home/runner/work/SPHinXsys/SPHinXsys mkdir build cd build - cmake .. + cmake .. -DSIMBODY_HOME=/home/simbody make -j$(nproc) ctest --output-on-failure From e5a18b2718691f47567bfe026706e63ca85e8d96 Mon Sep 17 00:00:00 2001 From: BenceVirtonomy Date: Wed, 4 Aug 2021 17:44:34 +0000 Subject: [PATCH 07/40] change path --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43a856de09..a6c0333495 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,6 @@ jobs: cd /home/runner/work/SPHinXsys/SPHinXsys mkdir build cd build - cmake .. -DSIMBODY_HOME=/home/simbody + cmake .. -DSIMBODY_HOME=/home/runner/simbody make -j$(nproc) ctest --output-on-failure From 40b7d7dbe1895717c1d08a215cabf37f7b7c3d7e Mon Sep 17 00:00:00 2001 From: BenceVirtonomy Date: Thu, 5 Aug 2021 12:08:57 +0000 Subject: [PATCH 08/40] conflict resolved --- .../solid_structural_simulation_class.cpp | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp b/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp index 7232ed28e9..7db88200a4 100644 --- a/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp +++ b/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp @@ -428,7 +428,6 @@ void StructuralSimulation::initializeTranslateSolidBody() Vecd translation = get<3>(translation_solid_body_tuple_[i]); BodyPartByParticleTriMesh* bp = new BodyPartByParticleTriMesh( solid_body_list_[body_index]->getImportedModel(), imported_stl_list_[body_index], &body_mesh_list_[body_index]); -<<<<<<< HEAD translation_solid_body_.emplace_back(make_shared( solid_body_list_[body_index]->getImportedModel(), bp, start_time, end_time, translation)); @@ -448,27 +447,6 @@ void StructuralSimulation::initializeTranslateSolidBodyPart() BodyPartByParticleTriMesh* bp = new BodyPartByParticleTriMesh( solid_body_list_[body_index]->getImportedModel(), imported_stl_list_[body_index], &body_mesh_list_[body_index]); -======= - - translation_solid_body_.emplace_back(make_shared( - solid_body_list_[body_index]->getImportedModel(), bp, start_time, end_time, translation)); - } -} - -void StructuralSimulation::initializeTranslateSolidBodyPart() -{ - translation_solid_body_part_ = {}; - for (size_t i = 0; i < translation_solid_body_part_tuple_.size(); i++) - { - int body_index = get<0>(translation_solid_body_part_tuple_[i]); - Real start_time = get<1>(translation_solid_body_part_tuple_[i]); - Real end_time = get<2>(translation_solid_body_part_tuple_[i]); - Vecd translation = get<3>(translation_solid_body_part_tuple_[i]); - BoundingBox bbox = get<4>(translation_solid_body_part_tuple_[i]); - BodyPartByParticleTriMesh* bp = new BodyPartByParticleTriMesh( - solid_body_list_[body_index]->getImportedModel(), imported_stl_list_[body_index], &body_mesh_list_[body_index]); - ->>>>>>> 536775cb7b75f26b92df3b143b32d2e91efff551 translation_solid_body_part_.emplace_back(make_shared( solid_body_list_[body_index]->getImportedModel(), bp, start_time, end_time, translation, bbox)); } @@ -689,25 +667,18 @@ void StructuralSimulation::runSimulationStep(Real &dt, Real &integration_time) executeConstrainSolidBodyRegion(); executePositionSolidBody(dt); executePositionScaleSolidBody(dt); -<<<<<<< HEAD - executeTranslateSolidBody(dt); // only one time -======= executeTranslateSolidBody(dt); // velocity based executeTranslateSolidBodyPart(dt); ->>>>>>> 536775cb7b75f26b92df3b143b32d2e91efff551 executeDamping(dt); executeConstrainSolidBodyRegion(); executePositionSolidBody(dt); executePositionScaleSolidBody(dt); -<<<<<<< HEAD -======= executeTranslateSolidBody(dt); // velocity based executeTranslateSolidBodyPart(dt); ->>>>>>> 536775cb7b75f26b92df3b143b32d2e91efff551 executeStressRelaxationSecondHalf(dt); From ff6e31308ef2238a1ab97f04e3c545cb50157b0f Mon Sep 17 00:00:00 2001 From: BenceVirtonomy Date: Thu, 5 Aug 2021 12:30:07 +0000 Subject: [PATCH 09/40] remove doubled test --- .../position_based_boundary_conditions.cpp | 60 ------------------- 1 file changed, 60 deletions(-) diff --git a/tests/unit_tests_src/for_3D_build/high_level_simulation_class/position_based_boundary_conditions/position_based_boundary_conditions.cpp b/tests/unit_tests_src/for_3D_build/high_level_simulation_class/position_based_boundary_conditions/position_based_boundary_conditions.cpp index af8a79e825..130fdc12c8 100644 --- a/tests/unit_tests_src/for_3D_build/high_level_simulation_class/position_based_boundary_conditions/position_based_boundary_conditions.cpp +++ b/tests/unit_tests_src/for_3D_build/high_level_simulation_class/position_based_boundary_conditions/position_based_boundary_conditions.cpp @@ -276,66 +276,6 @@ TEST(StructuralSimulation, TranslateSolidBodyPartTuple) } } -TEST(StructuralSimulation, TranslateSolidBodyPartTuple) -{ - Real scale_stl = 0.001 / 4; // diameter of 0.025 m - Real resolution_mass = 8.0; - Real poisson = 0.35; - Real Youngs_modulus = 1e4; - Real physical_viscosity = 200; - Real rho_0 = 1000; - Real end_time = 0.1; - - /** STL IMPORT PARAMETERS */ - std::string relative_input_path = "./input/"; //path definition for linux - std::vector imported_stl_list = { "ball_mass.stl" }; - std::vector translation_list = { Vec3d(0) }; - std::vector resolution_list = { resolution_mass}; - LinearElasticSolid material = LinearElasticSolid(rho_0, Youngs_modulus, poisson); - std::vector material_model_list = { material }; - - TriangleMeshShape ball_mesh("./input/ball_mass.stl", translation_list[0] * scale_stl, scale_stl); - BoundingBox bbox = ball_mesh.findBounds(); - Real z_limit = 0.75 * bbox.first[2] + 0.25 * bbox.second[2]; // only apply to the bottom 25% of the ball - bbox.second[2] = z_limit; - - StructuralSimulationInput input - { - relative_input_path, - imported_stl_list, - scale_stl, - translation_list, - resolution_list, - material_model_list, - physical_viscosity, - {} - }; - Vecd translation_vector = Vec3d(0.0, 0.0, 0.02); - input.translation_solid_body_part_tuple_ = { TranslateSolidBodyPartTuple(0, end_time * 0.124, end_time, translation_vector, bbox) }; - - //=================================================================================================// - TestStructuralSimulation sim (input); - sim.TestRunSimulation(end_time); - //=================================================================================================// - - StdLargeVec& pos_0 = sim.get_solid_body_list_()[0].get()->getElasticSolidParticles()->pos_0_; - StdLargeVec& pos_n = sim.get_solid_body_list_()[0].get()->getElasticSolidParticles()->pos_n_; - - for (size_t index = 0; index < pos_0.size(); index++) - { - if (pos_0[index][2] < z_limit) - { - Vec3d end_pos = pos_0[index] + translation_vector; - EXPECT_NEAR(pos_n[index][2], end_pos[2], end_pos.norm() * 0.05); - } - else - { - Real z_limit_end = z_limit + translation_vector[2] * 0.95; // z_limit + translation_vector[2] = 0.01375 is the actual limit, but some particle go below this level - EXPECT_GT(pos_n[index][2], z_limit_end); - } - } -} - int main(int argc, char* argv[]) { testing::InitGoogleTest(&argc, argv); From d34a86a5cb760e819579c30ea68bc9acf66771c1 Mon Sep 17 00:00:00 2001 From: alundilong Date: Thu, 19 Aug 2021 16:10:39 +0800 Subject: [PATCH 10/40] fix cannot find simbody debug lib --- cmake/FindSIMBODY.cmake | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/cmake/FindSIMBODY.cmake b/cmake/FindSIMBODY.cmake index 34d656c2b0..5227df76eb 100644 --- a/cmake/FindSIMBODY.cmake +++ b/cmake/FindSIMBODY.cmake @@ -93,11 +93,21 @@ if(NOT BUILD_WITH_SIMBODY) ) set(Simbody_LIBRARY_LIST SimTKsimbody SimTKmath SimTKcommon) - - find_library(Simbody_LIB_DIR_TEMP - NAMES ${Simbody_LIBRARY_LIST} - PATHS ${Simbody_ROOT_DIR}/lib ${Simbody_ROOT_DIR}/lib64 ${Simbody_ROOT_DIR}/lib/x86_64-linux-gnu - NO_DEFAULT_PATH) + set(Simbody_LIBRARY_LIST_D SimTKsimbody_d SimTKmath_d SimTKcommon_d) + set(Simbody_LIB_DIR_TEMP) + + # searching for lib per debug/release mode + IF(${CMAKE_BUILD_TYPE} MATCHES "Debug") + find_library(Simbody_LIB_DIR_TEMP + NAMES ${Simbody_LIBRARY_LIST_D} + PATHS ${Simbody_ROOT_DIR}/lib ${Simbody_ROOT_DIR}/lib64 + NO_DEFAULT_PATH) + ELSE() + find_library(Simbody_LIB_DIR_TEMP + NAMES ${Simbody_LIBRARY_LIST} + PATHS ${Simbody_ROOT_DIR}/lib ${Simbody_ROOT_DIR}/lib64 + NO_DEFAULT_PATH) + ENDIF() get_filename_component(Simbody_LIB_DIR ${Simbody_LIB_DIR_TEMP} DIRECTORY) @@ -197,4 +207,4 @@ if(NOT BUILD_WITH_SIMBODY) Simbody_DEBUG_LIBRARIES ) -endif(NOT BUILD_WITH_SIMBODY) \ No newline at end of file +endif(NOT BUILD_WITH_SIMBODY) From c68ffb5f0343b9ad49d2dbaf47a4fa4f9f1c304c Mon Sep 17 00:00:00 2001 From: alundilong Date: Sat, 21 Aug 2021 14:45:05 +0800 Subject: [PATCH 11/40] temporary fix for finding gtest.lib --- cmake/FindSIMBODY.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/FindSIMBODY.cmake b/cmake/FindSIMBODY.cmake index 5227df76eb..7a8807d635 100644 --- a/cmake/FindSIMBODY.cmake +++ b/cmake/FindSIMBODY.cmake @@ -166,6 +166,7 @@ if(NOT BUILD_WITH_SIMBODY) endif() ENDIF() + set(LIBS ${LIBS} gtest) if (LIBS) foreach(lapack_lib IN LISTS Simbody_LAPACK_LIBRARY_LIST) set(LIBS ${LIBS} "${lapack_lib}") From 3a74af16101837e10feb2283d7195c1c04e3e286 Mon Sep 17 00:00:00 2001 From: alundilong Date: Sat, 21 Aug 2021 16:40:24 +0800 Subject: [PATCH 12/40] add classes for loading mhd images (to be continued.) --- .../geometries/complex_shape_image_mesh.cpp | 163 +++++++ .../geometries/complex_shape_image_mesh.h | 90 ++++ .../geometries/complex_shape_mesh.h | 81 ++++ .../complex_shape_triangle_mesh.cpp | 178 ++++++++ .../geometries/complex_shape_triangle_mesh.h | 90 ++++ .../src/for_3D_build/geometries/geometry.cpp | 174 +------ .../src/for_3D_build/geometries/geometry.h | 45 +- .../geometries/image_mesh_shape.cpp | 432 ++++++++++++++++++ .../geometries/image_mesh_shape.h | 104 +++++ 9 files changed, 1181 insertions(+), 176 deletions(-) create mode 100644 SPHINXsys/src/for_3D_build/geometries/complex_shape_image_mesh.cpp create mode 100644 SPHINXsys/src/for_3D_build/geometries/complex_shape_image_mesh.h create mode 100644 SPHINXsys/src/for_3D_build/geometries/complex_shape_mesh.h create mode 100644 SPHINXsys/src/for_3D_build/geometries/complex_shape_triangle_mesh.cpp create mode 100644 SPHINXsys/src/for_3D_build/geometries/complex_shape_triangle_mesh.h create mode 100644 SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.cpp create mode 100644 SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h diff --git a/SPHINXsys/src/for_3D_build/geometries/complex_shape_image_mesh.cpp b/SPHINXsys/src/for_3D_build/geometries/complex_shape_image_mesh.cpp new file mode 100644 index 0000000000..b76ee5323a --- /dev/null +++ b/SPHINXsys/src/for_3D_build/geometries/complex_shape_image_mesh.cpp @@ -0,0 +1,163 @@ +#include "complex_shape_image_mesh.h" +#include "image_mesh_shape.h" + +namespace SPH +{ + //=================================================================================================// + bool ComplexShapeImageMesh::checkContain(const Vec3d& input_pnt, bool BOUNDARY_INCLUDED) + { + bool exist = false; + bool inside = false; + + for (auto& each_shape : image_mesh_shapes_) + { + ImageMeshShape* sp = each_shape.first; + ShapeBooleanOps operation_string = each_shape.second; + + switch (operation_string) + { + case ShapeBooleanOps::add: + { + inside = sp->checkContain(input_pnt); + exist = exist || inside; + break; + } + case ShapeBooleanOps::sub: + { + inside = sp->checkContain(input_pnt); + exist = exist && (!inside); + break; + } + default: + { + std::cout << "\n FAILURE: the boolean operation is not applicable!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + break; + } + } + } + return exist; + } + //=================================================================================================// + Vec3d ComplexShapeImageMesh::findClosestPoint(const Vec3d& input_pnt) + { + //a big positive number + Real large_number(Infinity); + Real dist_min = large_number; + Vec3d pnt_closest(0); + Vec3d pnt_found(0); + + for (auto& each_shape : image_mesh_shapes_) + { + ImageMeshShape* sp = each_shape.first; + pnt_found = sp->findClosestPoint(input_pnt); + Real dist = (input_pnt - pnt_found).norm(); + + if(dist <= dist_min) + { + dist_min = dist; + pnt_closest = pnt_found; + } + } + + return pnt_closest; + } + //=================================================================================================// + Real ComplexShapeImageMesh::findSignedDistance(const Vec3d& input_pnt) + { + //Real distance_to_surface = (input_pnt - findClosestPoint(input_pnt)).norm(); + return image_mesh_shapes_[0].first->findValueAtPoint(input_pnt); + } + //=================================================================================================// + Vec3d ComplexShapeImageMesh::findNormalDirection(const Vec3d& input_pnt) + { + Vecd direction_to_surface = image_mesh_shapes_[0].first->findNormalAtPoint(input_pnt); + bool is_contain = image_mesh_shapes_[0].first->checkContain(input_pnt); + return is_contain ? direction_to_surface : -1.0 * direction_to_surface; + } + //=================================================================================================// + void ComplexShapeImageMesh::addImageMeshShape(ImageMeshShape* image_mesh_shape, ShapeBooleanOps op) + { + std::pair shape_and_op(image_mesh_shape, op); + image_mesh_shapes_.push_back(shape_and_op); + } + //=================================================================================================// + void ComplexShapeImageMesh::addBrick(Vec3d halfsize, int resolution, Vec3d translation, Mat3d rotation, ShapeBooleanOps op) + { + ImageMeshShape* image_mesh_shape = new ImageMeshShape(halfsize, resolution, translation, rotation); + std::pair shape_and_op(image_mesh_shape, op); + image_mesh_shapes_.push_back(shape_and_op); + } + //=================================================================================================// + void ComplexShapeImageMesh::addSphere(Real radius, int resolution, Vec3d translation, Mat3d rotation, ShapeBooleanOps op) + { + ImageMeshShape* image_mesh_shape = new ImageMeshShape(radius, resolution, translation, rotation); + std::pair shape_and_op(image_mesh_shape, op); + image_mesh_shapes_.push_back(shape_and_op); + } + //=================================================================================================// + void ComplexShapeImageMesh::addCylinder(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation, Mat3d rotation, ShapeBooleanOps op) + { + /** Here SimTK::UnitVec3 give the direction of the cylinder, viz. SimTK::UnitVec3(0,0,1) create a cylinder in z-axis.*/ + ImageMeshShape* image_mesh_shape = new ImageMeshShape(axis, radius, halflength, resolution, translation, rotation); + std::pair shape_and_op(image_mesh_shape, op); + image_mesh_shapes_.push_back(shape_and_op); + } + //=================================================================================================// + void ComplexShapeImageMesh::addComplexShapeImageMesh(ComplexShapeImageMesh* complex_shape_image_mesh, ShapeBooleanOps op) + { + switch (op) + { + case ShapeBooleanOps::add: + { + for (auto& shape_and_op : complex_shape_image_mesh->image_mesh_shapes_) + { + image_mesh_shapes_.push_back(shape_and_op); + } + break; + } + case ShapeBooleanOps::sub: + { + for (auto& shape_and_op : complex_shape_image_mesh->image_mesh_shapes_) + { + ImageMeshShape* sp = shape_and_op.first; + ShapeBooleanOps operation_string + = shape_and_op.second == ShapeBooleanOps::add ? ShapeBooleanOps::sub : ShapeBooleanOps::add; + std::pair substract_shape_and_op(sp, operation_string); + + image_mesh_shapes_.push_back(substract_shape_and_op); + } + break; + } + } + } + //=================================================================================================// + bool ComplexShapeImageMesh::checkNotFar(const Vec3d& input_pnt, Real threshold) + { + return checkContain(input_pnt) || checkNearSurface(input_pnt , threshold) ? true : false; + } + //=================================================================================================// + bool ComplexShapeImageMesh::checkNearSurface(const Vec3d& input_pnt, Real threshold) + { + return getMaxAbsoluteElement(input_pnt - findClosestPoint(input_pnt)) < threshold ? true : false; + } + //=================================================================================================// + BoundingBox ComplexShapeImageMesh::findBounds() + { + //initial reference values + Vec3d lower_bound = Vec3d(Infinity); + Vec3d upper_bound = Vec3d(-Infinity); + + for (size_t i = 0; i < image_mesh_shapes_.size(); i++) + { + BoundingBox shape_bounds = image_mesh_shapes_[i].first->findBounds(); + for (int j = 0; j != 3; ++j) { + lower_bound[j] = SMIN(lower_bound[j], shape_bounds.first[j]); + upper_bound[j] = SMAX(upper_bound[j], shape_bounds.second[j]); + } + } + return BoundingBox(lower_bound, upper_bound); + } + //=================================================================================================// +} \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/geometries/complex_shape_image_mesh.h b/SPHINXsys/src/for_3D_build/geometries/complex_shape_image_mesh.h new file mode 100644 index 0000000000..49a1497025 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/geometries/complex_shape_image_mesh.h @@ -0,0 +1,90 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file complex_shape_image_mesh.h +* @brief x +* @details x +* x +* @author Yijin Mao +*/ + +#ifndef COMPLEX_SHAPE_IMAGE_MESH_H +#define COMPLEX_SHAPE_IMAGE_MESH_H + +#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING + +#include "base_geometry.h" +#include "SimTKcommon.h" +#include "SimTKmath.h" +#include "Simbody.h" +#include "simbody_middle.h" +#include "geometry.h" +#include "complex_shape_mesh.h" +//#include "image_mesh_shape.h" + +#include +#include +#include + +/** Macro for APPLE compilers*/ +#ifdef __APPLE__ +#include +namespace fs = boost::filesystem; +#else +#include +namespace fs = std::experimental::filesystem; +#endif + +namespace SPH +{ + class ImageMeshShape; + + class ComplexShapeImageMesh : public ComplexShapeMesh + { + public: + ComplexShapeImageMesh() : ComplexShapeMesh("ComplexShapeImageMesh") {}; + ComplexShapeImageMesh(std::string complex_shape_name) : ComplexShapeMesh(complex_shape_name) {}; + virtual ~ComplexShapeImageMesh() {}; + virtual BoundingBox findBounds() override; + + void addImageMeshShape(ImageMeshShape* image_mesh_shape, ShapeBooleanOps op); + void addComplexShapeImageMesh(ComplexShapeImageMesh* complex_shape_image_mesh, ShapeBooleanOps op); + virtual void addBrick(Vec3d halfsize, int resolution, Vec3d translation, Mat3d rotation, ShapeBooleanOps op); + virtual void addSphere(Real radius, int resolution, Vec3d translation, Mat3d rotation, ShapeBooleanOps op); + virtual void addCylinder(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation, Mat3d rotation, ShapeBooleanOps op); + + virtual bool checkContain(const Vec3d& input_pnt, bool BOUNDARY_INCLUDED = true); + virtual bool checkNotFar(const Vec3d& input_pnt, Real threshold); + virtual bool checkNearSurface(const Vec3d& input_pnt, Real threshold); + /** Signed distance is negative for point within the complex shape. */ + virtual Real findSignedDistance(const Vec3d& input_pnt); + /** Normal direction point toward outside of the complex shape. */ + virtual Vec3d findNormalDirection(const Vec3d& input_pnt); + virtual Vec3d findClosestPoint(const Vec3d& input_pnt); + protected: + /** shape container */ + std::vector> image_mesh_shapes_; + }; +} + +#endif //COMPLEX_SHAPE_IMAGE_MESH_H diff --git a/SPHINXsys/src/for_3D_build/geometries/complex_shape_mesh.h b/SPHINXsys/src/for_3D_build/geometries/complex_shape_mesh.h new file mode 100644 index 0000000000..8667d78916 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/geometries/complex_shape_mesh.h @@ -0,0 +1,81 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file complex_shape_mesh.h +* @brief x. +* @details x +* x. +* @author Yijin Mao +*/ + +#ifndef COMPLEX_SHAPE_MESH_H +#define COMPLEX_SHAPE_MESH_H + +#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING + +#include "SimTKcommon.h" +#include "SimTKmath.h" + +#include +#include +#include + +/** Macro for APPLE compilers*/ +#ifdef __APPLE__ +#include +namespace fs = boost::filesystem; +#else +#include +namespace fs = std::experimental::filesystem; +#endif + +namespace SPH +{ + class ComplexShapeMesh + { + public: + //constructor for load stl file from out side + ComplexShapeMesh(std::string name) : name_(name) {}; + virtual ~ComplexShapeMesh() {}; + virtual BoundingBox findBounds() = 0; + virtual bool checkContain(const Vec3d& input_pnt, bool BOUNDARY_INCLUDED = true) = 0; + virtual bool checkNotFar(const Vec3d& input_pnt, Real threshold) = 0; + virtual bool checkNearSurface(const Vec3d& input_pnt, Real threshold) = 0; + // virtual void addBrick(Vec3d halfsize, int resolution, Vec3d translation, ShapeBooleanOps op) = 0; + // virtual void addSphere(Real radius, int resolution, Vec3d translation, ShapeBooleanOps op) = 0; + // virtual void addCylinder(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation, ShapeBooleanOps op) = 0; + /** Signed distance is negative for point within the complex shape. */ + virtual Real findSignedDistance(const Vec3d& input_pnt) = 0; + /** Normal direction point toward outside of the complex shape. */ + virtual Vec3d findNormalDirection(const Vec3d& input_pnt) = 0; + + virtual Vec3d findClosestPoint(const Vec3d& input_pnt) = 0; + + std::string getName() { return name_; } + + protected: + std::string name_; + }; +} + +#endif //COMPLEX_SHAPE_MESH_H diff --git a/SPHINXsys/src/for_3D_build/geometries/complex_shape_triangle_mesh.cpp b/SPHINXsys/src/for_3D_build/geometries/complex_shape_triangle_mesh.cpp new file mode 100644 index 0000000000..c7a027738d --- /dev/null +++ b/SPHINXsys/src/for_3D_build/geometries/complex_shape_triangle_mesh.cpp @@ -0,0 +1,178 @@ +#include "complex_shape_triangle_mesh.h" +#include "geometry.h" + +namespace SPH +{ + //=================================================================================================// + bool ComplexShapeTriangleMesh::checkContain(const Vec3d& input_pnt, bool BOUNDARY_INCLUDED) + { + bool exist = false; + bool inside = false; + + for (auto& each_shape : triangle_mesh_shapes_) + { + TriangleMeshShape* sp = each_shape.first; + ShapeBooleanOps operation_string = each_shape.second; + + switch (operation_string) + { + case ShapeBooleanOps::add: + { + inside = sp->checkContain(input_pnt); + exist = exist || inside; + break; + } + case ShapeBooleanOps::sub: + { + inside = sp->checkContain(input_pnt); + exist = exist && (!inside); + break; + } + default: + { + std::cout << "\n FAILURE: the boolean operation is not applicable!" << std::endl; + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + exit(1); + break; + } + } + } + return exist; + } + //=================================================================================================// + Vec3d ComplexShapeTriangleMesh::findClosestPoint(const Vec3d& input_pnt) + { + //a big positive number + Real large_number(Infinity); + Real dist_min = large_number; + Vec3d pnt_closest(0); + Vec3d pnt_found(0); + + for (auto& each_shape : triangle_mesh_shapes_) + { + TriangleMeshShape* sp = each_shape.first; + pnt_found = sp->findClosestPoint(input_pnt); + Real dist = (input_pnt - pnt_found).norm(); + + if(dist <= dist_min) + { + dist_min = dist; + pnt_closest = pnt_found; + } + } + + return pnt_closest; + } + //=================================================================================================// + Real ComplexShapeTriangleMesh::findSignedDistance(const Vec3d& input_pnt) + { + Real distance_to_surface = (input_pnt - findClosestPoint(input_pnt)).norm(); + return checkContain(input_pnt) ? -distance_to_surface : distance_to_surface; + } + //=================================================================================================// + Vec3d ComplexShapeTriangleMesh::findNormalDirection(const Vec3d& input_pnt) + { + bool is_contain = checkContain(input_pnt); + Vecd displacement_to_surface = findClosestPoint(input_pnt) - input_pnt; + while (displacement_to_surface.norm() < Eps) { + Vecd jittered = input_pnt; //jittering + for (int l = 0; l != input_pnt.size(); ++l) + jittered[l] = input_pnt[l] + (((Real)rand() / (RAND_MAX)) - 0.5) * 100.0 * Eps; + if (checkContain(jittered) == is_contain) + displacement_to_surface = findClosestPoint(jittered) - jittered; + } + Vecd direction_to_surface = displacement_to_surface.normalize(); + return is_contain ? direction_to_surface : -1.0 * direction_to_surface; + } + //=================================================================================================// + void ComplexShapeTriangleMesh::addTriangleMeshShape(TriangleMeshShape* triangle_mesh_shape, ShapeBooleanOps op) + { + std::pair shape_and_op(triangle_mesh_shape, op); + triangle_mesh_shapes_.push_back(shape_and_op); + } + //=================================================================================================// + void ComplexShapeTriangleMesh::addBrick(Vec3d halfsize, int resolution, Vec3d translation, ShapeBooleanOps op) + { + TriangleMeshShape* triangle_mesh_shape = new TriangleMeshShape(halfsize, resolution, translation); + std::pair shape_and_op(triangle_mesh_shape, op); + triangle_mesh_shapes_.push_back(shape_and_op); + } + //=================================================================================================// + void ComplexShapeTriangleMesh::addSphere(Real radius, int resolution, Vec3d translation, ShapeBooleanOps op) + { + TriangleMeshShape* triangle_mesh_shape = new TriangleMeshShape(radius, resolution, translation); + std::pair shape_and_op(triangle_mesh_shape, op); + triangle_mesh_shapes_.push_back(shape_and_op); + } + //=================================================================================================// + void ComplexShapeTriangleMesh::addCylinder(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation, ShapeBooleanOps op) + { + /** Here SimTK::UnitVec3 give the direction of the cylinder, viz. SimTK::UnitVec3(0,0,1) create a cylinder in z-axis.*/ + TriangleMeshShape* triangle_mesh_shape = new TriangleMeshShape(axis, radius, halflength, resolution, translation); + std::pair shape_and_op(triangle_mesh_shape, op); + triangle_mesh_shapes_.push_back(shape_and_op); + } + //=================================================================================================// + void ComplexShapeTriangleMesh::addFormSTLFile(std::string file_path_name, Vec3d translation, Real scale_factor, ShapeBooleanOps op) + { + TriangleMeshShape* triangle_mesh_shape = new TriangleMeshShape(file_path_name, translation, scale_factor); + std::pair shape_and_op(triangle_mesh_shape, op); + triangle_mesh_shapes_.push_back(shape_and_op); + } + //=================================================================================================// + void ComplexShapeTriangleMesh::addComplexShapeTriangleMesh(ComplexShapeTriangleMesh* complex_shape_triangle_mesh, ShapeBooleanOps op) + { + switch (op) + { + case ShapeBooleanOps::add: + { + for (auto& shape_and_op : complex_shape_triangle_mesh->triangle_mesh_shapes_) + { + triangle_mesh_shapes_.push_back(shape_and_op); + } + break; + } + case ShapeBooleanOps::sub: + { + for (auto& shape_and_op : complex_shape_triangle_mesh->triangle_mesh_shapes_) + { + TriangleMeshShape* sp = shape_and_op.first; + ShapeBooleanOps operation_string + = shape_and_op.second == ShapeBooleanOps::add ? ShapeBooleanOps::sub : ShapeBooleanOps::add; + std::pair substract_shape_and_op(sp, operation_string); + + triangle_mesh_shapes_.push_back(substract_shape_and_op); + } + break; + } + } + } + //=================================================================================================// + bool ComplexShapeTriangleMesh::checkNotFar(const Vec3d& input_pnt, Real threshold) + { + return checkContain(input_pnt) || checkNearSurface(input_pnt , threshold) ? true : false; + } + //=================================================================================================// + bool ComplexShapeTriangleMesh::checkNearSurface(const Vec3d& input_pnt, Real threshold) + { + return getMaxAbsoluteElement(input_pnt - findClosestPoint(input_pnt)) < threshold ? true : false; + } + //=================================================================================================// + BoundingBox ComplexShapeTriangleMesh::findBounds() + { + //initial reference values + Vec3d lower_bound = Vec3d(Infinity); + Vec3d upper_bound = Vec3d(-Infinity); + + for (size_t i = 0; i < triangle_mesh_shapes_.size(); i++) + { + BoundingBox shape_bounds = triangle_mesh_shapes_[i].first->findBounds(); + for (int j = 0; j != 3; ++j) { + lower_bound[j] = SMIN(lower_bound[j], shape_bounds.first[j]); + upper_bound[j] = SMAX(upper_bound[j], shape_bounds.second[j]); + } + } + return BoundingBox(lower_bound, upper_bound); + } + //=================================================================================================// +} \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/geometries/complex_shape_triangle_mesh.h b/SPHINXsys/src/for_3D_build/geometries/complex_shape_triangle_mesh.h new file mode 100644 index 0000000000..834dfec60e --- /dev/null +++ b/SPHINXsys/src/for_3D_build/geometries/complex_shape_triangle_mesh.h @@ -0,0 +1,90 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file complex_shape_triangle_mesh.h +* @brief x +* @details x +* x +* @author Yijin Mao +*/ + +#ifndef COMPLEX_SHAPE_TRIANGLE_MESH_H +#define COMPLEX_SHAPE_TRIANGLE_MESH_H + +#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING + +#include "base_geometry.h" +#include "SimTKcommon.h" +#include "SimTKmath.h" +#include "Simbody.h" +#include "simbody_middle.h" +//#include "geometry.h" +#include "complex_shape_mesh.h" + +#include +#include +#include + +/** Macro for APPLE compilers*/ +#ifdef __APPLE__ +#include +namespace fs = boost::filesystem; +#else +#include +namespace fs = std::experimental::filesystem; +#endif + +namespace SPH +{ + class TriangleMeshShape; + + class ComplexShapeTriangleMesh : public ComplexShapeMesh + { + public: + ComplexShapeTriangleMesh() : ComplexShapeMesh("ComplexShapeTriangleMesh") {}; + ComplexShapeTriangleMesh(std::string complex_shape_name) : ComplexShapeMesh(complex_shape_name) {}; + virtual ~ComplexShapeTriangleMesh() {}; + virtual BoundingBox findBounds() override; + + void addTriangleMeshShape(TriangleMeshShape* triangle_mesh_shape, ShapeBooleanOps op); + void addComplexShapeTriangleMesh(ComplexShapeTriangleMesh* complex_shape_triangle_mesh, ShapeBooleanOps op); + virtual void addBrick(Vec3d halfsize, int resolution, Vec3d translation, ShapeBooleanOps op); + virtual void addSphere(Real radius, int resolution, Vec3d translation, ShapeBooleanOps op); + virtual void addCylinder(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation, ShapeBooleanOps op); + void addFormSTLFile(std::string file_path_name, Vec3d translation, Real scale_factor, ShapeBooleanOps op); + + virtual bool checkContain(const Vec3d& input_pnt, bool BOUNDARY_INCLUDED = true); + virtual bool checkNotFar(const Vec3d& input_pnt, Real threshold); + virtual bool checkNearSurface(const Vec3d& input_pnt, Real threshold); + /** Signed distance is negative for point within the complex shape. */ + virtual Real findSignedDistance(const Vec3d& input_pnt); + /** Normal direction point toward outside of the complex shape. */ + virtual Vec3d findNormalDirection(const Vec3d& input_pnt); + virtual Vec3d findClosestPoint(const Vec3d& input_pnt); + protected: + /** shape container */ + std::vector> triangle_mesh_shapes_; + }; +} + +#endif //COMPLEX_SHAPE_TRIANGLE_MESH_H diff --git a/SPHINXsys/src/for_3D_build/geometries/geometry.cpp b/SPHINXsys/src/for_3D_build/geometries/geometry.cpp index 4bc0e06b42..a448356dec 100644 --- a/SPHINXsys/src/for_3D_build/geometries/geometry.cpp +++ b/SPHINXsys/src/for_3D_build/geometries/geometry.cpp @@ -137,198 +137,42 @@ namespace SPH } return BoundingBox(lower_bound, upper_bound); } + //=================================================================================================// bool ComplexShape::checkContain(const Vec3d &input_pnt, bool BOUNDARY_INCLUDED) { - bool exist = false; - bool inside = false; - - for (auto &each_shape : triangle_mesh_shapes_) - { - TriangleMeshShape *sp = each_shape.first; - ShapeBooleanOps operation_string = each_shape.second; - - switch (operation_string) - { - case ShapeBooleanOps::add: - { - inside = sp->checkContain(input_pnt); - exist = exist || inside; - break; - } - case ShapeBooleanOps::sub: - { - inside = sp->checkContain(input_pnt); - exist = exist && (!inside); - break; - } - default: - { - std::cout << "\n FAILURE: the boolean operation is not applicable!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - throw; - } - } - } - return exist; + return complex_shape_mesh_->checkContain(input_pnt, BOUNDARY_INCLUDED); } //=================================================================================================// Vec3d ComplexShape::findClosestPoint(const Vec3d &input_pnt) { - //a big positive number - Real large_number(Infinity); - Real dist_min = large_number; - Vec3d pnt_closest(0); - Vec3d pnt_found(0); - - for (auto &each_shape : triangle_mesh_shapes_) - { - TriangleMeshShape *sp = each_shape.first; - pnt_found = sp->findClosestPoint(input_pnt); - Real dist = (input_pnt - pnt_found).norm(); - - if (dist <= dist_min) - { - dist_min = dist; - pnt_closest = pnt_found; - } - } - - return pnt_closest; + return complex_shape_mesh_->findClosestPoint(input_pnt); } //=================================================================================================// Real ComplexShape::findSignedDistance(const Vec3d &input_pnt) { - Real distance_to_surface = (input_pnt - findClosestPoint(input_pnt)).norm(); - return checkContain(input_pnt) ? -distance_to_surface : distance_to_surface; + return complex_shape_mesh_->findSignedDistance(input_pnt); } //=================================================================================================// Vec3d ComplexShape::findNormalDirection(const Vec3d &input_pnt) { - bool is_contain = checkContain(input_pnt); - Vecd displacement_to_surface = findClosestPoint(input_pnt) - input_pnt; - while (displacement_to_surface.norm() < Eps) - { - Vecd jittered = input_pnt; //jittering - for (int l = 0; l != input_pnt.size(); ++l) - jittered[l] = input_pnt[l] + (((Real)rand() / (RAND_MAX)) - 0.5) * 100.0 * Eps; - if (checkContain(jittered) == is_contain) - displacement_to_surface = findClosestPoint(jittered) - jittered; - } - Vecd direction_to_surface = displacement_to_surface.normalize(); - return is_contain ? direction_to_surface : -1.0 * direction_to_surface; - } - //=================================================================================================// - void ComplexShape::addTriangleMeshShape(TriangleMeshShape *triangle_mesh_shape, ShapeBooleanOps op) - { - std::pair shape_and_op(triangle_mesh_shape, op); - triangle_mesh_shapes_.push_back(shape_and_op); - } - //=================================================================================================// - void ComplexShape::addBrick(Vec3d halfsize, int resolution, Vec3d translation, ShapeBooleanOps op) - { - TriangleMeshShape *triangle_mesh_shape = new TriangleMeshShape(halfsize, resolution, translation); - std::pair shape_and_op(triangle_mesh_shape, op); - triangle_mesh_shapes_.push_back(shape_and_op); - } - //=================================================================================================// - void ComplexShape::addSphere(Real radius, int resolution, Vec3d translation, ShapeBooleanOps op) - { - TriangleMeshShape *triangle_mesh_shape = new TriangleMeshShape(radius, resolution, translation); - std::pair shape_and_op(triangle_mesh_shape, op); - triangle_mesh_shapes_.push_back(shape_and_op); - } - //=================================================================================================// - void ComplexShape::addCylinder(SimTK::UnitVec3 axis, Real radius, Real halflength, - int resolution, Vec3d translation, ShapeBooleanOps op) - { - /** Here SimTK::UnitVec3 give the direction of the cylinder, viz. SimTK::UnitVec3(0,0,1) create a cylinder in z-axis.*/ - TriangleMeshShape *triangle_mesh_shape = - new TriangleMeshShape(axis, radius, halflength, resolution, translation); - std::pair shape_and_op(triangle_mesh_shape, op); - triangle_mesh_shapes_.push_back(shape_and_op); - } - //=================================================================================================// - void ComplexShape::addFormSTLFile(std::string file_path_name, Vec3d translation, - Real scale_factor, ShapeBooleanOps op) - { - TriangleMeshShape *triangle_mesh_shape = - new TriangleMeshShape(file_path_name, translation, scale_factor); - std::pair shape_and_op(triangle_mesh_shape, op); - triangle_mesh_shapes_.push_back(shape_and_op); + return complex_shape_mesh_->findNormalDirection(input_pnt); } //=================================================================================================// - void ComplexShape::addComplexShape(ComplexShape *complex_shape, ShapeBooleanOps op) - { - switch (op) - { - case ShapeBooleanOps::add: - { - for (auto &shape_and_op : complex_shape->triangle_mesh_shapes_) - { - triangle_mesh_shapes_.push_back(shape_and_op); - } - break; - } - case ShapeBooleanOps::sub: - { - for (auto &shape_and_op : complex_shape->triangle_mesh_shapes_) - { - TriangleMeshShape *sp = shape_and_op.first; - ShapeBooleanOps operation_string = - shape_and_op.second == ShapeBooleanOps::add ? ShapeBooleanOps::sub : ShapeBooleanOps::add; - std::pair substract_shape_and_op(sp, operation_string); - triangle_mesh_shapes_.push_back(substract_shape_and_op); - } - break; - } - case ShapeBooleanOps::sym_diff: - { - std::cout << "\n FAILURE: the boolean operation: ShapeBooleanOps::sym_diff is not applicable for 3D geometry!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - throw; - } - case ShapeBooleanOps::intersect: - { - std::cout << "\n FAILURE: the boolean operation: ShapeBooleanOps::intersect is not applicable for 3D geometry!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - throw; - } - default: - { - std::cout << "\n FAILURE: the boolean operation is not applicable!" << std::endl; - std::cout << __FILE__ << ':' << __LINE__ << std::endl; - throw; - } - } - } //=================================================================================================// - bool ComplexShape::checkNotFar(const Vec3d &input_pnt, Real threshold) + bool ComplexShape::checkNotFar(const Vec3d& input_pnt, Real threshold) { - return checkContain(input_pnt) || checkNearSurface(input_pnt, threshold) ? true : false; + return complex_shape_mesh_->checkNotFar(input_pnt, threshold); } //=================================================================================================// bool ComplexShape::checkNearSurface(const Vec3d &input_pnt, Real threshold) { - return getMaxAbsoluteElement(input_pnt - findClosestPoint(input_pnt)) < threshold ? true : false; + return complex_shape_mesh_->checkNearSurface(input_pnt, threshold); } //=================================================================================================// BoundingBox ComplexShape::findBounds() { - //initial reference values - Vec3d lower_bound = Vec3d(Infinity); - Vec3d upper_bound = Vec3d(-Infinity); - - for (size_t i = 0; i < triangle_mesh_shapes_.size(); i++) - { - BoundingBox shape_bounds = triangle_mesh_shapes_[i].first->findBounds(); - for (int j = 0; j != 3; ++j) - { - lower_bound[j] = SMIN(lower_bound[j], shape_bounds.first[j]); - upper_bound[j] = SMAX(upper_bound[j], shape_bounds.second[j]); - } - } - return BoundingBox(lower_bound, upper_bound); + return complex_shape_mesh_->findBounds(); } //=================================================================================================// } \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/geometries/geometry.h b/SPHINXsys/src/for_3D_build/geometries/geometry.h index 7a63cf1c3c..4530843c7a 100644 --- a/SPHINXsys/src/for_3D_build/geometries/geometry.h +++ b/SPHINXsys/src/for_3D_build/geometries/geometry.h @@ -34,7 +34,14 @@ #define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING #include "base_geometry.h" +#include "SimTKcommon.h" +#include "SimTKmath.h" +#include "Simbody.h" #include "simbody_middle.h" +#include "complex_shape_mesh.h" +#include "complex_shape_triangle_mesh.h" +#include "complex_shape_image_mesh.h" +#include "image_mesh_shape.h" #include #include @@ -86,17 +93,32 @@ namespace SPH Vec3d findClosestPoint(const Vec3d &input_pnt); public: - ComplexShape() : Shape("ComplexShape"){}; - ComplexShape(std::string complex_shape_name) : Shape(complex_shape_name){}; - virtual ~ComplexShape(){}; + ComplexShape() : Shape("ComplexShape") { complex_shape_mesh_ = nullptr; }; + ComplexShape(std::string complex_shape_name) : Shape(complex_shape_name) { complex_shape_mesh_ = nullptr; }; + ComplexShape(ComplexShapeMesh*complex_shape_mesh) : Shape("ComplexShape") { complex_shape_mesh_ = complex_shape_mesh; }; + virtual ~ComplexShape() {}; virtual BoundingBox findBounds() override; - void addTriangleMeshShape(TriangleMeshShape *triangle_mesh_shape, ShapeBooleanOps op); - void addComplexShape(ComplexShape *complex_shape, ShapeBooleanOps op); - void addBrick(Vec3d halfsize, int resolution, Vec3d translation, ShapeBooleanOps op); - void addSphere(Real radius, int resolution, Vec3d translation, ShapeBooleanOps op); - void addCylinder(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation, ShapeBooleanOps op); - void addFormSTLFile(std::string file_path_name, Vec3d translation, Real scale_factor, ShapeBooleanOps op); + void addTriangleMeshShape(TriangleMeshShape *triangle_mesh_shape, ShapeBooleanOps op) + { + if(complex_shape_mesh_->getName() == "ComplexShapeTriangleMesh") + { + ComplexShapeTriangleMesh *complex_shape_mesh = dynamic_cast(complex_shape_mesh_); + complex_shape_mesh->addTriangleMeshShape(triangle_mesh_shape, op); + } + } + // void addComplexShape(ComplexShape *complex_shape, ShapeBooleanOps op); + void addBrick(Vec3d halfsize, int resolution, Vec3d translation, ShapeBooleanOps op) + { + if(complex_shape_mesh_->getName() == "ComplexShapeTriangleMesh") + { + ComplexShapeTriangleMesh *complex_shape_mesh = dynamic_cast(complex_shape_mesh_); + complex_shape_mesh->addBrick(halfsize, resolution, translation, op); + } + } + //void addSphere(Real radius, int resolution, Vec3d translation, ShapeBooleanOps op); + //void addCylinder(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation, ShapeBooleanOps op); + //void addFormSTLFile(std::string file_path_name, Vec3d translation, Real scale_factor, ShapeBooleanOps op); virtual bool checkContain(const Vec3d &input_pnt, bool BOUNDARY_INCLUDED = true); virtual bool checkNotFar(const Vec3d &input_pnt, Real threshold); @@ -108,8 +130,9 @@ namespace SPH protected: /** shape container */ - std::vector> triangle_mesh_shapes_; + + ComplexShapeMesh* complex_shape_mesh_; }; } -#endif //GEOMETRY_3D_H \ No newline at end of file +#endif //GEOMETRY_3D_H diff --git a/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.cpp b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.cpp new file mode 100644 index 0000000000..cb70bd3cce --- /dev/null +++ b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.cpp @@ -0,0 +1,432 @@ +#include "image_mesh_shape.h" + +namespace SPH +{ + //=================================================================================================// + ImageMeshShape::ImageMeshShape(std::string file_path_name) : + origin_(0.0, 0.0, 0.0), + translation_(0.0, 0.0, 0.0), + rotation_(1.0), + spacing_(1.0, 1.0, 1.0), + dimensions_(1, 1, 1), + data_(nullptr), + size_(1), + width_(1), + height_(1), + depth_(1), + max_distance_(-INFINITY), + min_distance_(INFINITY) + { + std::fstream dataFile(file_path_name); + if (dataFile.fail()) + { + std::cout << "File can not open <<" << file_path_name << std::endl; + } + + while (!dataFile.fail() && !dataFile.eof()) + { + } + dataFile.close(); + + } + //=================================================================================================// + ImageMeshShape::ImageMeshShape(Vec3d halfsize, int resolution, Vec3d translation, Mat3d rotation) : + origin_(0.0, 0.0, 0.0), + translation_(translation), + rotation_(1.0), + spacing_(1.0, 1.0, 1.0), + dimensions_(1, 1, 1), + data_(nullptr), + size_(1), + width_(1), + height_(1), + depth_(1), + max_distance_(-INFINITY), + min_distance_(INFINITY) + { + + } + //=================================================================================================// + ImageMeshShape::ImageMeshShape(Real radius, int resolution, Vec3d translation, Mat3d rotation) : + origin_(radius, radius, radius), + translation_(translation), + rotation_(rotation), + spacing_(1.0, 1.0, 1.0), + dimensions_(1, 1, 1), + data_(nullptr), + size_(1), + width_(1), + height_(1), + depth_(1), + max_distance_(-INFINITY), + min_distance_(INFINITY) + { + // origin_ = origin; + rotation_ = rotation; + translation_ = translation; + int length = int(std::ceil(3.0*radius)); + dimensions_ = Vec3i(length, length, length); + width_ = dimensions_[0]; + height_ = dimensions_[1]; + depth_ = dimensions_[2]; + size_ = width_*height_*depth_; + data_ = new float[size_]; + + std::ofstream output_file("sphere.dat",std::ofstream::out); + + output_file << "\n"; + output_file << "title='View'" << "\n"; + output_file << "variables= " << "x, " << "y, " << "z, " << "phi " << "\n"; + output_file << "zone i=" << width_ << " j=" << height_ << " k=" << depth_ + << " DATAPACKING=BLOCK SOLUTIONTIME=" << 0 << "\n"; + + for (int z = 0; z < depth_; z++) + { + for (int y = 0; y < height_; y++) + { + for (int x = 0; x < width_; x++) + { + //std::cout << data_[index] << std::endl; + output_file << x << " "; + } + output_file << " \n"; + } + } + + for (int z = 0; z < depth_; z++) + { + for (int y = 0; y < height_; y++) + { + for (int x = 0; x < width_; x++) + { + //std::cout << data_[index] << std::endl; + output_file << y << " "; + } + output_file << " \n"; + } + } + for (int z = 0; z < depth_; z++) + { + for (int y = 0; y < height_; y++) + { + for (int x = 0; x < width_; x++) + { + //std::cout << data_[index] << std::endl; + output_file << z << " "; + } + output_file << " \n"; + } + } + + for(int z = 0; z < depth_; z++) + { + for (int y = 0; y < height_; y++) + { + for (int x = 0; x < width_; x++) + { + int index = z*width_*height_ + y*width_ + x; + double distance = (Vec3d(x,y,z) - origin_).norm()*spacing_[0]-radius*spacing_[0]; + if(distance < min_distance_) min_distance_ = distance; + if(distance > max_distance_) max_distance_ = distance; + data_[index] = float(distance); + /*if(distance < radius) + { + data_[index] = -float(distance); + } + else + { + data_[index] = float(distance); + }*/ + //std::cout << data_[index] << std::endl; + output_file << data_[index] << " "; + } + output_file << " \n"; + } + } + output_file.close(); + } + //=================================================================================================// + ImageMeshShape::ImageMeshShape(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation, Mat3d rotation) : + origin_(0.0, 0.0, 0.0), + translation_(translation), + rotation_(rotation), + spacing_(1.0, 1.0, 1.0), + dimensions_(1, 1, 1), + data_(nullptr), + size_(1), + width_(1), + height_(1), + depth_(1), + max_distance_(-INFINITY), + min_distance_(INFINITY) + { + + } + //=================================================================================================// + ImageMeshShape::~ImageMeshShape() + { + if(data_) + { + delete data_; + data_ = nullptr; + } + } + //=================================================================================================// + bool ImageMeshShape::checkContain(const Vec3d& input_pnt, bool BOUNDARY_INCLUDED) + { + Real value = findValueAtPoint(input_pnt); + if (BOUNDARY_INCLUDED == true) + { + if (value > 0.0) return false; + else return true; + } + else + { + if (value >= 0.0) return false; + else return true; + } + } + //=================================================================================================// + Vec3d ImageMeshShape::findClosestPoint(const Vec3d& input_pnt) + { + Vec3i this_cell; + std::vector neighbors = findNeighbors(input_pnt, this_cell); + Vec3d n_sum(0.0, 0.0, 0.0); + double weight_sum = 0.0; + double d_sum = 0.0; + for (const int& i : neighbors) + { + // checkIndexBound(i); + Vec3d nCj = computeNormalAtCell(i); + double dCj = float(getValueAtCell(i)); + double weight_Cj = 1.0/(fabs(dCj)+Eps); + n_sum = n_sum + weight_Cj*nCj; + weight_sum = weight_sum + weight_Cj; + d_sum = d_sum + dCj; + } + Vec3d n = n_sum/(weight_sum+Eps); + double d = d_sum/(weight_sum+Eps); + + Vec3d p_image = Vec3d(this_cell[0], this_cell[1], this_cell[2]) + n.normalize()*d; + Vec3d p = convertToPhysicalSpace(p_image); + return p; + + } + //=================================================================================================// + BoundingBox ImageMeshShape::findBounds() + { + //initial reference values + Vec3d lower_bound = Vec3d(Infinity); + Vec3d upper_bound = Vec3d(-Infinity); + + for(int z = 0; z < depth_+1; z++) + { + for(int y = 0; y < height_+1; y++) + { + for(int x = 0; x < width_+1; x++) + { + Vec3d p_image = Vec3d(x, y, z); + Vec3d vertex_position = convertToPhysicalSpace(p_image); + for (int j = 0; j != 3; ++j) { + lower_bound[j] = SMIN(lower_bound[j], vertex_position[j]); + upper_bound[j] = SMAX(upper_bound[j], vertex_position[j]); + } + } + } + } + + return BoundingBox(lower_bound, upper_bound); + + } + //=================================================================================================// + Real ImageMeshShape::findValueAtPoint(const Vec3d& input_pnt) + { + Vec3i this_cell; + std::vector neighbors = findNeighbors(input_pnt, this_cell); + double weight_sum = 0.0; + double d_sum = 0.0; + if(neighbors.size() > 0) + { + for (const int& i : neighbors) + { + // checkIndexBound(i); + double dCj = float(getValueAtCell(i)); + double weight_Cj = 1.0/(fabs(dCj)+Eps); + weight_sum = weight_sum + weight_Cj; + d_sum = d_sum + dCj; + } + return d_sum/(weight_sum+Eps); + } + else + { + return max_distance_; + } + + } + //=================================================================================================// + Vec3d ImageMeshShape::findNormalAtPoint(const Vec3d & input_pnt) + { + Vec3i this_cell; + std::vector neighbors = findNeighbors(input_pnt, this_cell); + Vec3d n_sum(0.0, 0.0, 0.0); + double weight_sum = 0.0; + double d_sum = 0.0; + if( neighbors.size() > 0) + { + for (const int& i : neighbors) + { + // checkIndexBound(i); + Vec3d nCj = computeNormalAtCell(i); + double dCj = float(getValueAtCell(i)); + double weight_Cj = 1.0/(fabs(dCj)+Eps); + n_sum = n_sum + weight_Cj*nCj; + weight_sum = weight_sum + weight_Cj; + d_sum = d_sum + dCj; + } + Vec3d n = n_sum/(weight_sum+Eps); + return n.normalize(); + } + else + { + return Vec3d(1.0,1.0,1.0).normalize(); + } + + } + //=================================================================================================// + std::vector ImageMeshShape::findNeighbors(const Vec3d& input_pnt, Vec3i& this_cell) + { + std::vector neighbors; + + Vec3d image_coord = rotation_.invert()*(input_pnt - translation_); + // std::cout <<"findNeighbor of " << input_pnt << " ........... " << image_coord << std::endl; + + int z = int(floor(image_coord[2])); + int y = int(floor(image_coord[1])); + int x = int(floor(image_coord[0])); + + //- cannot count cells in buffer zone + if (x < 0 || x > width_ - 1 || y < 0 || y > height_ - 1 || z < 0 || z > depth_ - 1) return neighbors; + + for (int k = z - 1; k < z + 2; k = k + 2) + { + for (int j = y - 1; j < y + 2; j = j + 2) + { + for (int i = x - 1; i < x + 2; i = i + 2) + { + if (i<0 || i >width_ - 1 || j <0 || j >height_ - 1 || k<0 || k >depth_) + continue; + int index = z * width_*height_ + y * width_ + x; + neighbors.push_back(index); + } + } + + } + + return neighbors; + + } + //=================================================================================================// + Vec3d ImageMeshShape::computeGradientAtCell(int i) + { + //- translate 1D index to 3D index + int width = width_; + int height = height_; + int depth = depth_; + int sliceSize = width*height; + int z = i/sliceSize; + int y = (i%sliceSize)/width; + int x = (i%sliceSize)%width; + + double gradx = 0.0; double grady = 0.0; double gradz = 0.0; + //- cds (if inner cell) + //- otherwise back/forward scheme + if(x == 0) + { + int indexHigh = z*sliceSize + y*width + (x+1); + gradx = (getValueAtCell(indexHigh) - getValueAtCell(i)); + } + else if(x == width - 1) + { + int indexLow = z*sliceSize + y*width + (x-1); + gradx = -(getValueAtCell(indexLow) - getValueAtCell(i)); + } + else if (x > 0 && x < width_ - 1) + { + int indexHigh = z*sliceSize + y*width + (x+1); + int indexLow = z*sliceSize + y*width + (x-1); + gradx = (getValueAtCell(indexHigh) - getValueAtCell(indexLow))/2.0; + } + + if(y == 0) + { + int indexHigh = z*sliceSize + (y+1)*width + x; + grady = (getValueAtCell(indexHigh) - getValueAtCell(i)); + } + else if(y == height - 1) + { + int indexLow = z*sliceSize + (y-1)*width + x; + grady = -(getValueAtCell(indexLow) - getValueAtCell(i)); + } + else if (y > 0 && y < height_ - 1) + { + int indexHigh = z*sliceSize + (y+1)*width + x; + int indexLow = z*sliceSize + (y-1)*width + x; + grady = (getValueAtCell(indexHigh) - getValueAtCell(indexLow))/2.0; + } + + if(z == 0) + { + int indexHigh = (z+1)*sliceSize + y*width + x; + gradz = (getValueAtCell(indexHigh) - getValueAtCell(i)); + } + else if(z == depth - 1) + { + int indexLow = (z-1)*sliceSize + y*width + x; + gradz = -(getValueAtCell(indexLow) - getValueAtCell(i)); + } + else if (z > 0 && z < depth_ - 1) + { + int indexHigh = (z+1)*sliceSize + y*width + x; + int indexLow = (z-1)*sliceSize + y*width + x; + gradz = (getValueAtCell(indexHigh) - getValueAtCell(indexLow))/2.0; + } + gradx = gradx / spacing_[0]; + grady = grady / spacing_[1]; + gradz = gradz / spacing_[2]; + return Vec3d(gradx, grady, gradz); + + } + //=================================================================================================// + Vec3d ImageMeshShape::computeNormalAtCell(int i) + { + Vec3d grad_phi = computeGradientAtCell(i); + Vec3d n = grad_phi.normalize(); + return n; + } + + float ImageMeshShape::getValueAtCell(int i) + { + if(i < 0 || i > size_) + { + return float(max_distance_); + } + else + { + return data_[i]; + } + + } + //=================================================================================================// + Vec3d ImageMeshShape::convertToPhysicalSpace(Vec3d p) + { + Vec3d position = rotation_*p + translation_; + for(int i = 0; i < position.size(); i++) + { + position[i] = position[i]*spacing_[i]; + } + return position; + + } + //=================================================================================================// + +} \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h new file mode 100644 index 0000000000..5891affa36 --- /dev/null +++ b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h @@ -0,0 +1,104 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file image_mesh_shape.h +* @brief x +* @details x +* x +* @author Yijin Mao +*/ + +#ifndef IMAGE_MESH_SHAPE_H +#define IMAGE_MESH_SHAPE_H + +#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING + +#include "base_geometry.h" +#include "SimTKcommon.h" +#include "SimTKmath.h" +#include "Simbody.h" +#include "simbody_middle.h" +#include "geometry.h" + +#include +#include +#include + +/** Macro for APPLE compilers*/ +#ifdef __APPLE__ +#include +namespace fs = boost::filesystem; +#else +#include +namespace fs = std::experimental::filesystem; +#endif + +namespace SPH +{ +//- imageMesh is input 3d image + class ImageMeshShape + { + public: + //constructor for load mhd/raw file from out side + ImageMeshShape(std::string file_path_name); + ImageMeshShape(Vec3d halfsize, int resolution, Vec3d translation, Mat3d rotation); + ImageMeshShape(Real radius, int resolution, Vec3d translation, Mat3d rotation); + ImageMeshShape(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation, Mat3d rotation); + virtual ~ImageMeshShape(); + + bool checkContain(const Vec3d& input_pnt, bool BOUNDARY_INCLUDED = true); + Vec3d findClosestPoint(const Vec3d& input_pnt); + BoundingBox findBounds(); + + Real findValueAtPoint(const Vec3d& input_pnt); + Vec3d findNormalAtPoint(const Vec3d & input_pnt); + + protected: + Vec3d origin_; + Vec3d translation_; + Mat3d rotation_; + Vec3d spacing_; + Vec3i dimensions_; + float *data_; + int size_; + int width_; + int height_; + int depth_; + Real max_distance_; + Real min_distance_; + + private: + std::vector findNeighbors(const Vec3d& input_pnt, Vec3i& this_cell); + Vec3d computeGradientAtCell(int i); + Vec3d computeNormalAtCell(int i); + float getValueAtCell(int i); + Vec3d convertToPhysicalSpace(Vec3d p); + // void createSphere(double radius = 10.0, Vec3d origin=Vec3d(25.0,25.0,25.0), \ + // Mat3d rotation=Mat3d(1.0), \ + // Vec3d translation=Vec3d(0.0, 0.0, 0.0), \ + // Vec3i dimensions=Vec3i(50,50,50)); + // void checkIndexBound(int i); + }; +} + +#endif //IMAGE_MESH_SHAPE_H From 44d742fef41e2a8e04b89aecbe462973b44d1919 Mon Sep 17 00:00:00 2001 From: alundilong Date: Mon, 23 Aug 2021 20:24:28 +0800 Subject: [PATCH 13/40] read/write mhd --- .../geometries/image_mesh_shape.cpp | 191 ++++++++++++------ .../geometries/image_mesh_shape.h | 1 + 2 files changed, 128 insertions(+), 64 deletions(-) diff --git a/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.cpp b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.cpp index cb70bd3cce..c2f1c79414 100644 --- a/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.cpp +++ b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.cpp @@ -17,17 +17,96 @@ namespace SPH max_distance_(-INFINITY), min_distance_(INFINITY) { - std::fstream dataFile(file_path_name); - if (dataFile.fail()) + //- read mhd file + std::ifstream dataFile(file_path_name, std::ifstream::in); + std::string file_path_name_raw; + if(dataFile.is_open()) { - std::cout << "File can not open <<" << file_path_name << std::endl; - } + std::string line; + std::vector values; + while(std::getline(dataFile, line)) + { + std::stringstream ss(line); + std::vector elements; + split(line, '=', elements); + if(elements.size() == 2) + { + if(elements[0].compare("TransformMatrix") == 0) + { + std::vector values; + split(elements[1], ' ', values); + rotation_[0][0] = std::stof(values[0]); + rotation_[0][1] = std::stof(values[1]); + rotation_[0][2] = std::stof(values[2]); + rotation_[1][0] = std::stof(values[3]); + rotation_[1][1] = std::stof(values[4]); + rotation_[1][2] = std::stof(values[5]); + rotation_[2][0] = std::stof(values[6]); + rotation_[2][1] = std::stof(values[7]); + rotation_[2][2] = std::stof(values[8]); + } + else if(elements[0].compare("Offset") == 0) + { + std::vector values; + split(elements[1], ' ', values); + translation_[0] = std::stof(values[0]); + translation_[1] = std::stof(values[1]); + translation_[2] = std::stof(values[2]); + } + else if(elements[0].compare("CenterOfRotation") == 0) + { + std::vector values; + split(elements[1], ' ', values); + origin_[0] = std::stof(values[0]); + origin_[1] = std::stof(values[1]); + origin_[2] = std::stof(values[2]); + } + else if(elements[0].compare("ElementSpacing") == 0) + { + std::vector values; + split(elements[1], ' ', values); + spacing_[0] = std::stof(values[0]); + spacing_[1] = std::stof(values[1]); + spacing_[2] = std::stof(values[2]); + } + else if(elements[0].compare("DimSize") == 0) + { + std::vector values; + split(elements[1], ' ', values); + dimensions_[0] = std::stoi(values[0]); + dimensions_[1] = std::stoi(values[1]); + dimensions_[2] = std::stoi(values[2]); - while (!dataFile.fail() && !dataFile.eof()) - { + data_ = new float[dimensions_[0]*dimensions_[1]*dimensions_[2]]; + } + else if(elements[0].compare("ElementDataFile") == 0) + { + file_path_name_raw = file_path_name +'/'+ elements[1]; + } + } + } } dataFile.close(); + //- read raw file + std::ifstream dataFileRaw(file_path_name_raw, std::ifstream::in || std::ifstream::binary); + + dataFileRaw.seekg(0, std::ifstream::end); + int size = (int)dataFileRaw.tellg(); + dataFileRaw.seekg(0, std::ifstream::beg); + if(dataFileRaw.is_open()) + { + int index = 0; + while(dataFileRaw.tellg() < size) + { + char buffer[32]; + dataFileRaw.read(buffer, sizeof(data_[index])); + data_[index] = (float)std::strtod(buffer, NULL); + index++; + } + + } + dataFileRaw.close(); } //=================================================================================================// ImageMeshShape::ImageMeshShape(Vec3d halfsize, int resolution, Vec3d translation, Mat3d rotation) : @@ -72,51 +151,31 @@ namespace SPH size_ = width_*height_*depth_; data_ = new float[size_]; - std::ofstream output_file("sphere.dat",std::ofstream::out); - - output_file << "\n"; - output_file << "title='View'" << "\n"; - output_file << "variables= " << "x, " << "y, " << "z, " << "phi " << "\n"; - output_file << "zone i=" << width_ << " j=" << height_ << " k=" << depth_ - << " DATAPACKING=BLOCK SOLUTIONTIME=" << 0 << "\n"; - - for (int z = 0; z < depth_; z++) - { - for (int y = 0; y < height_; y++) - { - for (int x = 0; x < width_; x++) - { - //std::cout << data_[index] << std::endl; - output_file << x << " "; - } - output_file << " \n"; - } - } + std::ofstream output_file("sphere.mhd",std::ofstream::out); + output_file << "ObjectType = Image" << "\n"; + output_file << "NDims = 3" << "\n"; + output_file << "BinaryData = True" << "\n"; + output_file << "BinaryDataByteOrderMSB = False" << "\n"; + output_file << "CompressedData = False" << "\n"; + output_file << "TransformMatrix = " + << rotation[0][0] << " " << rotation[0][1] << " " << rotation[0][2] << " " + << rotation[1][0] << " " << rotation[1][1] << " " << rotation[1][2] << " " + << rotation[2][0] << " " << rotation[2][1] << " " << rotation[2][2] << "\n"; + output_file << "Offset = " + << translation[0] << " " << translation[1] << " " << translation[2] << "\n"; + output_file << "CenterOfRotation = " + << origin_[0] << " " << origin_[1] << " " << origin_[2] << "\n"; + output_file << "ElementSpacing = " + << spacing_[0] << " " << spacing_[1] << " " << spacing_[2] << "\n"; + output_file << "DimSize = " + << dimensions_[0] << " " << dimensions_[1] << " " << dimensions_[2] << "\n"; + output_file << "AnatomicalOrientation = ??? " << "\n"; + output_file << "ElementType = MET_FLOAT" << "\n"; + output_file << "ElementDataFile = sphere.raw" << "\n"; - for (int z = 0; z < depth_; z++) - { - for (int y = 0; y < height_; y++) - { - for (int x = 0; x < width_; x++) - { - //std::cout << data_[index] << std::endl; - output_file << y << " "; - } - output_file << " \n"; - } - } - for (int z = 0; z < depth_; z++) - { - for (int y = 0; y < height_; y++) - { - for (int x = 0; x < width_; x++) - { - //std::cout << data_[index] << std::endl; - output_file << z << " "; - } - output_file << " \n"; - } - } + output_file.close(); + std::ofstream output_file_raw("sphere.raw", std::ofstream::binary); + Vec3d center(0.5*width_, 0.5*height_, 0.5*depth_); for(int z = 0; z < depth_; z++) { @@ -125,25 +184,20 @@ namespace SPH for (int x = 0; x < width_; x++) { int index = z*width_*height_ + y*width_ + x; - double distance = (Vec3d(x,y,z) - origin_).norm()*spacing_[0]-radius*spacing_[0]; + double distance = (Vec3d(x,y,z) - center).norm()-radius; if(distance < min_distance_) min_distance_ = distance; if(distance > max_distance_) max_distance_ = distance; data_[index] = float(distance); - /*if(distance < radius) - { - data_[index] = -float(distance); - } - else - { - data_[index] = float(distance); - }*/ - //std::cout << data_[index] << std::endl; - output_file << data_[index] << " "; + + //output_file << data_[index] << " "; + //char array[10]; + //sprintf(array, "%f", data_[index]); + //std::cout << array << " " << data_[index] << std::endl; + output_file_raw.write((char*)(&(data_[index])), sizeof(data_[index])); } - output_file << " \n"; } } - output_file.close(); + output_file_raw.close(); } //=================================================================================================// ImageMeshShape::ImageMeshShape(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation, Mat3d rotation) : @@ -171,6 +225,15 @@ namespace SPH data_ = nullptr; } } + //=================================================================================================// + void ImageMeshShape::split(const std::string &s, char delim, std::vector &elems) + { + std::stringstream ss(s); + std::string item; + while(std::getline(ss, item, delim)) { + elems.push_back(item); + } + } //=================================================================================================// bool ImageMeshShape::checkContain(const Vec3d& input_pnt, bool BOUNDARY_INCLUDED) { @@ -429,4 +492,4 @@ namespace SPH } //=================================================================================================// -} \ No newline at end of file +} diff --git a/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h index 5891affa36..a7e4a21571 100644 --- a/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h +++ b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h @@ -65,6 +65,7 @@ namespace SPH ImageMeshShape(Real radius, int resolution, Vec3d translation, Mat3d rotation); ImageMeshShape(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation, Mat3d rotation); virtual ~ImageMeshShape(); + void ImageMeshShape::split(const std::string &s, char delim, std::vector &elems); bool checkContain(const Vec3d& input_pnt, bool BOUNDARY_INCLUDED = true); Vec3d findClosestPoint(const Vec3d& input_pnt); From 69f77446331c0e099b5929fe382bdd31e9dadcea Mon Sep 17 00:00:00 2001 From: Jan Lebert Date: Mon, 23 Aug 2021 18:46:33 -0700 Subject: [PATCH 14/40] Fix BodyStatesIO::convertPhysicalTimeToString() `BodyStatesIO::convertPhysicalTimeToString(Real t)` ignores the argument passed to it --- SPHINXsys/src/shared/io_system/in_output.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SPHINXsys/src/shared/io_system/in_output.cpp b/SPHINXsys/src/shared/io_system/in_output.cpp index ca5568ce8e..6acb4ada56 100644 --- a/SPHINXsys/src/shared/io_system/in_output.cpp +++ b/SPHINXsys/src/shared/io_system/in_output.cpp @@ -69,9 +69,9 @@ namespace SPH out_file << std::fixed << std::setprecision(9) << quantity[i] << " "; } //=============================================================================================// - std::string BodyStatesIO::convertPhysicalTimeToString(Real convertPhysicalTimeToStream) + std::string BodyStatesIO::convertPhysicalTimeToString(Real physical_time) { - int i_time = int(GlobalStaticVariables::physical_time_ * 1.0e6); + int i_time = int(physical_time * 1.0e6); std::stringstream s_time; s_time << std::setw(10) << std::setfill('0') << i_time; return s_time.str(); From d871626697222d85cd573fe3941166890323ad78 Mon Sep 17 00:00:00 2001 From: alundilong Date: Tue, 24 Aug 2021 18:22:42 +0800 Subject: [PATCH 15/40] test case added --- .../test_3d_load_image/CMakeLists.txt | 65 ++++++++++++++ tests/3d_examples/test_3d_load_image/case.h | 55 ++++++++++++ .../test_3d_load_image/load_image.cpp | 84 +++++++++++++++++++ 3 files changed, 204 insertions(+) create mode 100644 tests/3d_examples/test_3d_load_image/CMakeLists.txt create mode 100644 tests/3d_examples/test_3d_load_image/case.h create mode 100644 tests/3d_examples/test_3d_load_image/load_image.cpp diff --git a/tests/3d_examples/test_3d_load_image/CMakeLists.txt b/tests/3d_examples/test_3d_load_image/CMakeLists.txt new file mode 100644 index 0000000000..a555187c0d --- /dev/null +++ b/tests/3d_examples/test_3d_load_image/CMakeLists.txt @@ -0,0 +1,65 @@ + +CMAKE_MINIMUM_REQUIRED(VERSION 3.10) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir +set(CMAKE_VERBOSE_MAKEFILE on) + +STRING( REGEX REPLACE ".*/(.*)" "\\1" CURRENT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR} ) +PROJECT("${CURRENT_FOLDER}") + +include(ImportSPHINXsysFromSource_for_3D_build) + +SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) +SET(EXECUTABLE_OUTPUT_PATH "${PROJECT_BINARY_DIR}/bin/") +SET(BUILD_INPUT_PATH "${EXECUTABLE_OUTPUT_PATH}/input") +SET(BUILD_RELOAD_PATH "${EXECUTABLE_OUTPUT_PATH}/reload") + +file(MAKE_DIRECTORY ${BUILD_INPUT_PATH}) +execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${BUILD_INPUT_PATH}) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/teapot.stl + DESTINATION ${BUILD_INPUT_PATH}) + +aux_source_directory(. DIR_SRCS) +ADD_EXECUTABLE(${PROJECT_NAME} ${DIR_SRCS}) + +add_test(NAME ${PROJECT_NAME} + COMMAND ${PROJECT_NAME} --r=true + WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) + +if(NOT SPH_ONLY_STATIC_BUILD) # usual dynamic build + if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${EXECUTABLE_OUTPUT_PATH}") + target_link_libraries(${PROJECT_NAME} sphinxsys_3d) + add_dependencies(${PROJECT_NAME} sphinxsys_3d sphinxsys_static_3d) + else(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + target_link_libraries(${PROJECT_NAME} sphinxsys_3d stdc++) + else(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + target_link_libraries(${PROJECT_NAME} sphinxsys_3d stdc++ stdc++fs dl) + endif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + + if(DEFINED BOOST_AVAILABLE) # link Boost if available (not for Windows) + target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES}) + endif() + endif(${CMAKE_SYSTEM_NAME} MATCHES "Windows") +else() # static build only + if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${EXECUTABLE_OUTPUT_PATH}") + target_link_libraries(${PROJECT_NAME} sphinxsys_static_3d) + else(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + target_link_libraries(${PROJECT_NAME} sphinxsys_static_3d stdc++) + else(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + target_link_libraries(${PROJECT_NAME} sphinxsys_static_3d stdc++ stdc++fs dl) + endif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + + if(DEFINED BOOST_AVAILABLE) # link Boost if available (not for Windows) + target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES}) + endif() + endif(${CMAKE_SYSTEM_NAME} MATCHES "Windows") +endif() +if(NOT BUILD_WITH_SIMBODY) # link Simbody if not built by the project +target_link_libraries(${PROJECT_NAME} ${Simbody_LIBRARIES}) +endif() +if(NOT BUILD_WITH_ONETBB) # link TBB if not built by the project +target_link_libraries(${PROJECT_NAME} ${TBB_LIBRARYS}) +endif() diff --git a/tests/3d_examples/test_3d_load_image/case.h b/tests/3d_examples/test_3d_load_image/case.h new file mode 100644 index 0000000000..1e18e3042e --- /dev/null +++ b/tests/3d_examples/test_3d_load_image/case.h @@ -0,0 +1,55 @@ +/** +* @file case.h +* @brief This is the test of using levelset to generate particles relax particles. +* @details We use this case to test the particle generation and relaxation by levelset for a complex geometry (3D). +* Before particle generation, we clean the sharp corner and smooth 0 levelset value, then doing the re-initialization +* @author Yongchuan Yu and Xiangyu Hu +*/ + +#ifndef TEST_3D_PARTICLE_GENERATION_CASE_H +#define TEST_3D_PARTICLE_GENERATION_CASE_H + +#include "sphinxsys.h" + +using namespace SPH; + +//---------------------------------------------------------------------- +// Set the file path to the data file. +//---------------------------------------------------------------------- +std::string full_path_to_airfoil = "./input/teapot.stl"; +//---------------------------------------------------------------------- +// Basic geometry parameters and numerical setup. +//---------------------------------------------------------------------- +Vec3d domain_lower_bound(-25.0, -25.0, -25.0); +Vec3d domain_upper_bound(25.0, 25.0, 25.0); +Real dp_0 = (domain_upper_bound[0] - domain_lower_bound[0]) / 50.0; +/** Domain bounds of the system. */ +BoundingBox system_domain_bounds(domain_lower_bound, domain_upper_bound); + +ImageMeshShape *CreateImportedModelSurface() +{ + Vecd translation(0.0, 0.0, 0.0); + ImageMeshShape *geometry_imported_model = \ + new ImageMeshShape(10.0, 1.0, Vec3d(-10.0, -10.0, -10.0), Mat3d(1.0)); + + return geometry_imported_model; +} + +class ImportedModel : public SolidBody +{ +public: + ImportedModel(SPHSystem &system, std::string body_name) + : SolidBody(system, body_name, + new ParticleSpacingByBodyShape(1.15, 1.0, 2), + new ParticleGeneratorMultiResolution()) + { + /** Geometry definition. */ + ComplexShapeImageMesh *original_body_shape_mesh = new ComplexShapeImageMesh(); + original_body_shape_mesh->addImageMeshShape(CreateImportedModelSurface(), \ + ShapeBooleanOps::add); + ComplexShape original_body_shape(original_body_shape_mesh); + body_shape_ = new LevelSetComplexShape(this, original_body_shape, true); + } +}; + +#endif //TEST_3D_PARTICLE_GENERATION_CASE_Hs \ No newline at end of file diff --git a/tests/3d_examples/test_3d_load_image/load_image.cpp b/tests/3d_examples/test_3d_load_image/load_image.cpp new file mode 100644 index 0000000000..5903871deb --- /dev/null +++ b/tests/3d_examples/test_3d_load_image/load_image.cpp @@ -0,0 +1,84 @@ + /** + * @file load_image.cpp + * @brief This is the test of using distance map to generate body fitted particles (3D). + * @details We use this case to test the particle generation and relaxation for a complex geometry. + * Before particle generation, we clean the sharp corners of the model. + * @author Yongchuan Yu and Xiangyu Hu + */ + +#include "sphinxsys.h" + +/** case file to setup the test case */ +#include "case.h" + +using namespace SPH; + +int main(int ac, char* av[]) +{ + //---------------------------------------------------------------------- + // Build up -- a SPHSystem + //---------------------------------------------------------------------- + SPHSystem system(system_domain_bounds, dp_0); + /** Tag for run particle relaxation for the initial body fitted distribution. */ + system.run_particle_relaxation_ = true; + //handle command line arguments + #ifdef BOOST_AVAILABLE + system.handleCommandlineOptions(ac, av); + #endif + /** output environment. */ + In_Output in_output(system); + //---------------------------------------------------------------------- + // Creating body, materials and particles. + //---------------------------------------------------------------------- + ImportedModel* imported_model = new ImportedModel(system, "ImportedModel"); + SolidParticles imported_model_particles(imported_model); + imported_model_particles.addAVariableToWrite("SmoothingLengthRatio"); + //---------------------------------------------------------------------- + // Define simple file input and outputs functions. + //---------------------------------------------------------------------- + BodyStatesRecordingToVtu write_imported_model_to_vtu(in_output, { imported_model }); + MeshRecordingToPlt cell_linked_list_recording(in_output, imported_model, imported_model->cell_linked_list_); + //---------------------------------------------------------------------- + // Define body relation map. + // The contact map gives the topological connections between the bodies. + // Basically the the range of bodies to build neighbor particle lists. + //---------------------------------------------------------------------- + BaseBodyRelationInner* imported_model_inner + = new BodyRelationInnerVariableSmoothingLength(imported_model); + //---------------------------------------------------------------------- + // Methods used for particle relaxation. + //---------------------------------------------------------------------- + RandomizePartilePosition random_imported_model_particles(imported_model); + /** A Physics relaxation step. */ + relax_dynamics::RelaxationStepInner relaxation_step_inner(imported_model_inner, true); + relax_dynamics::UpdateSmoothingLengthRatioByBodyShape update_smoothing_length_ratio(imported_model); + //---------------------------------------------------------------------- + // Particle relaxation starts here. + //---------------------------------------------------------------------- + random_imported_model_particles.parallel_exec(0.25); + relaxation_step_inner.surface_bounding_.parallel_exec(); + update_smoothing_length_ratio.parallel_exec(); + write_imported_model_to_vtu.writeToFile(); + imported_model->updateCellLinkedList(); + cell_linked_list_recording.writeToFile(0); + //---------------------------------------------------------------------- + // Particle relaxation time stepping start here. + //---------------------------------------------------------------------- + int ite_p = 0; + while (ite_p < 1000) + { + update_smoothing_length_ratio.parallel_exec(); + relaxation_step_inner.parallel_exec(); + ite_p += 1; + if (ite_p % 100 == 0) + { + std::cout << std::fixed << std::setprecision(9) << "Relaxation steps for the imported model N = " << ite_p << "\n"; + write_imported_model_to_vtu.writeToFile(ite_p); + } + } + std::cout << "The physics relaxation process of imported model finish !" << std::endl; + + return 0; +} + + From e412704fb403312f01eab7f03e0e4dc36d3d8683 Mon Sep 17 00:00:00 2001 From: Jan Lebert Date: Mon, 23 Aug 2021 18:50:42 -0700 Subject: [PATCH 16/40] Fix ParticleSpacingByBodyShape::getLocalSpacing() `complex_shape.findSignedDistance(position)` returns type `Real`, but the `abs` called on it is `int abs(int)`. Shouldn't it be `fabs`? To avoid this bug, I usually use `std::abs`, however `fabs` is used elsewhere. --- SPHINXsys/src/shared/particles/particle_adaptation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SPHINXsys/src/shared/particles/particle_adaptation.cpp b/SPHINXsys/src/shared/particles/particle_adaptation.cpp index 3ec8370dc7..fd9d1a5278 100644 --- a/SPHINXsys/src/shared/particles/particle_adaptation.cpp +++ b/SPHINXsys/src/shared/particles/particle_adaptation.cpp @@ -167,7 +167,7 @@ namespace SPH //=================================================================================================// Real ParticleSpacingByBodyShape::getLocalSpacing(ComplexShape &complex_shape, Vecd &position) { - Real phi = abs(complex_shape.findSignedDistance(position)); + Real phi = fabs(complex_shape.findSignedDistance(position)); Real ratio_ref = phi / (2.0 * spacing_ref_ * spacing_ratio_max_); Real target_ratio = spacing_ratio_max_; if (ratio_ref < kernel_->KernelSize()) From 67dcab411f9a5341bb83149a2a4350dccb9a22bb Mon Sep 17 00:00:00 2001 From: Xiangyu Hu Date: Thu, 26 Aug 2021 11:47:22 +0200 Subject: [PATCH 17/40] redirect installation and tutorial to project website --- README.md | 250 +++++++++--------------------------------------------- 1 file changed, 41 insertions(+), 209 deletions(-) diff --git a/README.md b/README.md index b45647b00b..e4cec62dcb 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ It provides C++ APIs for physical accurate simulation and aims to model coupled and beyond with SPH (smoothed particle hydrodynamics), a meshless computational method using particle discretization. Please check the documentation of the code at https://xiangyu-hu.github.io/SPHinXsys/. For more information on the SPHinXsys project, please check the project website: https://www.sphinxsys.org. -For program manual and tutorials, please check https://www.sphinxsys.org/html/sphinx_index.html. SPHinXsys is a multi-physics, multi-resolution SPH library. Although it is not a standalone application itself, @@ -31,232 +30,65 @@ Software Impacts, 6 (2020) 100033 4. Chi Zhang, Massoud Rezavand, Xiangyu Hu, "A multi-resolution SPH method for fluid-structure interactions", -Journal of Computational Physics, in press (2021) +Journal of Computational Physics, Journal of Computational Physics, 429 (2021) 110028. 5. Chi Zhang, Yanji Wei, Frederic Dias, Xiangyu Hu, "An efficient fully Lagrangian solver for modeling wave interaction with oscillating wave energy converter", -arXiv:2012.05323 +Ocean Engineering, +Volume 236, (2021) 109540 6. Chi Zhang, Jianhang Wang, Massoud Rezavand, Dong Wu, Xiangyu Hu, "An integrative smoothed particle hydrodynamics framework for modeling cardiac function", - arXiv:2009.03759 + Computer Methods in Applied Mechanics and Engineering, 381, 113847, 2021. -## Software Architecture - -SPHinXsys is cross-platform can be compiled and used in Windows, Linux and Apple systems. - -## Installation - -Here, we give the instructions for installing on Ubuntu Linux, Apple OS and Windows Visual Studio. - -### Installing on Ubunutu Linux and Mac OS - -0. Make sure that gcc, gfrotran, wget, git, cmake and google test are installed and updated. - -1. Install Boost and TBB libraries - - $ sudo apt-get install libtbb-dev - $ sudo apt-get install libboost-all-dev - - and set the environment by - - $ echo 'export TBB_HOME=/usr/lib/x86_64-linux-gnu' >> ~/.bashrc - $ echo 'export BOOST_HOME=/usr/lib/x86_64-linux-gnu' >> ~/.bashrc - -2. Install Simbody library - - LAPCK library: - $ sudo apt-get install liblapack-dev - - optinal for visualizer: - $ sudo apt-get install libglu1-mesa-dev freeglut3-dev mesa-common-dev - $ sudo apt-get install libxi-dev libxmu-dev - - Download a release version at https://github.com/simbody/simbody/releases, for example version 3.7: - $ wget https://github.com/simbody/simbody/archive/Simbody-3.7.tar.gz - $ tar xvzf Simbody-3.7.tar.gz - - Make build and install directory, and go the build folder: - $ mkdir $HOME/simbody-build && mkdir $HOME/simbody - $ cd $HOME/simbody-build - - Configure and generate Make files: - $ cmake $HOME/simbody-Simbody-3.7 - -DCMAKE_INSTALL_PREFIX=$HOME/simbody - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DBUILD_VISUALIZER=on (optional set to ON if simbody visualizer is going to be used) - -DBUILD_STATIC_LIBRARIES=on (optional, leave it off if you don't know what are you doing) - - Bulid, test and install: - $ make -j8 - $ ctest -j8 - $ make -j8 install - - Allow to be found by SPHinXsys: - Mac: - $ echo 'export SIMBODY_HOME=$HOME/simbody' >> ~/.bash_profile - Linux: - $ echo 'export SIMBODY_HOME=$HOME/simbody' >> ~/.bashrc - - Set environment variables: - Mac:: - $ echo 'export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$HOME/simbody-prefix/lib' >> ~/.bash_profile - Linux: - $ echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$SIMBODY_HOME/lib' >> ~/.bashrc - $ echo 'export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:$SIMBODY_HOME/include' >> ~/.bashrc - -3. Update and check environment setup before building SPHinXsys. The following commands should update the environment and report the corresponding paths. - - $ source ~/.bashrc - $ echo $SIMBODY_HOME - $ echo $TBB_HOME - $ echo $BOOST_HOME - -4. Build SPHinXsys - - Download the SPHinXsys from https://github.com/Xiangyu-Hu/SPHinXsys or Bitbucket if you have the link and password to the internal group repository for the newest version: - $ git clone https://github.com/Xiangyu-Hu/SPHinXsys.git - $ mkdir $HOME/sphinxsys-build - $ cd $HOME/sphinxsys-build - $ cmake /path/to/sphinxsys/source-code -DCMAKE_BUILD_TYPE=RelWithDebInfo - - You can build, test all test cases and install by - $ make -j - $ ctest - - You can play with SPHinXsys, for example run a specific test case by - $ cd /path/to/sphinxsys-build/cases_test/test_2d_dambreak - $ make -j - $ cd /bin - $ ./test_2d_dambreak - -5. Create and build your own application - - Create your own application in the cases_user in the source folder simply by copying the entire folder of a similar test case and rename and modify application files - - Re-run the cmake in the build folder - $ cmake /path/to/sphinxsys/source-code -DCMAKE_BUILD_TYPE=RelWithDebInfo - - You can make and run your application - $ cd /path/to/sphinxsys-build/cases_user/your_application_folder - $ make -j - $ cd /bin - $ ./your_application - -### Installing on Ubunutu Linux using the dependency-free version - -0. Note: Do not clone the submodules if you are using the default installation - -1. Get all submodules, run this command in the command line of the SPHinXsys project folder + 7. C. Zhang, M. Rezavand, Y. Zhu, Y. Yu, D. Wu, W. Zhang, J. Wang, X. Hu, "SPHinXsys: an open-source multi-physics and multi-resolution library based on smoothed particle hydrodynamics", Computer Physics Communications, 267, 108066, 2021. - $ git submodule update --init --recursive + 8. Yujie Zhu, Chi Zhang, Yongchuan Yu, Xiangyu Hu, "A CAD-compatible body-fitted particle generator for arbitrarily complex geometry and its application to wave-structure interaction", Journal of Hydrodynamics, 33(2), 195-206, 2021. -2. Edit the CMake variables to define which dependency to use. Simbody and/or TBB can be built by the project. If one is not built by the project, install that dependency in the usual way as written before. - - Go to SPHinXsys/cmake/Dependency_free_settings.cmake - Set BUILD_WITH_DEPENDENCIES to 1 - Set BUILD_WITH_SIMBODY to 1 if Simbody should be built by the project - Set BUILD_WITH_ONETBB to 1 if TBB should be built by the project - Set ONLY_3D to 1 if the 2D libraries and test cases are not needed. Note that Boost is still needed if this variable is set to 0 - Do not modify the other variables - -3. Build the SPHinXsys project as described in the previous section - -### Install on Windows Visual Studio - -You can find a installation instruction video: https://youtu.be/m0p1nybM4v4, and install by the following steps: -1. Install latest version Cmake, SmartGit (choose non-commercial option) binary and make sure that google test is installed in Visual Studio. -2. Build, test and install Simbody - - Downloading from https://github.com/simbody/simbody/releases - Unpack to source folder, like: c:\simbody-source - Create build folder, like: c:\simbody-build - Use Cmake, configure with option Visual Studio 2017 x64 and then Generate the solution file for VS2017 (Note that install prefix should be a file folder not in system folder. For example : C:/simbody) - Choose RelWithDebInfo target, Right-clicking ALL_BUILD and selecting build - Right-clicking INSTALL and selecting build. - Choose Debug target (this important for debug with Simbody functions) - Right-clicking ALL_BUILD and selecting build - Right-clicking INSTALL and selecting build. - Set Environment Variable (User Variables) by add an entry SIMBODY_HOME to the simbody directory. - Add the simbody\bin path to Environmental Variable (System variables) - -3. Install TBB library - - Download Binary installer, actually extract the file to the assigned folder , e.g. C:/tbb_version - Set Environment Variable (User Variables): TBB_HOME to the tbb directory - Set the path bin\intel64\vc14 to Environmental Variable (System variables) - -4. Install Boost library - - Download binary and install boost with download from https://sourceforge.net/projects/boost/files/boost-binaries/ - NOTE that you need choose right version for your visual studio. For VS 2019 you choose msvc-14.2-64, VS2017 msvc-14.1-64 - Set Environment Variable (User Variables): BOOST_HOME to its directory - Add the Boost library (lib64 with version) path to Environmental Variable (System variables) - -5. Buidling SPHinXsys project - - Clone SPHinXsys source files to local computer using SmartGit - Remember to create a new build directory outside of the git directory to avoid upload the project files to the - Use Cmake to build project file - Configure x64 build and Generate - After configuration, one can choose debug or release mode of the project file. - -6. Build execute able and run test cases in Visual Studio - -7. Create and build your own application - - Create your own application in the cases_user in the source folder simply by copying the entire folder of a similar test case and rename and modify application files - -### Build with docler - -Two docker files are provided: - -1. Dockerfile: C++ default docker image (x86_64 ubuntu 20.04). Every libraries are installed using debian packages (Simbody is 3.6 instead of 3.7). The docker image size is smallest and suitable for docker hub or CI/CD. - -command to build: - -docker build . -t sphinxsys:latest - -command to run test: - -docker run sphinxsys:latest bash scripts/runTest.sh - -2. dev.Dockerfile: development packages are all installed and simbody is downloaded and compiled in the image building process. This image is too big and can not be used for github testing. Only for local development purposes. +## Software Architecture -command to build: +SPHinXsys is cross-platform can be compiled and used in Windows, Linux and Apple systems. -docker build . -f dev.Dockerfile -t sphinxsys:dev +## Installation and tutorials +For installation, program manual and tutorials, please check https://www.sphinxsys.org/html/sphinx_index.html. -additional build arguement can be added to the end of the docker build command using the following syntax: --build-arg = +## Contribute to SPHinXsys -build_with_dependencies_source=0 : default, builds with preprecompiled dependencies -build_with_dependencies_source=1 : builds dependencies together with Sphinxsys +You are welcomed to contribute to SPHinXsys. +As the code is on git-hub, you can register an account there (if you do not have a github account yet) +and fork out the SPHinXsys repository. +You can work on the forked repository and add new features, and then commit them. -### How to run gpuSPHinXsys cases on CUDA enabled GPUs? -The build process for GPU cases are identical to the CPU cases on all platforms, viz. Linux, Windows and Mac OSX. -However, the following notes must be considered: +### Contribute new features - The flag -DACTIVATE_CUDA=ON must be added to let the compiler find an installed version of CUDA. - CUDA 11.0 or higher has to be installed. - Choose RelWithDebInfo mode to build. - You need to modify the "BUILD_GPU_ARCH" from the master CMake file according to the architecture of your GPU. - Just identify the architecture of your GPU and use the proper value of BUILD_GPU_ARCH according the the available tables like this one: - http://arnon.dk/matching-sm-architectures-arch-and-gencode-for-various-nvidia-cards/ - Two examples are 75 for Turing and 60 for Pascal. +After you have finished a new feature, +which is a commit together with a new test case which uses the new feature, +you can send a pull request to the SPHinXsys repository. +The SPHinXsys team will review the new code and give suggestions for revision, if there is. +After the revision is done, your contribution will be merged into the master branch of the original SPHinXsys repository. +When you write the code, please remember to add your name as an author in the header files that you have contributed. +### Contribute new test cases -## Contribution +Test cases are very important for testing the features and showing the ability of SPHinXsys. +If you think that a cases which implemented by you are very useful for the above propose, +you can move this test into the folder cases_test in your local commit +which is directly branched from the master branch in the original SPHinXsys repository. +After test it, you can carry out a pull request. +Then, a standard review process will be carried out before merge it to the original SPHinXsys repository. +If a new test cases is added, it will be maintained together with code by SPHinXsys team. -Any contribution to SPHinXsys are welcome. For this, you will do the following steps: +### Contribute challenging benchmarks -1. Fork the project & clone locally. -2. Create an upstream remote and sync your local copy before you branch. -3. Branch for each separate piece of work. -4. Do the work, write good commit messages, and read the CONTRIBUTING file if there is one. -5. Push to your origin repository. -6. Create a new PR in GitHub. -7. Respond to any code review feedback. +Improving numerical algorithms is one of our main drives +and we are happy to test challenging cases and to be informed +if there is any other code that does better on any benchmark case. +For this, you can inform us by the contact listed below. +If you have the case implemented, you can do just as contribute a new test case, +except move this case to cases_user folder. +Then, after a standard pull request and review process, +we will merge you code to master branch and discuss with you on the issues of the case +and work together to improve the numerical algorithm for that case. -For more detailed guide, please visit -https://akrabat.com/the-beginners-guide-to-contributing-to-a-github-project/ \ No newline at end of file +If you have any further question, you are also welcomed to contact xiangyu.hu@tum.de. \ No newline at end of file From 8c91b62545b759790cfe8ecfb55096ebd740d8e2 Mon Sep 17 00:00:00 2001 From: Xiangyu Hu Date: Thu, 26 Aug 2021 11:50:47 +0200 Subject: [PATCH 18/40] redirect installation and tutorial to prroject website --- README.md | 98 ------------------------------------------------------- 1 file changed, 98 deletions(-) diff --git a/README.md b/README.md index 88089bebfa..e4cec62dcb 100644 --- a/README.md +++ b/README.md @@ -30,11 +30,7 @@ Software Impacts, 6 (2020) 100033 4. Chi Zhang, Massoud Rezavand, Xiangyu Hu, "A multi-resolution SPH method for fluid-structure interactions", -<<<<<<< HEAD Journal of Computational Physics, Journal of Computational Physics, 429 (2021) 110028. -======= -Journal of Computational Physics, 2021(429) 110028 ->>>>>>> e4a3571838df4a6c121d061d5da795fbc677f134 5. Chi Zhang, Yanji Wei, Frederic Dias, Xiangyu Hu, "An efficient fully Lagrangian solver for modeling wave interaction with oscillating wave energy converter", @@ -43,107 +39,13 @@ Volume 236, (2021) 109540 6. Chi Zhang, Jianhang Wang, Massoud Rezavand, Dong Wu, Xiangyu Hu, "An integrative smoothed particle hydrodynamics framework for modeling cardiac function", -<<<<<<< HEAD Computer Methods in Applied Mechanics and Engineering, 381, 113847, 2021. -======= - Computer Methods in Applied Mechanics and Engineering, 381, 113847, 2021 - - 7. Yujie Zhu, Chi Zhang, Yongchuan Yu, Xiangyu Hu, - “A CAD-compatible body-fitted particle generator for arbitrarily complex geometry and its application to wave-structure interaction”, - Journal of Hydrodynamics, 33(2), 195-206, 2021. ->>>>>>> e4a3571838df4a6c121d061d5da795fbc677f134 7. C. Zhang, M. Rezavand, Y. Zhu, Y. Yu, D. Wu, W. Zhang, J. Wang, X. Hu, "SPHinXsys: an open-source multi-physics and multi-resolution library based on smoothed particle hydrodynamics", Computer Physics Communications, 267, 108066, 2021. 8. Yujie Zhu, Chi Zhang, Yongchuan Yu, Xiangyu Hu, "A CAD-compatible body-fitted particle generator for arbitrarily complex geometry and its application to wave-structure interaction", Journal of Hydrodynamics, 33(2), 195-206, 2021. -<<<<<<< HEAD ## Software Architecture -======= -2. Edit the CMake variables to define which dependency to use. Simbody and/or TBB can be built by the project. If one is not built by the project, install that dependency in the usual way as written before. - - Go to SPHinXsys/cmake/Dependency_free_settings.cmake - Set BUILD_WITH_DEPENDENCIES to 1 - Set BUILD_WITH_SIMBODY to 1 if Simbody should be built by the project - Set BUILD_WITH_ONETBB to 1 if TBB should be built by the project - Set ONLY_3D to 1 if the 2D libraries and test cases are not needed. Note that Boost is still needed if this variable is set to 0 - Do not modify the other variables - -3. Build the SPHinXsys project as described in the previous section - -### Install on Windows Visual Studio - -You can find a installation instruction video: https://youtu.be/m0p1nybM4v4, and install by the following steps: -1. Install latest version Cmake, SmartGit (choose non-commercial option) binary and googel test. - - Install google test, we download the release version from the github reporsitory: , - build and install it. For this, you will extract the source and create a new build directory. - Using Cmake, you will configurate and generate a Visual Studio project. - Be sure that, in Cmake GUI, you have clicked the two options: build_shared_libs and install_gtest. - The install prefix you can choose the default one (in winodws program files and, in this case, you later need run Visual Studio as admistrator) or other new directory. - Open the generated project in Visual Studio, build all and install both for Debug and ReleaseWithDebugInfo targets. - Then, you need setup Windows system environment variables: GTEST_HOME with the vaule of the install prefix directory. - Also you need add the bin directory as new path. the dll files inside need to found when running the tests. - -2. Build, test and install Simbody - - Downloading from https://github.com/simbody/simbody/releases - Unpack to source folder, like: c:\simbody-source - Create build folder, like: c:\simbody-build - Use Cmake, configure with option Visual Studio 2017 x64 and then Generate the solution file for VS2017 (Note that install prefix should be a file folder not in system folder. For example : C:/simbody) - Choose RelWithDebInfo target, Right-clicking ALL_BUILD and selecting build - Right-clicking INSTALL and selecting build. - Choose Debug target (this important for debug with Simbody functions) - Right-clicking ALL_BUILD and selecting build - Right-clicking INSTALL and selecting build. - Set Environment Variable (User Variables) by add an entry SIMBODY_HOME to the simbody directory. - Add the simbody\bin path to Environmental Variable (System variables) - -3. Install TBB library - - Download Binary installer, actually extract the file to the assigned folder , e.g. C:/tbb_version - Set Environment Variable (User Variables): TBB_HOME to the tbb directory - Set the path bin\intel64\vc14 to Environmental Variable (System variables) - -4. Install Boost library - - Download binary and install boost with download from https://sourceforge.net/projects/boost/files/boost-binaries/ - NOTE that you need choose right version for your visual studio. For VS 2019 you choose msvc-14.2-64, VS2017 msvc-14.1-64 - Set Environment Variable (User Variables): BOOST_HOME to its directory - Add the Boost library (lib64 with version) path to Environmental Variable (System variables) - -5. Buidling SPHinXsys project - - Clone SPHinXsys source files to local computer using SmartGit - Remember to create a new build directory outside of the git directory to avoid upload the project files to the - Use Cmake to build project file - Configure x64 build and Generate - After configuration, one can choose debug or release mode of the project file. - Note that you need choose the same debug or release mode in the Visual Studio as you have chosen in Cmake. - Otherwise, it may lead to compiling issues. - -6. Build execute able and run test cases in Visual Studio - -7. Create and build your own application - - Create your own application in the cases_user in the source folder simply by copying the entire folder of a similar test case and rename and modify application files - -### Build with docler - -Two docker files are provided: - -1. Dockerfile: C++ default docker image (x86_64 ubuntu 20.04). Every libraries are installed using debian packages (Simbody is 3.6 instead of 3.7). The docker image size is smallest and suitable for docker hub or CI/CD. - -command to build: - -docker build . -t sphinxsys:latest - -command to run test: - -docker run sphinxsys:latest bash scripts/runTest.sh - -2. dev.Dockerfile: development packages are all installed and simbody is downloaded and compiled in the image building process. This image is too big and can not be used for github testing. Only for local development purposes. ->>>>>>> e4a3571838df4a6c121d061d5da795fbc677f134 SPHinXsys is cross-platform can be compiled and used in Windows, Linux and Apple systems. From 8bf26d40ff7477edf8253812ecfba640299b4db0 Mon Sep 17 00:00:00 2001 From: Xiangyu Hu Date: Thu, 26 Aug 2021 11:54:06 +0200 Subject: [PATCH 19/40] rearrange readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e4cec62dcb..6af15ed3b9 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,13 @@ SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle Hydrodynamics for industrial compleX systems. It provides C++ APIs for physical accurate simulation and aims to model coupled industrial dynamic systems including fluid, solid, multi-body dynamics and beyond with SPH (smoothed particle hydrodynamics), a meshless computational method using particle discretization. -Please check the documentation of the code at https://xiangyu-hu.github.io/SPHinXsys/. For more information on the SPHinXsys project, please check the project website: https://www.sphinxsys.org. SPHinXsys is a multi-physics, multi-resolution SPH library. Although it is not a standalone application itself, many examples designated for the specific type of applications are provided. -## Algorithm publications +## Journal publications The algorithms in SPHinXsys are based on the following publications: @@ -49,9 +48,10 @@ Volume 236, (2021) 109540 SPHinXsys is cross-platform can be compiled and used in Windows, Linux and Apple systems. -## Installation and tutorials +## Installation, tutorial and documentation For installation, program manual and tutorials, please check https://www.sphinxsys.org/html/sphinx_index.html. +Please check the documentation of the code at https://xiangyu-hu.github.io/SPHinXsys/. ## Contribute to SPHinXsys From 0ad06c3b206eba12bbeea10c7147a05742bbc2f9 Mon Sep 17 00:00:00 2001 From: alundilong Date: Thu, 26 Aug 2021 19:59:46 +0800 Subject: [PATCH 20/40] refactor by add image_mhd to handle image data --- .../geometries/complex_shape_image_mesh.cpp | 15 +- .../geometries/image_mesh_shape.cpp | 457 +--------------- .../geometries/image_mesh_shape.h | 30 +- SPHINXsys/src/shared/common/image_mhd.h | 154 ++++++ SPHINXsys/src/shared/common/image_mhd.hpp | 492 ++++++++++++++++++ tests/3d_examples/test_3d_load_image/case.h | 6 +- .../test_3d_load_image/data/teapot.stl | Bin 0 -> 471984 bytes 7 files changed, 688 insertions(+), 466 deletions(-) create mode 100644 SPHINXsys/src/shared/common/image_mhd.h create mode 100644 SPHINXsys/src/shared/common/image_mhd.hpp create mode 100644 tests/3d_examples/test_3d_load_image/data/teapot.stl diff --git a/SPHINXsys/src/for_3D_build/geometries/complex_shape_image_mesh.cpp b/SPHINXsys/src/for_3D_build/geometries/complex_shape_image_mesh.cpp index b76ee5323a..72ff6e6388 100644 --- a/SPHINXsys/src/for_3D_build/geometries/complex_shape_image_mesh.cpp +++ b/SPHINXsys/src/for_3D_build/geometries/complex_shape_image_mesh.cpp @@ -85,24 +85,23 @@ namespace SPH //=================================================================================================// void ComplexShapeImageMesh::addBrick(Vec3d halfsize, int resolution, Vec3d translation, Mat3d rotation, ShapeBooleanOps op) { - ImageMeshShape* image_mesh_shape = new ImageMeshShape(halfsize, resolution, translation, rotation); - std::pair shape_and_op(image_mesh_shape, op); - image_mesh_shapes_.push_back(shape_and_op); + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + throw std::runtime_error("addBrick is not implemented"); } //=================================================================================================// void ComplexShapeImageMesh::addSphere(Real radius, int resolution, Vec3d translation, Mat3d rotation, ShapeBooleanOps op) { - ImageMeshShape* image_mesh_shape = new ImageMeshShape(radius, resolution, translation, rotation); + Vec3d spacings(resolution,resolution,resolution); + Vec3d center(translation); + ImageMeshShape* image_mesh_shape = new ImageMeshShape(radius, spacings, center); std::pair shape_and_op(image_mesh_shape, op); image_mesh_shapes_.push_back(shape_and_op); } //=================================================================================================// void ComplexShapeImageMesh::addCylinder(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation, Mat3d rotation, ShapeBooleanOps op) { - /** Here SimTK::UnitVec3 give the direction of the cylinder, viz. SimTK::UnitVec3(0,0,1) create a cylinder in z-axis.*/ - ImageMeshShape* image_mesh_shape = new ImageMeshShape(axis, radius, halflength, resolution, translation, rotation); - std::pair shape_and_op(image_mesh_shape, op); - image_mesh_shapes_.push_back(shape_and_op); + std::cout << __FILE__ << ':' << __LINE__ << std::endl; + throw std::runtime_error("addCylinder is not implemented"); } //=================================================================================================// void ComplexShapeImageMesh::addComplexShapeImageMesh(ComplexShapeImageMesh* complex_shape_image_mesh, ShapeBooleanOps op) diff --git a/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.cpp b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.cpp index c2f1c79414..27d86fff83 100644 --- a/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.cpp +++ b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.cpp @@ -4,236 +4,38 @@ namespace SPH { //=================================================================================================// ImageMeshShape::ImageMeshShape(std::string file_path_name) : - origin_(0.0, 0.0, 0.0), - translation_(0.0, 0.0, 0.0), + translation_(0.0), rotation_(1.0), - spacing_(1.0, 1.0, 1.0), - dimensions_(1, 1, 1), - data_(nullptr), - size_(1), - width_(1), - height_(1), - depth_(1), + image_(nullptr), max_distance_(-INFINITY), min_distance_(INFINITY) { - //- read mhd file - std::ifstream dataFile(file_path_name, std::ifstream::in); - std::string file_path_name_raw; - if(dataFile.is_open()) - { - std::string line; - std::vector values; - while(std::getline(dataFile, line)) - { - std::stringstream ss(line); - std::vector elements; - split(line, '=', elements); - if(elements.size() == 2) - { - if(elements[0].compare("TransformMatrix") == 0) - { - std::vector values; - split(elements[1], ' ', values); - rotation_[0][0] = std::stof(values[0]); - rotation_[0][1] = std::stof(values[1]); - rotation_[0][2] = std::stof(values[2]); - rotation_[1][0] = std::stof(values[3]); - rotation_[1][1] = std::stof(values[4]); - rotation_[1][2] = std::stof(values[5]); - rotation_[2][0] = std::stof(values[6]); - rotation_[2][1] = std::stof(values[7]); - rotation_[2][2] = std::stof(values[8]); - } - else if(elements[0].compare("Offset") == 0) - { - std::vector values; - split(elements[1], ' ', values); - translation_[0] = std::stof(values[0]); - translation_[1] = std::stof(values[1]); - translation_[2] = std::stof(values[2]); - } - else if(elements[0].compare("CenterOfRotation") == 0) - { - std::vector values; - split(elements[1], ' ', values); - origin_[0] = std::stof(values[0]); - origin_[1] = std::stof(values[1]); - origin_[2] = std::stof(values[2]); - } - else if(elements[0].compare("ElementSpacing") == 0) - { - std::vector values; - split(elements[1], ' ', values); - spacing_[0] = std::stof(values[0]); - spacing_[1] = std::stof(values[1]); - spacing_[2] = std::stof(values[2]); - } - else if(elements[0].compare("DimSize") == 0) - { - std::vector values; - split(elements[1], ' ', values); - dimensions_[0] = std::stoi(values[0]); - dimensions_[1] = std::stoi(values[1]); - dimensions_[2] = std::stoi(values[2]); - - data_ = new float[dimensions_[0]*dimensions_[1]*dimensions_[2]]; - } - else if(elements[0].compare("ElementDataFile") == 0) - { - file_path_name_raw = file_path_name +'/'+ elements[1]; - } - } - } - } - dataFile.close(); - - //- read raw file - std::ifstream dataFileRaw(file_path_name_raw, std::ifstream::in || std::ifstream::binary); - - dataFileRaw.seekg(0, std::ifstream::end); - int size = (int)dataFileRaw.tellg(); - dataFileRaw.seekg(0, std::ifstream::beg); - if(dataFileRaw.is_open()) - { - int index = 0; - while(dataFileRaw.tellg() < size) - { - char buffer[32]; - dataFileRaw.read(buffer, sizeof(data_[index])); - data_[index] = (float)std::strtod(buffer, NULL); - index++; - } - - } - dataFileRaw.close(); + if (image_ == nullptr) + image_ = new ImageMHD(file_path_name); } //=================================================================================================// - ImageMeshShape::ImageMeshShape(Vec3d halfsize, int resolution, Vec3d translation, Mat3d rotation) : - origin_(0.0, 0.0, 0.0), - translation_(translation), + ImageMeshShape::ImageMeshShape(Real radius, Vec3d spacings, Vec3d center) : + translation_(0.0), rotation_(1.0), - spacing_(1.0, 1.0, 1.0), - dimensions_(1, 1, 1), - data_(nullptr), - size_(1), - width_(1), - height_(1), - depth_(1), - max_distance_(-INFINITY), - min_distance_(INFINITY) - { - - } - //=================================================================================================// - ImageMeshShape::ImageMeshShape(Real radius, int resolution, Vec3d translation, Mat3d rotation) : - origin_(radius, radius, radius), - translation_(translation), - rotation_(rotation), - spacing_(1.0, 1.0, 1.0), - dimensions_(1, 1, 1), - data_(nullptr), - size_(1), - width_(1), - height_(1), - depth_(1), + image_(nullptr), max_distance_(-INFINITY), min_distance_(INFINITY) { - // origin_ = origin; - rotation_ = rotation; - translation_ = translation; - int length = int(std::ceil(3.0*radius)); - dimensions_ = Vec3i(length, length, length); - width_ = dimensions_[0]; - height_ = dimensions_[1]; - depth_ = dimensions_[2]; - size_ = width_*height_*depth_; - data_ = new float[size_]; - - std::ofstream output_file("sphere.mhd",std::ofstream::out); - output_file << "ObjectType = Image" << "\n"; - output_file << "NDims = 3" << "\n"; - output_file << "BinaryData = True" << "\n"; - output_file << "BinaryDataByteOrderMSB = False" << "\n"; - output_file << "CompressedData = False" << "\n"; - output_file << "TransformMatrix = " - << rotation[0][0] << " " << rotation[0][1] << " " << rotation[0][2] << " " - << rotation[1][0] << " " << rotation[1][1] << " " << rotation[1][2] << " " - << rotation[2][0] << " " << rotation[2][1] << " " << rotation[2][2] << "\n"; - output_file << "Offset = " - << translation[0] << " " << translation[1] << " " << translation[2] << "\n"; - output_file << "CenterOfRotation = " - << origin_[0] << " " << origin_[1] << " " << origin_[2] << "\n"; - output_file << "ElementSpacing = " - << spacing_[0] << " " << spacing_[1] << " " << spacing_[2] << "\n"; - output_file << "DimSize = " - << dimensions_[0] << " " << dimensions_[1] << " " << dimensions_[2] << "\n"; - output_file << "AnatomicalOrientation = ??? " << "\n"; - output_file << "ElementType = MET_FLOAT" << "\n"; - output_file << "ElementDataFile = sphere.raw" << "\n"; - - output_file.close(); - std::ofstream output_file_raw("sphere.raw", std::ofstream::binary); - Vec3d center(0.5*width_, 0.5*height_, 0.5*depth_); - - for(int z = 0; z < depth_; z++) - { - for (int y = 0; y < height_; y++) - { - for (int x = 0; x < width_; x++) - { - int index = z*width_*height_ + y*width_ + x; - double distance = (Vec3d(x,y,z) - center).norm()-radius; - if(distance < min_distance_) min_distance_ = distance; - if(distance > max_distance_) max_distance_ = distance; - data_[index] = float(distance); - - //output_file << data_[index] << " "; - //char array[10]; - //sprintf(array, "%f", data_[index]); - //std::cout << array << " " << data_[index] << std::endl; - output_file_raw.write((char*)(&(data_[index])), sizeof(data_[index])); - } - } - } - output_file_raw.close(); - } - //=================================================================================================// - ImageMeshShape::ImageMeshShape(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation, Mat3d rotation) : - origin_(0.0, 0.0, 0.0), - translation_(translation), - rotation_(rotation), - spacing_(1.0, 1.0, 1.0), - dimensions_(1, 1, 1), - data_(nullptr), - size_(1), - width_(1), - height_(1), - depth_(1), - max_distance_(-INFINITY), - min_distance_(INFINITY) - { - + double extend = 1.5; + int length = int(std::ceil(2.0*extend*radius)); + Vec3i NxNyNz(length, length, length); + if(image_ == nullptr) + image_ = new ImageMHD(radius, NxNyNz,spacings); } //=================================================================================================// ImageMeshShape::~ImageMeshShape() { - if(data_) + if (image_) { - delete data_; - data_ = nullptr; + delete image_; + image_ = nullptr; } } - //=================================================================================================// - void ImageMeshShape::split(const std::string &s, char delim, std::vector &elems) - { - std::stringstream ss(s); - std::string item; - while(std::getline(ss, item, delim)) { - elems.push_back(item); - } - } //=================================================================================================// bool ImageMeshShape::checkContain(const Vec3d& input_pnt, bool BOUNDARY_INCLUDED) { @@ -252,243 +54,22 @@ namespace SPH //=================================================================================================// Vec3d ImageMeshShape::findClosestPoint(const Vec3d& input_pnt) { - Vec3i this_cell; - std::vector neighbors = findNeighbors(input_pnt, this_cell); - Vec3d n_sum(0.0, 0.0, 0.0); - double weight_sum = 0.0; - double d_sum = 0.0; - for (const int& i : neighbors) - { - // checkIndexBound(i); - Vec3d nCj = computeNormalAtCell(i); - double dCj = float(getValueAtCell(i)); - double weight_Cj = 1.0/(fabs(dCj)+Eps); - n_sum = n_sum + weight_Cj*nCj; - weight_sum = weight_sum + weight_Cj; - d_sum = d_sum + dCj; - } - Vec3d n = n_sum/(weight_sum+Eps); - double d = d_sum/(weight_sum+Eps); - - Vec3d p_image = Vec3d(this_cell[0], this_cell[1], this_cell[2]) + n.normalize()*d; - Vec3d p = convertToPhysicalSpace(p_image); - return p; - + return image_->findClosestPoint(input_pnt); } //=================================================================================================// BoundingBox ImageMeshShape::findBounds() { - //initial reference values - Vec3d lower_bound = Vec3d(Infinity); - Vec3d upper_bound = Vec3d(-Infinity); - - for(int z = 0; z < depth_+1; z++) - { - for(int y = 0; y < height_+1; y++) - { - for(int x = 0; x < width_+1; x++) - { - Vec3d p_image = Vec3d(x, y, z); - Vec3d vertex_position = convertToPhysicalSpace(p_image); - for (int j = 0; j != 3; ++j) { - lower_bound[j] = SMIN(lower_bound[j], vertex_position[j]); - upper_bound[j] = SMAX(upper_bound[j], vertex_position[j]); - } - } - } - } - - return BoundingBox(lower_bound, upper_bound); - + return image_->findBounds(); } //=================================================================================================// Real ImageMeshShape::findValueAtPoint(const Vec3d& input_pnt) { - Vec3i this_cell; - std::vector neighbors = findNeighbors(input_pnt, this_cell); - double weight_sum = 0.0; - double d_sum = 0.0; - if(neighbors.size() > 0) - { - for (const int& i : neighbors) - { - // checkIndexBound(i); - double dCj = float(getValueAtCell(i)); - double weight_Cj = 1.0/(fabs(dCj)+Eps); - weight_sum = weight_sum + weight_Cj; - d_sum = d_sum + dCj; - } - return d_sum/(weight_sum+Eps); - } - else - { - return max_distance_; - } - + return image_->findValueAtPoint(input_pnt); } //=================================================================================================// Vec3d ImageMeshShape::findNormalAtPoint(const Vec3d & input_pnt) { - Vec3i this_cell; - std::vector neighbors = findNeighbors(input_pnt, this_cell); - Vec3d n_sum(0.0, 0.0, 0.0); - double weight_sum = 0.0; - double d_sum = 0.0; - if( neighbors.size() > 0) - { - for (const int& i : neighbors) - { - // checkIndexBound(i); - Vec3d nCj = computeNormalAtCell(i); - double dCj = float(getValueAtCell(i)); - double weight_Cj = 1.0/(fabs(dCj)+Eps); - n_sum = n_sum + weight_Cj*nCj; - weight_sum = weight_sum + weight_Cj; - d_sum = d_sum + dCj; - } - Vec3d n = n_sum/(weight_sum+Eps); - return n.normalize(); - } - else - { - return Vec3d(1.0,1.0,1.0).normalize(); - } - - } - //=================================================================================================// - std::vector ImageMeshShape::findNeighbors(const Vec3d& input_pnt, Vec3i& this_cell) - { - std::vector neighbors; - - Vec3d image_coord = rotation_.invert()*(input_pnt - translation_); - // std::cout <<"findNeighbor of " << input_pnt << " ........... " << image_coord << std::endl; - - int z = int(floor(image_coord[2])); - int y = int(floor(image_coord[1])); - int x = int(floor(image_coord[0])); - - //- cannot count cells in buffer zone - if (x < 0 || x > width_ - 1 || y < 0 || y > height_ - 1 || z < 0 || z > depth_ - 1) return neighbors; - - for (int k = z - 1; k < z + 2; k = k + 2) - { - for (int j = y - 1; j < y + 2; j = j + 2) - { - for (int i = x - 1; i < x + 2; i = i + 2) - { - if (i<0 || i >width_ - 1 || j <0 || j >height_ - 1 || k<0 || k >depth_) - continue; - int index = z * width_*height_ + y * width_ + x; - neighbors.push_back(index); - } - } - - } - - return neighbors; - - } - //=================================================================================================// - Vec3d ImageMeshShape::computeGradientAtCell(int i) - { - //- translate 1D index to 3D index - int width = width_; - int height = height_; - int depth = depth_; - int sliceSize = width*height; - int z = i/sliceSize; - int y = (i%sliceSize)/width; - int x = (i%sliceSize)%width; - - double gradx = 0.0; double grady = 0.0; double gradz = 0.0; - //- cds (if inner cell) - //- otherwise back/forward scheme - if(x == 0) - { - int indexHigh = z*sliceSize + y*width + (x+1); - gradx = (getValueAtCell(indexHigh) - getValueAtCell(i)); - } - else if(x == width - 1) - { - int indexLow = z*sliceSize + y*width + (x-1); - gradx = -(getValueAtCell(indexLow) - getValueAtCell(i)); - } - else if (x > 0 && x < width_ - 1) - { - int indexHigh = z*sliceSize + y*width + (x+1); - int indexLow = z*sliceSize + y*width + (x-1); - gradx = (getValueAtCell(indexHigh) - getValueAtCell(indexLow))/2.0; - } - - if(y == 0) - { - int indexHigh = z*sliceSize + (y+1)*width + x; - grady = (getValueAtCell(indexHigh) - getValueAtCell(i)); - } - else if(y == height - 1) - { - int indexLow = z*sliceSize + (y-1)*width + x; - grady = -(getValueAtCell(indexLow) - getValueAtCell(i)); - } - else if (y > 0 && y < height_ - 1) - { - int indexHigh = z*sliceSize + (y+1)*width + x; - int indexLow = z*sliceSize + (y-1)*width + x; - grady = (getValueAtCell(indexHigh) - getValueAtCell(indexLow))/2.0; - } - - if(z == 0) - { - int indexHigh = (z+1)*sliceSize + y*width + x; - gradz = (getValueAtCell(indexHigh) - getValueAtCell(i)); - } - else if(z == depth - 1) - { - int indexLow = (z-1)*sliceSize + y*width + x; - gradz = -(getValueAtCell(indexLow) - getValueAtCell(i)); - } - else if (z > 0 && z < depth_ - 1) - { - int indexHigh = (z+1)*sliceSize + y*width + x; - int indexLow = (z-1)*sliceSize + y*width + x; - gradz = (getValueAtCell(indexHigh) - getValueAtCell(indexLow))/2.0; - } - gradx = gradx / spacing_[0]; - grady = grady / spacing_[1]; - gradz = gradz / spacing_[2]; - return Vec3d(gradx, grady, gradz); - - } - //=================================================================================================// - Vec3d ImageMeshShape::computeNormalAtCell(int i) - { - Vec3d grad_phi = computeGradientAtCell(i); - Vec3d n = grad_phi.normalize(); - return n; - } - - float ImageMeshShape::getValueAtCell(int i) - { - if(i < 0 || i > size_) - { - return float(max_distance_); - } - else - { - return data_[i]; - } - - } - //=================================================================================================// - Vec3d ImageMeshShape::convertToPhysicalSpace(Vec3d p) - { - Vec3d position = rotation_*p + translation_; - for(int i = 0; i < position.size(); i++) - { - position[i] = position[i]*spacing_[i]; - } - return position; - + return image_->findNormalAtPoint(input_pnt); } //=================================================================================================// diff --git a/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h index a7e4a21571..c737092d41 100644 --- a/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h +++ b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h @@ -39,6 +39,8 @@ #include "Simbody.h" #include "simbody_middle.h" #include "geometry.h" +#include "image_mhd.h" +#include "base_data_type.h" #include #include @@ -61,11 +63,8 @@ namespace SPH public: //constructor for load mhd/raw file from out side ImageMeshShape(std::string file_path_name); - ImageMeshShape(Vec3d halfsize, int resolution, Vec3d translation, Mat3d rotation); - ImageMeshShape(Real radius, int resolution, Vec3d translation, Mat3d rotation); - ImageMeshShape(SimTK::UnitVec3 axis, Real radius, Real halflength, int resolution, Vec3d translation, Mat3d rotation); + ImageMeshShape(Real radius, Vec3d spacings, Vec3d center); virtual ~ImageMeshShape(); - void ImageMeshShape::split(const std::string &s, char delim, std::vector &elems); bool checkContain(const Vec3d& input_pnt, bool BOUNDARY_INCLUDED = true); Vec3d findClosestPoint(const Vec3d& input_pnt); @@ -75,25 +74,20 @@ namespace SPH Vec3d findNormalAtPoint(const Vec3d & input_pnt); protected: - Vec3d origin_; + //- distance map has to be float type image + ImageMHD *image_; Vec3d translation_; Mat3d rotation_; - Vec3d spacing_; - Vec3i dimensions_; - float *data_; - int size_; - int width_; - int height_; - int depth_; + Real max_distance_; Real min_distance_; - private: - std::vector findNeighbors(const Vec3d& input_pnt, Vec3i& this_cell); - Vec3d computeGradientAtCell(int i); - Vec3d computeNormalAtCell(int i); - float getValueAtCell(int i); - Vec3d convertToPhysicalSpace(Vec3d p); +// private: + //std::vector findNeighbors(const Vec3d& input_pnt, Vec3i& this_cell); + //Vec3d computeGradientAtCell(int i); + //Vec3d computeNormalAtCell(int i); + //float getValueAtCell(int i); + //Vec3d convertToPhysicalSpace(Vec3d p); // void createSphere(double radius = 10.0, Vec3d origin=Vec3d(25.0,25.0,25.0), \ // Mat3d rotation=Mat3d(1.0), \ // Vec3d translation=Vec3d(0.0, 0.0, 0.0), \ diff --git a/SPHINXsys/src/shared/common/image_mhd.h b/SPHINXsys/src/shared/common/image_mhd.h new file mode 100644 index 0000000000..4a33744a20 --- /dev/null +++ b/SPHINXsys/src/shared/common/image_mhd.h @@ -0,0 +1,154 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +#ifndef IMAGE_MHD_H +#define IMAGE_MHD_H + +#include "small_vectors.h" +#include "sph_data_containers.h" + +#include +#include +#include + +namespace SPH { + + enum Image_Data_Type + { + MET_FLOAT, + MET_UCHAR, + MET_LONG + }; + + template + class ImageMHD + { + public: + ImageMHD() {}; + // constructor for input files + ImageMHD(std::string full_path_file); + // constructor for sphere + ImageMHD(Real radius, Vec3i dxdydz, Vec3d spacings); + //ImageMHD(Vec3i dxdydz, Vec3d spacings, T*data); + ~ImageMHD(); + + void set_objectType(std::string objectType) + { + objectType_ = objectType; + }; + void set_ndims(int ndims) + { + ndims_ = ndims; + }; + void set_binaryData(bool binaryData) + { + binaryData_ = binaryData; + }; + void set_binaryDataByteOrderMSB(bool binaryDataByteOrderMSB) + { + binaryDataByteOrderMSB_ = binaryDataByteOrderMSB; + }; + void set_compressedData(bool compressedData) + { + compressedData_ = compressedData; + }; + void set_transformMatrix(Mat3d transformMatrix) + { + transformMatrix_ = transformMatrix; + }; + void set_offset(Vec3d offset) + { + offset_ = offset; + }; + void set_centerOfRotation(Vec3d centerOfRotation) + { + centerOfRotation_ = centerOfRotation; + }; + void set_elementSpacing(Vec3d elementSpacing) + { + elementSpacing_ = elementSpacing; + }; + void set_dimSize(Vec3d dimSize) + { + dimSize_ = dimSize; + }; + void set_anatomicalOrientation(std::string anatomicalOrientation) + { + anatomicalOrientation_ = anatomicalOrientation + }; + void set_elementType(std::string elementType) + { + elementType_ = elementType; + }; + void set_elementDataFile(std::string elementDataFile) + { + elementDataFile_ = elementDataFile; + }; + + + T* get_data() { return data_; }; + + int get_size() { return size_; } + + Real get_min_value() { return min_value_; }; + Real get_max_value() { return max_value_; }; + + Vec3d findClosestPoint(const Vec3d& input_pnt); + BoundingBox findBounds(); + Real findValueAtPoint(const Vec3d& input_pnt); + Vec3d findNormalAtPoint(const Vec3d & input_pnt); + + private: + std::string objectType_; + int ndims_; + bool binaryData_; + bool binaryDataByteOrderMSB_; + bool compressedData_; + Mat3d transformMatrix_; + Vec3d offset_; + Vec3d centerOfRotation_; + Vec3d elementSpacing_; + Vec3i dimSize_; + int width_; + int height_; + int depth_; + int size_; + std::string anatomicalOrientation_; + Image_Data_Type elementType_; + std::string elementDataFile_; + Real min_value_; + Real max_value_; + T *data_; + + std::vector findNeighbors(const Vec3d& input_pnt, Vec3i& this_cell); + Vec3d computeGradientAtCell(int i); + Vec3d computeNormalAtCell(int i); + T getValueAtCell(int i); + Vec3d convertToPhysicalSpace(Vec3d p); + void split(const std::string &s, char delim, std::vector &elems); + }; + +} + +#include "image_mhd.hpp" + +#endif //IMAGE_MHD_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/common/image_mhd.hpp b/SPHINXsys/src/shared/common/image_mhd.hpp new file mode 100644 index 0000000000..6e242dbc2a --- /dev/null +++ b/SPHINXsys/src/shared/common/image_mhd.hpp @@ -0,0 +1,492 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ + +#pragma once + +#include "image_mhd.h" +#include "boost/algorithm/string.hpp" + +namespace SPH { + + template + ImageMHD::ImageMHD(std::string full_path_to_file): + objectType_("Image"), + ndims_(3), + binaryData_(true), + binaryDataByteOrderMSB_(false), + compressedData_(false), + transformMatrix_(Mat3d(1.0)), + offset_(Vec3d(0.0,0.0,0.0)), + centerOfRotation_(Vec3d(0.0,0.0,0.0)), + elementSpacing_(Vec3d(1.0,1.0,1.0)), + dimSize_(Vec3i(1,1,1)), + width_(dimSize_[0]), + height_(dimSize_[1]), + depth_(dimSize_[2]), + size_(dimSize_[0] * dimSize_[1] * dimSize_[2]), + anatomicalOrientation_("???"), + elementType_(MET_FLOAT), + elementDataFile_(""), + min_value_(INFINITE), + max_value_(-INFINITE), + data_(nullptr) + { + //- read mhd file + std::ifstream dataFile(full_path_to_file, std::ifstream::in); + std::string file_path_to_raw_file; + if (dataFile.is_open()) + { + std::string line; + std::vector values; + while (std::getline(dataFile, line)) + { + std::stringstream ss(line); + std::vector elements; + split(line, '=', elements); + if (elements.size() == 2) + { + boost::trim(elements[0]); + boost::trim(elements[1]); + if (elements[0].compare("TransformMatrix") == 0) + { + std::vector values; + split(elements[1], ' ', values); + transformMatrix_[0][0] = std::stof(values[0]); + transformMatrix_[0][1] = std::stof(values[1]); + transformMatrix_[0][2] = std::stof(values[2]); + transformMatrix_[1][0] = std::stof(values[3]); + transformMatrix_[1][1] = std::stof(values[4]); + transformMatrix_[1][2] = std::stof(values[5]); + transformMatrix_[2][0] = std::stof(values[6]); + transformMatrix_[2][1] = std::stof(values[7]); + transformMatrix_[2][2] = std::stof(values[8]); + } + else if (elements[0].compare("Offset") == 0) + { + std::vector values; + split(elements[1], ' ', values); + offset_[0] = std::stof(values[0]); + offset_[1] = std::stof(values[1]); + offset_[2] = std::stof(values[2]); + } + else if (elements[0].compare("ElementSpacing") == 0) + { + std::vector values; + split(elements[1], ' ', values); + elementSpacing_[0] = std::stof(values[0]); + elementSpacing_[1] = std::stof(values[1]); + elementSpacing_[2] = std::stof(values[2]); + } + else if (elements[0].compare("DimSize") == 0) + { + std::vector values; + split(elements[1], ' ', values); + dimSize_[0] = std::stoi(values[0]); + dimSize_[1] = std::stoi(values[1]); + dimSize_[2] = std::stoi(values[2]); + width_ = dimSize_[0]; + height_ = dimSize_[1]; + depth_ = dimSize_[2]; + size_ = width_ * height_*depth_; + if(data_ == nullptr) + data_ = new T[dimSize_[0] * dimSize_[1] * dimSize_[2]]; + } + else if (elements[0].compare("ElementDataFile") == 0) + { + file_path_to_raw_file = full_path_to_file + '/' + elements[1]; + } + } + } + } + + dataFile.close(); + std::cout << "dimensions: " << dimSize_ << std::endl; + std::cout << "spacing: " << elementSpacing_ << std::endl; + std::cout << "offset: " << offset_ << std::endl; + std::cout << "transformMatrix: " << transformMatrix_ << std::endl; + + //- read raw file + std::ifstream dataFileRaw(file_path_to_raw_file, std::ifstream::in || std::ifstream::binary); + + dataFileRaw.seekg(0, std::ifstream::end); + int size = (int)dataFileRaw.tellg(); + dataFileRaw.seekg(0, std::ifstream::beg); + if (dataFileRaw.is_open()) + { + int index = 0; + while (dataFileRaw.tellg() < size) + { + char buffer[32]; + dataFileRaw.read(buffer, sizeof(data_[index])); + data_[index] = (float)std::strtod(buffer, NULL); + std::cout << data_[index] << std::endl; + index++; + } + } + dataFileRaw.close(); + } + template + ImageMHD::ImageMHD(Real radius, Vec3i NxNyNz, Vec3d spacings): + objectType_("Image"), + ndims_(3), + binaryData_(true), + binaryDataByteOrderMSB_(false), + compressedData_(false), + transformMatrix_(Mat3d(1.0)), + offset_(Vec3d(-0.5*NxNyNz[0]*spacings[0], -0.5*NxNyNz[1] * spacings[1], -0.5*NxNyNz[1] * spacings[1])), + centerOfRotation_(Vec3d(0.0, 0.0, 0.0)), + elementSpacing_(spacings), + dimSize_(NxNyNz), + width_(dimSize_[0]), + height_(dimSize_[1]), + depth_(dimSize_[2]), + size_(width_*height_*depth_), + anatomicalOrientation_("???"), + elementType_(MET_FLOAT), + elementDataFile_(""), + min_value_(INFINITE), + max_value_(-INFINITE), + data_(nullptr) + { + if(data_ == nullptr) + data_ = new float[size_]; + + std::ofstream output_file("sphere.mhd", std::ofstream::out); + output_file << "ObjectType = " << objectType_ << "\n"; + output_file << "NDims = " << ndims_ << "\n"; + output_file << "BinaryData = " << binaryData_ << "\n"; + output_file << "BinaryDataByteOrderMSB = " << binaryDataByteOrderMSB_ << "\n"; + output_file << "CompressedData = " << compressedData_ << "\n"; + output_file << "TransformMatrix = " + << transformMatrix_[0][0] << " " << transformMatrix_[0][1] << " " << transformMatrix_[0][2] << " " + << transformMatrix_[1][0] << " " << transformMatrix_[1][1] << " " << transformMatrix_[1][2] << " " + << transformMatrix_[2][0] << " " << transformMatrix_[2][1] << " " << transformMatrix_[2][2] << "\n"; + output_file << "Offset = " + << offset_[0] << " " << offset_[1] << " " << offset_[2] << "\n"; + output_file << "CenterOfRotation = " + << centerOfRotation_[0] << " " << centerOfRotation_[1] << " " << centerOfRotation_[2] << "\n"; + output_file << "ElementSpacing = " + << elementSpacing_[0] << " " << elementSpacing_[1] << " " << elementSpacing_[2] << "\n"; + output_file << "DimSize = " + << dimSize_[0] << " " << dimSize_[1] << " " << dimSize_[2] << "\n"; + output_file << "AnatomicalOrientation = " << anatomicalOrientation_ << "\n"; + if(elementType_ == MET_FLOAT) + output_file << "ElementType = MET_FLOAT" << "\n"; + else if (elementType_ == MET_UCHAR) + output_file << "ElementType = MET_UCHAR" << "\n"; + if (elementType_ == MET_LONG) + output_file << "ElementType = MET_LONG" << "\n"; + output_file << "ElementDataFile = sphere.raw" << "\n"; + + output_file.close(); + std::ofstream output_file_raw("sphere.raw", std::ofstream::binary); + Vec3d center(0.5*width_, 0.5*height_, 0.5*depth_); + + for (int z = 0; z < depth_; z++) + { + for (int y = 0; y < height_; y++) + { + for (int x = 0; x < width_; x++) + { + int index = z * width_*height_ + y * width_ + x; + double distance = (Vec3d(x, y, z) - center).norm() - radius; + if (distance < min_value_) min_value_ = distance; + if (distance > max_value_) max_value_ = distance; + data_[index] = float(distance); + + output_file_raw.write((char*)(&(data_[index])), sizeof(data_[index])); + } + } + } + output_file_raw.close(); + } + + template + ImageMHD::~ImageMHD() + { + if (data_) + { + delete data_; + data_ = nullptr; + } + } + + //=================================================================================================// + template + std::vector ImageMHD::findNeighbors(const Vec3d& input_pnt, Vec3i& this_cell) + { + std::vector neighbors; + + Vec3d image_coord = transformMatrix_.invert()*(input_pnt - offset_); + // std::cout <<"findNeighbor of " << input_pnt << " ........... " << image_coord << std::endl; + + int z = int(floor(image_coord[2])); + int y = int(floor(image_coord[1])); + int x = int(floor(image_coord[0])); + + //- cannot count cells in buffer zone + if (x < 0 || x > width_ - 1 || y < 0 || y > height_ - 1 || z < 0 || z > depth_ - 1) return neighbors; + + for (int k = z - 1; k < z + 2; k = k + 2) + { + for (int j = y - 1; j < y + 2; j = j + 2) + { + for (int i = x - 1; i < x + 2; i = i + 2) + { + if (i<0 || i >width_ - 1 || j <0 || j >height_ - 1 || k<0 || k >depth_) + continue; + int index = z * width_*height_ + y * width_ + x; + neighbors.push_back(index); + } + } + + } + + return neighbors; + + } + //=================================================================================================// + template + Vec3d ImageMHD::computeGradientAtCell(int i) + { + //- translate 1D index to 3D index + int width = width_; + int height = height_; + int depth = depth_; + int sliceSize = width * height; + int z = i / sliceSize; + int y = (i%sliceSize) / width; + int x = (i%sliceSize) % width; + + double gradx = 0.0; double grady = 0.0; double gradz = 0.0; + //- cds (if inner cell) + //- otherwise back/forward scheme + if (x == 0) + { + int indexHigh = z * sliceSize + y * width + (x + 1); + gradx = (getValueAtCell(indexHigh) - getValueAtCell(i)); + } + else if (x == width - 1) + { + int indexLow = z * sliceSize + y * width + (x - 1); + gradx = -(getValueAtCell(indexLow) - getValueAtCell(i)); + } + else if (x > 0 && x < width_ - 1) + { + int indexHigh = z * sliceSize + y * width + (x + 1); + int indexLow = z * sliceSize + y * width + (x - 1); + gradx = (getValueAtCell(indexHigh) - getValueAtCell(indexLow)) / 2.0; + } + + if (y == 0) + { + int indexHigh = z * sliceSize + (y + 1)*width + x; + grady = (getValueAtCell(indexHigh) - getValueAtCell(i)); + } + else if (y == height - 1) + { + int indexLow = z * sliceSize + (y - 1)*width + x; + grady = -(getValueAtCell(indexLow) - getValueAtCell(i)); + } + else if (y > 0 && y < height_ - 1) + { + int indexHigh = z * sliceSize + (y + 1)*width + x; + int indexLow = z * sliceSize + (y - 1)*width + x; + grady = (getValueAtCell(indexHigh) - getValueAtCell(indexLow)) / 2.0; + } + + if (z == 0) + { + int indexHigh = (z + 1)*sliceSize + y * width + x; + gradz = (getValueAtCell(indexHigh) - getValueAtCell(i)); + } + else if (z == depth - 1) + { + int indexLow = (z - 1)*sliceSize + y * width + x; + gradz = -(getValueAtCell(indexLow) - getValueAtCell(i)); + } + else if (z > 0 && z < depth_ - 1) + { + int indexHigh = (z + 1)*sliceSize + y * width + x; + int indexLow = (z - 1)*sliceSize + y * width + x; + gradz = (getValueAtCell(indexHigh) - getValueAtCell(indexLow)) / 2.0; + } + gradx = gradx / elementSpacing_[0]; + grady = grady / elementSpacing_[1]; + gradz = gradz / elementSpacing_[2]; + return Vec3d(gradx, grady, gradz); + + } + //=================================================================================================// + template + Vec3d ImageMHD::computeNormalAtCell(int i) + { + Vec3d grad_phi = computeGradientAtCell(i); + Vec3d n = grad_phi.normalize(); + return n; + } + + template + T ImageMHD::getValueAtCell(int i) + { + if (i < 0 || i > size_) + { + return float(max_value_); + } + else + { + return data_[i]; + } + + } + //=================================================================================================// + template + Vec3d ImageMHD::convertToPhysicalSpace(Vec3d p) + { + Vec3d position = transformMatrix_ * p + offset_; + for (int i = 0; i < position.size(); i++) + { + position[i] = position[i] * elementSpacing_[i]; + } + return position; + + } + //=================================================================================================// + template + void ImageMHD::split(const std::string &s, char delim, \ + std::vector &elems) + { + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) { + if (item.length() > 0) elems.push_back(item); + } + } + //=================================================================================================// + template + Vec3d ImageMHD::findClosestPoint(const Vec3d& input_pnt) + { + Vec3i this_cell; + std::vector neighbors = findNeighbors(input_pnt, this_cell); + Vec3d n_sum(0.0, 0.0, 0.0); + double weight_sum = 0.0; + double d_sum = 0.0; + for (const int& i : neighbors) + { + // checkIndexBound(i); + Vec3d nCj = computeNormalAtCell(i); + double dCj = float(getValueAtCell(i)); + double weight_Cj = 1.0 / (fabs(dCj) + Eps); + n_sum = n_sum + weight_Cj * nCj; + weight_sum = weight_sum + weight_Cj; + d_sum = d_sum + dCj; + } + Vec3d n = n_sum / (weight_sum + Eps); + double d = d_sum / (weight_sum + Eps); + + Vec3d p_image = Vec3d(this_cell[0], this_cell[1], this_cell[2]) + n.normalize()*d; + Vec3d p = convertToPhysicalSpace(p_image); + return p; + + } + + template + BoundingBox ImageMHD::findBounds() + { + //initial reference values + Vec3d lower_bound = Vec3d(Infinity); + Vec3d upper_bound = Vec3d(-Infinity); + + for (int z = 0; z < depth_ + 1; z++) + { + for (int y = 0; y < height_ + 1; y++) + { + for (int x = 0; x < width_ + 1; x++) + { + Vec3d p_image = Vec3d(x, y, z); + Vec3d vertex_position = convertToPhysicalSpace(p_image); + for (int j = 0; j != 3; ++j) { + lower_bound[j] = SMIN(lower_bound[j], vertex_position[j]); + upper_bound[j] = SMAX(upper_bound[j], vertex_position[j]); + } + } + } + } + return BoundingBox(lower_bound, upper_bound); + } + + template + Real ImageMHD::findValueAtPoint(const Vec3d& input_pnt) + { + Vec3i this_cell; + std::vector neighbors = findNeighbors(input_pnt, this_cell); + double weight_sum = 0.0; + double d_sum = 0.0; + if (neighbors.size() > 0) + { + for (const int& i : neighbors) + { + // checkIndexBound(i); + double dCj = float(getValueAtCell(i)); + double weight_Cj = 1.0 / (fabs(dCj) + Eps); + weight_sum = weight_sum + weight_Cj; + d_sum = d_sum + dCj; + } + return d_sum / (weight_sum + Eps); + } + else + { + return max_value_; + } + + } + //=================================================================================================// + template + Vec3d ImageMHD::findNormalAtPoint(const Vec3d & input_pnt) + { + Vec3i this_cell; + std::vector neighbors = findNeighbors(input_pnt, this_cell); + Vec3d n_sum(0.0, 0.0, 0.0); + double weight_sum = 0.0; + double d_sum = 0.0; + if (neighbors.size() > 0) + { + for (const int& i : neighbors) + { + // checkIndexBound(i); + Vec3d nCj = computeNormalAtCell(i); + double dCj = float(getValueAtCell(i)); + double weight_Cj = 1.0 / (fabs(dCj) + Eps); + n_sum = n_sum + weight_Cj * nCj; + weight_sum = weight_sum + weight_Cj; + d_sum = d_sum + dCj; + } + Vec3d n = n_sum / (weight_sum + Eps); + return n.normalize(); + } + else + { + return Vec3d(1.0, 1.0, 1.0).normalize(); + } + } +} diff --git a/tests/3d_examples/test_3d_load_image/case.h b/tests/3d_examples/test_3d_load_image/case.h index 1e18e3042e..4b27502863 100644 --- a/tests/3d_examples/test_3d_load_image/case.h +++ b/tests/3d_examples/test_3d_load_image/case.h @@ -28,9 +28,11 @@ BoundingBox system_domain_bounds(domain_lower_bound, domain_upper_bound); ImageMeshShape *CreateImportedModelSurface() { - Vecd translation(0.0, 0.0, 0.0); + double radius = 10.0; + Vec3d center(0.0, 0.0, 0.0); + Vec3d spacings(1.0, 1.0, 1.0); ImageMeshShape *geometry_imported_model = \ - new ImageMeshShape(10.0, 1.0, Vec3d(-10.0, -10.0, -10.0), Mat3d(1.0)); + new ImageMeshShape(radius, spacings, center); return geometry_imported_model; } diff --git a/tests/3d_examples/test_3d_load_image/data/teapot.stl b/tests/3d_examples/test_3d_load_image/data/teapot.stl new file mode 100644 index 0000000000000000000000000000000000000000..d2259187e6d68cafdc9a6e7f7f3ce29271410e59 GIT binary patch literal 471984 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⪙uWiA*&n=75k-4Mb-k0(Vxh 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%0D>5*^Mdo?y1 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
Gv*+o$DbC~%7vw#{TF=<8TGhe_ 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

;tyIXuP!n;u$U>s^ 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 Date: Sat, 28 Aug 2021 16:40:54 +0800 Subject: [PATCH 21/40] successfully read/write file in binary --- SPHINXsys/src/shared/common/image_mhd.h | 23 ++- SPHINXsys/src/shared/common/image_mhd.hpp | 176 +++++++++++++--------- 2 files changed, 121 insertions(+), 78 deletions(-) diff --git a/SPHINXsys/src/shared/common/image_mhd.h b/SPHINXsys/src/shared/common/image_mhd.h index 4a33744a20..7c41641543 100644 --- a/SPHINXsys/src/shared/common/image_mhd.h +++ b/SPHINXsys/src/shared/common/image_mhd.h @@ -20,6 +20,13 @@ * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * * * * --------------------------------------------------------------------------*/ +/** +* @file image_mesh_shape.h +* @brief x +* @details x +* x +* @author Yijin Mao +*/ #ifndef IMAGE_MHD_H #define IMAGE_MHD_H @@ -39,7 +46,13 @@ namespace SPH { MET_LONG }; - template + enum Output_Mode + { + BINARY, + ASCII + }; + + template class ImageMHD { public: @@ -55,10 +68,7 @@ namespace SPH { { objectType_ = objectType; }; - void set_ndims(int ndims) - { - ndims_ = ndims; - }; + void set_binaryData(bool binaryData) { binaryData_ = binaryData; @@ -104,7 +114,6 @@ namespace SPH { elementDataFile_ = elementDataFile; }; - T* get_data() { return data_; }; int get_size() { return size_; } @@ -117,6 +126,8 @@ namespace SPH { Real findValueAtPoint(const Vec3d& input_pnt); Vec3d findNormalAtPoint(const Vec3d & input_pnt); + void write(std::string filename, Output_Mode=BINARY); + private: std::string objectType_; int ndims_; diff --git a/SPHINXsys/src/shared/common/image_mhd.hpp b/SPHINXsys/src/shared/common/image_mhd.hpp index 6e242dbc2a..79d2001d35 100644 --- a/SPHINXsys/src/shared/common/image_mhd.hpp +++ b/SPHINXsys/src/shared/common/image_mhd.hpp @@ -20,7 +20,13 @@ * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * * * * --------------------------------------------------------------------------*/ - +/** +* @file image_mesh_shape.h +* @brief x +* @details x +* x +* @author Yijin Mao +*/ #pragma once #include "image_mhd.h" @@ -28,10 +34,10 @@ namespace SPH { - template - ImageMHD::ImageMHD(std::string full_path_to_file): + template + ImageMHD::ImageMHD(std::string full_path_to_file): objectType_("Image"), - ndims_(3), + ndims_(nDims), binaryData_(true), binaryDataByteOrderMSB_(false), compressedData_(false), @@ -113,6 +119,7 @@ namespace SPH { } else if (elements[0].compare("ElementDataFile") == 0) { + full_path_to_file = full_path_to_file.substr(0, full_path_to_file.find_last_of("\\/")); file_path_to_raw_file = full_path_to_file + '/' + elements[1]; } } @@ -126,34 +133,35 @@ namespace SPH { std::cout << "transformMatrix: " << transformMatrix_ << std::endl; //- read raw file - std::ifstream dataFileRaw(file_path_to_raw_file, std::ifstream::in || std::ifstream::binary); + std::ifstream dataFileRaw(file_path_to_raw_file, std::ios::in | std::ios::binary); - dataFileRaw.seekg(0, std::ifstream::end); - int size = (int)dataFileRaw.tellg(); - dataFileRaw.seekg(0, std::ifstream::beg); if (dataFileRaw.is_open()) { int index = 0; - while (dataFileRaw.tellg() < size) + dataFileRaw.read((char*)data_, sizeof(T)*size_); + T distance = 0; + for(int index = 0; index max_value_) max_value_ = distance; } } dataFileRaw.close(); + + //write(std::string("sphere-binary"),ASCII); } - template - ImageMHD::ImageMHD(Real radius, Vec3i NxNyNz, Vec3d spacings): + template + ImageMHD::ImageMHD(Real radius, Vec3i NxNyNz, Vec3d spacings): objectType_("Image"), - ndims_(3), + ndims_(nDims), binaryData_(true), binaryDataByteOrderMSB_(false), compressedData_(false), transformMatrix_(Mat3d(1.0)), - offset_(Vec3d(-0.5*NxNyNz[0]*spacings[0], -0.5*NxNyNz[1] * spacings[1], -0.5*NxNyNz[1] * spacings[1])), + offset_(Vec3d(-0.5*NxNyNz[0]*spacings[0], -0.5*NxNyNz[1] * spacings[1], -0.5*NxNyNz[2] * spacings[2])), centerOfRotation_(Vec3d(0.0, 0.0, 0.0)), elementSpacing_(spacings), dimSize_(NxNyNz), @@ -171,35 +179,6 @@ namespace SPH { if(data_ == nullptr) data_ = new float[size_]; - std::ofstream output_file("sphere.mhd", std::ofstream::out); - output_file << "ObjectType = " << objectType_ << "\n"; - output_file << "NDims = " << ndims_ << "\n"; - output_file << "BinaryData = " << binaryData_ << "\n"; - output_file << "BinaryDataByteOrderMSB = " << binaryDataByteOrderMSB_ << "\n"; - output_file << "CompressedData = " << compressedData_ << "\n"; - output_file << "TransformMatrix = " - << transformMatrix_[0][0] << " " << transformMatrix_[0][1] << " " << transformMatrix_[0][2] << " " - << transformMatrix_[1][0] << " " << transformMatrix_[1][1] << " " << transformMatrix_[1][2] << " " - << transformMatrix_[2][0] << " " << transformMatrix_[2][1] << " " << transformMatrix_[2][2] << "\n"; - output_file << "Offset = " - << offset_[0] << " " << offset_[1] << " " << offset_[2] << "\n"; - output_file << "CenterOfRotation = " - << centerOfRotation_[0] << " " << centerOfRotation_[1] << " " << centerOfRotation_[2] << "\n"; - output_file << "ElementSpacing = " - << elementSpacing_[0] << " " << elementSpacing_[1] << " " << elementSpacing_[2] << "\n"; - output_file << "DimSize = " - << dimSize_[0] << " " << dimSize_[1] << " " << dimSize_[2] << "\n"; - output_file << "AnatomicalOrientation = " << anatomicalOrientation_ << "\n"; - if(elementType_ == MET_FLOAT) - output_file << "ElementType = MET_FLOAT" << "\n"; - else if (elementType_ == MET_UCHAR) - output_file << "ElementType = MET_UCHAR" << "\n"; - if (elementType_ == MET_LONG) - output_file << "ElementType = MET_LONG" << "\n"; - output_file << "ElementDataFile = sphere.raw" << "\n"; - - output_file.close(); - std::ofstream output_file_raw("sphere.raw", std::ofstream::binary); Vec3d center(0.5*width_, 0.5*height_, 0.5*depth_); for (int z = 0; z < depth_; z++) @@ -213,16 +192,14 @@ namespace SPH { if (distance < min_value_) min_value_ = distance; if (distance > max_value_) max_value_ = distance; data_[index] = float(distance); - - output_file_raw.write((char*)(&(data_[index])), sizeof(data_[index])); } } } - output_file_raw.close(); + write(std::string("sphere"), BINARY); } - template - ImageMHD::~ImageMHD() + template + ImageMHD::~ImageMHD() { if (data_) { @@ -232,8 +209,8 @@ namespace SPH { } //=================================================================================================// - template - std::vector ImageMHD::findNeighbors(const Vec3d& input_pnt, Vec3i& this_cell) + template + std::vector ImageMHD::findNeighbors(const Vec3d& input_pnt, Vec3i& this_cell) { std::vector neighbors; @@ -266,8 +243,8 @@ namespace SPH { } //=================================================================================================// - template - Vec3d ImageMHD::computeGradientAtCell(int i) + template + Vec3d ImageMHD::computeGradientAtCell(int i) { //- translate 1D index to 3D index int width = width_; @@ -338,16 +315,16 @@ namespace SPH { } //=================================================================================================// - template - Vec3d ImageMHD::computeNormalAtCell(int i) + template + Vec3d ImageMHD::computeNormalAtCell(int i) { Vec3d grad_phi = computeGradientAtCell(i); Vec3d n = grad_phi.normalize(); return n; } - template - T ImageMHD::getValueAtCell(int i) + template + T ImageMHD::getValueAtCell(int i) { if (i < 0 || i > size_) { @@ -360,8 +337,8 @@ namespace SPH { } //=================================================================================================// - template - Vec3d ImageMHD::convertToPhysicalSpace(Vec3d p) + template + Vec3d ImageMHD::convertToPhysicalSpace(Vec3d p) { Vec3d position = transformMatrix_ * p + offset_; for (int i = 0; i < position.size(); i++) @@ -372,8 +349,8 @@ namespace SPH { } //=================================================================================================// - template - void ImageMHD::split(const std::string &s, char delim, \ + template + void ImageMHD::split(const std::string &s, char delim, \ std::vector &elems) { std::stringstream ss(s); @@ -383,8 +360,8 @@ namespace SPH { } } //=================================================================================================// - template - Vec3d ImageMHD::findClosestPoint(const Vec3d& input_pnt) + template + Vec3d ImageMHD::findClosestPoint(const Vec3d& input_pnt) { Vec3i this_cell; std::vector neighbors = findNeighbors(input_pnt, this_cell); @@ -410,8 +387,8 @@ namespace SPH { } - template - BoundingBox ImageMHD::findBounds() + template + BoundingBox ImageMHD::findBounds() { //initial reference values Vec3d lower_bound = Vec3d(Infinity); @@ -435,8 +412,8 @@ namespace SPH { return BoundingBox(lower_bound, upper_bound); } - template - Real ImageMHD::findValueAtPoint(const Vec3d& input_pnt) + template + Real ImageMHD::findValueAtPoint(const Vec3d& input_pnt) { Vec3i this_cell; std::vector neighbors = findNeighbors(input_pnt, this_cell); @@ -461,8 +438,8 @@ namespace SPH { } //=================================================================================================// - template - Vec3d ImageMHD::findNormalAtPoint(const Vec3d & input_pnt) + template + Vec3d ImageMHD::findNormalAtPoint(const Vec3d & input_pnt) { Vec3i this_cell; std::vector neighbors = findNeighbors(input_pnt, this_cell); @@ -489,4 +466,59 @@ namespace SPH { return Vec3d(1.0, 1.0, 1.0).normalize(); } } + + //=================================================================================================// + template + void ImageMHD::write(std::string filename, Output_Mode mode) + { + std::ofstream output_file(filename+".mhd", std::ofstream::out); + output_file << "ObjectType = " << objectType_ << "\n"; + output_file << "NDims = " << ndims_ << "\n"; + if (mode == BINARY) + output_file << "BinaryData = True" << "\n"; + else + output_file << "BinaryData = False" << "\n"; + output_file << "BinaryDataByteOrderMSB = " << binaryDataByteOrderMSB_ << "\n"; + output_file << "CompressedData = " << compressedData_ << "\n"; + output_file << "TransformMatrix = " + << transformMatrix_[0][0] << " " << transformMatrix_[0][1] << " " << transformMatrix_[0][2] << " " + << transformMatrix_[1][0] << " " << transformMatrix_[1][1] << " " << transformMatrix_[1][2] << " " + << transformMatrix_[2][0] << " " << transformMatrix_[2][1] << " " << transformMatrix_[2][2] << "\n"; + output_file << "Offset = " + << offset_[0] << " " << offset_[1] << " " << offset_[2] << "\n"; + output_file << "CenterOfRotation = " + << centerOfRotation_[0] << " " << centerOfRotation_[1] << " " << centerOfRotation_[2] << "\n"; + output_file << "ElementSpacing = " + << elementSpacing_[0] << " " << elementSpacing_[1] << " " << elementSpacing_[2] << "\n"; + output_file << "DimSize = " + << dimSize_[0] << " " << dimSize_[1] << " " << dimSize_[2] << "\n"; + output_file << "AnatomicalOrientation = " << anatomicalOrientation_ << "\n"; + if (elementType_ == MET_FLOAT) + output_file << "ElementType = MET_FLOAT" << "\n"; + else if (elementType_ == MET_UCHAR) + output_file << "ElementType = MET_UCHAR" << "\n"; + if (elementType_ == MET_LONG) + output_file << "ElementType = MET_LONG" << "\n"; + output_file << "ElementDataFile = "<< filename+".raw" << "\n"; + + output_file.close(); + + if (mode == BINARY) + { + std::ofstream output_file_raw(filename + ".raw", std::ios::binary | std::ios::out); + output_file_raw.write((const char*)data_, sizeof(T)*size_); + output_file_raw.close(); + } + else + { + std::ofstream output_file_raw(filename + ".raw"); + for (int index = 0; index < size_; index++) + { + output_file_raw << data_[index] << std::endl; + } + output_file_raw.close(); + } + + + } } From a5c8aef93abe8e17327224c87249382e0940b31e Mon Sep 17 00:00:00 2001 From: alundilong Date: Sat, 28 Aug 2021 16:42:20 +0800 Subject: [PATCH 22/40] templatization of image mhd class --- SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.cpp | 4 ++-- SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.cpp b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.cpp index 27d86fff83..a14dd27337 100644 --- a/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.cpp +++ b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.cpp @@ -11,7 +11,7 @@ namespace SPH min_distance_(INFINITY) { if (image_ == nullptr) - image_ = new ImageMHD(file_path_name); + image_ = new ImageMHD(file_path_name); } //=================================================================================================// ImageMeshShape::ImageMeshShape(Real radius, Vec3d spacings, Vec3d center) : @@ -25,7 +25,7 @@ namespace SPH int length = int(std::ceil(2.0*extend*radius)); Vec3i NxNyNz(length, length, length); if(image_ == nullptr) - image_ = new ImageMHD(radius, NxNyNz,spacings); + image_ = new ImageMHD(radius, NxNyNz,spacings); } //=================================================================================================// ImageMeshShape::~ImageMeshShape() diff --git a/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h index c737092d41..5e8099d10f 100644 --- a/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h +++ b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h @@ -75,7 +75,7 @@ namespace SPH protected: //- distance map has to be float type image - ImageMHD *image_; + ImageMHD *image_; Vec3d translation_; Mat3d rotation_; From d080d1d4cc1d2fa7c3762d436f20a8986ff4aff9 Mon Sep 17 00:00:00 2001 From: alundilong Date: Sat, 28 Aug 2021 16:42:57 +0800 Subject: [PATCH 23/40] prepare an example by load image in mhd format --- tests/3d_examples/test_3d_load_image/case.h | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/3d_examples/test_3d_load_image/case.h b/tests/3d_examples/test_3d_load_image/case.h index 4b27502863..c69a4abeb5 100644 --- a/tests/3d_examples/test_3d_load_image/case.h +++ b/tests/3d_examples/test_3d_load_image/case.h @@ -1,13 +1,13 @@ /** * @file case.h -* @brief This is the test of using levelset to generate particles relax particles. +* @brief This is the test of using levelset(distance map) to generate particles relax particles. * @details We use this case to test the particle generation and relaxation by levelset for a complex geometry (3D). * Before particle generation, we clean the sharp corner and smooth 0 levelset value, then doing the re-initialization -* @author Yongchuan Yu and Xiangyu Hu +* @author Yijin Mao */ -#ifndef TEST_3D_PARTICLE_GENERATION_CASE_H -#define TEST_3D_PARTICLE_GENERATION_CASE_H +#ifndef TEST_3D_LOAD_IMAGE_CASE_H +#define TEST_3D_LOAD_IMAGE_CASE_H #include "sphinxsys.h" @@ -16,7 +16,7 @@ using namespace SPH; //---------------------------------------------------------------------- // Set the file path to the data file. //---------------------------------------------------------------------- -std::string full_path_to_airfoil = "./input/teapot.stl"; +std::string full_path_to_image = "./input/sphere.mhd"; //---------------------------------------------------------------------- // Basic geometry parameters and numerical setup. //---------------------------------------------------------------------- @@ -31,8 +31,10 @@ ImageMeshShape *CreateImportedModelSurface() double radius = 10.0; Vec3d center(0.0, 0.0, 0.0); Vec3d spacings(1.0, 1.0, 1.0); - ImageMeshShape *geometry_imported_model = \ + //ImageMeshShape *geometry_imported_model = \ new ImageMeshShape(radius, spacings, center); + ImageMeshShape *geometry_imported_model = \ + new ImageMeshShape(full_path_to_image); return geometry_imported_model; } @@ -54,4 +56,4 @@ class ImportedModel : public SolidBody } }; -#endif //TEST_3D_PARTICLE_GENERATION_CASE_Hs \ No newline at end of file +#endif //TEST_3D_LOAD_IMAGE_CASE_Hs \ No newline at end of file From 9f0cceb88a91d878d07700ea11d71dc7a1c33cb2 Mon Sep 17 00:00:00 2001 From: alundilong Date: Sat, 28 Aug 2021 16:43:28 +0800 Subject: [PATCH 24/40] prepare data and change cmakelist.txt --- .../test_3d_load_image/CMakeLists.txt | 4 +++- .../test_3d_load_image/data/sphere.mhd | 13 +++++++++++++ .../test_3d_load_image/data/teapot.stl | Bin 471984 -> 0 bytes .../test_3d_load_image/input/sphere.mhd | 13 +++++++++++++ 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tests/3d_examples/test_3d_load_image/data/sphere.mhd delete mode 100644 tests/3d_examples/test_3d_load_image/data/teapot.stl create mode 100644 tests/3d_examples/test_3d_load_image/input/sphere.mhd diff --git a/tests/3d_examples/test_3d_load_image/CMakeLists.txt b/tests/3d_examples/test_3d_load_image/CMakeLists.txt index a555187c0d..633cd83a21 100644 --- a/tests/3d_examples/test_3d_load_image/CMakeLists.txt +++ b/tests/3d_examples/test_3d_load_image/CMakeLists.txt @@ -15,7 +15,9 @@ SET(BUILD_RELOAD_PATH "${EXECUTABLE_OUTPUT_PATH}/reload") file(MAKE_DIRECTORY ${BUILD_INPUT_PATH}) execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${BUILD_INPUT_PATH}) -file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/teapot.stl +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/sphere.mhd + DESTINATION ${BUILD_INPUT_PATH}) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/sphere.raw DESTINATION ${BUILD_INPUT_PATH}) aux_source_directory(. DIR_SRCS) diff --git a/tests/3d_examples/test_3d_load_image/data/sphere.mhd b/tests/3d_examples/test_3d_load_image/data/sphere.mhd new file mode 100644 index 0000000000..71121dfdd7 --- /dev/null +++ b/tests/3d_examples/test_3d_load_image/data/sphere.mhd @@ -0,0 +1,13 @@ +ObjectType = Image +NDims = 3 +BinaryData = 1 +BinaryDataByteOrderMSB = 0 +CompressedData = 0 +TransformMatrix = 1 0 0 0 1 0 0 0 1 +Offset = -15 -15 -15 +CenterOfRotation = 0 0 0 +ElementSpacing = 1 1 1 +DimSize = 30 30 30 +AnatomicalOrientation = ??? +ElementType = MET_FLOAT +ElementDataFile = sphere.raw diff --git a/tests/3d_examples/test_3d_load_image/data/teapot.stl b/tests/3d_examples/test_3d_load_image/data/teapot.stl deleted file mode 100644 index d2259187e6d68cafdc9a6e7f7f3ce29271410e59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 471984 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⪙uWiA*&n=75k-4Mb-k0(Vxh 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%0D>5*^Mdo?y1 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
Gv*+o$DbC~%7vw#{TF=<8TGhe_ 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

;tyIXuP!n;u$U>s^ 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 Date: Sat, 28 Aug 2021 16:44:31 +0800 Subject: [PATCH 25/40] add data in raw extension --- .../test_3d_load_image/data/sphere.raw | Bin 0 -> 108000 bytes .../test_3d_load_image/input/sphere.raw | Bin 0 -> 108000 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/3d_examples/test_3d_load_image/data/sphere.raw create mode 100644 tests/3d_examples/test_3d_load_image/input/sphere.raw diff --git a/tests/3d_examples/test_3d_load_image/data/sphere.raw b/tests/3d_examples/test_3d_load_image/data/sphere.raw new file mode 100644 index 0000000000000000000000000000000000000000..f3ec934143f4f13660ad85cb0cb23ae298d4049a GIT binary patch literal 108000 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 literal 0 HcmV?d00001 diff --git a/tests/3d_examples/test_3d_load_image/input/sphere.raw b/tests/3d_examples/test_3d_load_image/input/sphere.raw new file mode 100644 index 0000000000000000000000000000000000000000..f3ec934143f4f13660ad85cb0cb23ae298d4049a GIT binary patch literal 108000 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 literal 0 HcmV?d00001 From 806ae15845534e58d7302c2fba9df2783b56c4fb Mon Sep 17 00:00:00 2001 From: alundilong Date: Mon, 30 Aug 2021 09:02:06 +0800 Subject: [PATCH 26/40] type INFINITY by mistake --- SPHINXsys/src/shared/common/image_mhd.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SPHINXsys/src/shared/common/image_mhd.hpp b/SPHINXsys/src/shared/common/image_mhd.hpp index 79d2001d35..286d3989da 100644 --- a/SPHINXsys/src/shared/common/image_mhd.hpp +++ b/SPHINXsys/src/shared/common/image_mhd.hpp @@ -53,8 +53,8 @@ namespace SPH { anatomicalOrientation_("???"), elementType_(MET_FLOAT), elementDataFile_(""), - min_value_(INFINITE), - max_value_(-INFINITE), + min_value_(Infinity), + max_value_(-Infinity), data_(nullptr) { //- read mhd file @@ -172,8 +172,8 @@ namespace SPH { anatomicalOrientation_("???"), elementType_(MET_FLOAT), elementDataFile_(""), - min_value_(INFINITE), - max_value_(-INFINITE), + min_value_(Infinity), + max_value_(-Infinity), data_(nullptr) { if(data_ == nullptr) From eabbbee5fb19a5fb1322d6ab16599ec7b8222362 Mon Sep 17 00:00:00 2001 From: alundilong Date: Mon, 30 Aug 2021 09:18:28 +0800 Subject: [PATCH 27/40] add semicolon --- .../src/for_3D_build/geometries/image_mesh_shape.h | 11 ----------- SPHINXsys/src/shared/common/image_mhd.h | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h index 5e8099d10f..d976ba84cf 100644 --- a/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h +++ b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h @@ -82,17 +82,6 @@ namespace SPH Real max_distance_; Real min_distance_; -// private: - //std::vector findNeighbors(const Vec3d& input_pnt, Vec3i& this_cell); - //Vec3d computeGradientAtCell(int i); - //Vec3d computeNormalAtCell(int i); - //float getValueAtCell(int i); - //Vec3d convertToPhysicalSpace(Vec3d p); - // void createSphere(double radius = 10.0, Vec3d origin=Vec3d(25.0,25.0,25.0), \ - // Mat3d rotation=Mat3d(1.0), \ - // Vec3d translation=Vec3d(0.0, 0.0, 0.0), \ - // Vec3i dimensions=Vec3i(50,50,50)); - // void checkIndexBound(int i); }; } diff --git a/SPHINXsys/src/shared/common/image_mhd.h b/SPHINXsys/src/shared/common/image_mhd.h index 7c41641543..ecfc4b9bd4 100644 --- a/SPHINXsys/src/shared/common/image_mhd.h +++ b/SPHINXsys/src/shared/common/image_mhd.h @@ -103,7 +103,7 @@ namespace SPH { }; void set_anatomicalOrientation(std::string anatomicalOrientation) { - anatomicalOrientation_ = anatomicalOrientation + anatomicalOrientation_ = anatomicalOrientation; }; void set_elementType(std::string elementType) { From 9d1134bc3283455e848e220681692648ec15e99b Mon Sep 17 00:00:00 2001 From: alundilong Date: Mon, 30 Aug 2021 10:27:18 +0800 Subject: [PATCH 28/40] add default to handle otherwise case for shape boolean operation --- .../src/for_3D_build/geometries/complex_shape_image_mesh.cpp | 5 +++++ .../for_3D_build/geometries/complex_shape_triangle_mesh.cpp | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/SPHINXsys/src/for_3D_build/geometries/complex_shape_image_mesh.cpp b/SPHINXsys/src/for_3D_build/geometries/complex_shape_image_mesh.cpp index 72ff6e6388..7a7c46ec6e 100644 --- a/SPHINXsys/src/for_3D_build/geometries/complex_shape_image_mesh.cpp +++ b/SPHINXsys/src/for_3D_build/geometries/complex_shape_image_mesh.cpp @@ -129,6 +129,11 @@ namespace SPH } break; } + default: + { + throw std::runtime_error("unknown operation shape boolean operator"); + break; + } } } //=================================================================================================// diff --git a/SPHINXsys/src/for_3D_build/geometries/complex_shape_triangle_mesh.cpp b/SPHINXsys/src/for_3D_build/geometries/complex_shape_triangle_mesh.cpp index c7a027738d..4e752a5fe6 100644 --- a/SPHINXsys/src/for_3D_build/geometries/complex_shape_triangle_mesh.cpp +++ b/SPHINXsys/src/for_3D_build/geometries/complex_shape_triangle_mesh.cpp @@ -145,6 +145,11 @@ namespace SPH } break; } + default: + { + throw std::runtime_error("unknown operation shape boolean operator"); + break; + } } } //=================================================================================================// From d8f7a9cf3d48ddda3472850613cbecf0ed25fb73 Mon Sep 17 00:00:00 2001 From: alundilong Date: Mon, 30 Aug 2021 10:51:29 +0800 Subject: [PATCH 29/40] variable initialization in correct order --- SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h | 2 +- SPHINXsys/src/shared/common/image_mhd.hpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h index d976ba84cf..1e4d48d4fa 100644 --- a/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h +++ b/SPHINXsys/src/for_3D_build/geometries/image_mesh_shape.h @@ -75,9 +75,9 @@ namespace SPH protected: //- distance map has to be float type image - ImageMHD *image_; Vec3d translation_; Mat3d rotation_; + ImageMHD *image_; Real max_distance_; Real min_distance_; diff --git a/SPHINXsys/src/shared/common/image_mhd.hpp b/SPHINXsys/src/shared/common/image_mhd.hpp index 286d3989da..37738e645c 100644 --- a/SPHINXsys/src/shared/common/image_mhd.hpp +++ b/SPHINXsys/src/shared/common/image_mhd.hpp @@ -137,7 +137,6 @@ namespace SPH { if (dataFileRaw.is_open()) { - int index = 0; dataFileRaw.read((char*)data_, sizeof(T)*size_); T distance = 0; for(int index = 0; index Date: Mon, 30 Aug 2021 11:21:09 +0800 Subject: [PATCH 30/40] remove lines commented out for compiling error --- tests/3d_examples/test_3d_load_image/case.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/3d_examples/test_3d_load_image/case.h b/tests/3d_examples/test_3d_load_image/case.h index c69a4abeb5..1f107ac7d4 100644 --- a/tests/3d_examples/test_3d_load_image/case.h +++ b/tests/3d_examples/test_3d_load_image/case.h @@ -31,8 +31,6 @@ ImageMeshShape *CreateImportedModelSurface() double radius = 10.0; Vec3d center(0.0, 0.0, 0.0); Vec3d spacings(1.0, 1.0, 1.0); - //ImageMeshShape *geometry_imported_model = \ - new ImageMeshShape(radius, spacings, center); ImageMeshShape *geometry_imported_model = \ new ImageMeshShape(full_path_to_image); From c018df9e6311c3a7844dbb0fc78c771af96f42a6 Mon Sep 17 00:00:00 2001 From: alundilong Date: Mon, 30 Aug 2021 11:42:09 +0800 Subject: [PATCH 31/40] remove unused variable --- tests/3d_examples/test_3d_load_image/case.h | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/3d_examples/test_3d_load_image/case.h b/tests/3d_examples/test_3d_load_image/case.h index 1f107ac7d4..3062bf10f0 100644 --- a/tests/3d_examples/test_3d_load_image/case.h +++ b/tests/3d_examples/test_3d_load_image/case.h @@ -28,7 +28,6 @@ BoundingBox system_domain_bounds(domain_lower_bound, domain_upper_bound); ImageMeshShape *CreateImportedModelSurface() { - double radius = 10.0; Vec3d center(0.0, 0.0, 0.0); Vec3d spacings(1.0, 1.0, 1.0); ImageMeshShape *geometry_imported_model = \ From 964ad8207013e402db27e9b848a91ef619ecd302 Mon Sep 17 00:00:00 2001 From: alundilong Date: Mon, 30 Aug 2021 17:05:01 +0800 Subject: [PATCH 32/40] update examples after refactoring ComplexShape --- .../test_3d_dambreak/src/Dambreak.cpp | 13 ++++++----- .../excitation-contraction.cpp | 10 +++++---- .../src/muscle_contact.cpp | 22 +++++++++++-------- .../src/muscle_contact_soft_body_contact.cpp | 22 +++++++++++-------- .../src/muscle_activation.cpp | 10 +++++---- tests/3d_examples/test_3d_network/sphere.h | 5 +++-- .../test_3d_particle_generation/case.h | 5 +++-- .../particle_generator_single_resolution_3D.h | 3 ++- .../src/passive_cantilever.cpp | 12 +++++----- .../src/passive_cantilever_neohookean.cpp | 12 +++++----- .../test_3d_pkj_lv_electrocontraction/case.h | 15 ++++++++----- .../3d_examples/test_3d_taylor_bar/src/case.h | 10 +++++---- .../test_3d_twisting_column/src/case.h | 12 +++++----- 13 files changed, 90 insertions(+), 61 deletions(-) diff --git a/tests/3d_examples/test_3d_dambreak/src/Dambreak.cpp b/tests/3d_examples/test_3d_dambreak/src/Dambreak.cpp index 7f86202871..27dccf3b39 100644 --- a/tests/3d_examples/test_3d_dambreak/src/Dambreak.cpp +++ b/tests/3d_examples/test_3d_dambreak/src/Dambreak.cpp @@ -41,8 +41,9 @@ class WaterBlock : public FluidBody { Vecd halfsize_water(0.5 * LL, 0.5 * LH, 0.5 * LW); Vecd translation_water = halfsize_water; - body_shape_ = new ComplexShape(body_name); - body_shape_->addBrick(halfsize_water, resolution, translation_water, ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + mesh->addBrick(halfsize_water, resolution, translation_water, ShapeBooleanOps::add); + body_shape_ = new ComplexShape(mesh); } }; /** @@ -69,9 +70,11 @@ class WallBoundary : public SolidBody Vecd halfsize_outer(0.5 * DL + BW, 0.5 * DH + BW, 0.5 * DW + BW); Vecd translation_wall(0.5 * DL, 0.5 * DH, 0.5 * DW); Vecd halfsize_inner(0.5 * DL, 0.5 * DH, 0.5 * DW); - body_shape_ = new ComplexShape(body_name); - body_shape_->addBrick(halfsize_outer, resolution, translation_wall, ShapeBooleanOps::add); - body_shape_->addBrick(halfsize_inner, resolution, translation_wall, ShapeBooleanOps::sub); + + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + mesh->addBrick(halfsize_outer, resolution, translation_wall, ShapeBooleanOps::add); + mesh->addBrick(halfsize_inner, resolution, translation_wall, ShapeBooleanOps::sub); + body_shape_ = new ComplexShape(mesh); } }; diff --git a/tests/3d_examples/test_3d_heart_electromechanics/excitation-contraction.cpp b/tests/3d_examples/test_3d_heart_electromechanics/excitation-contraction.cpp index 724a1f1861..284691bcc8 100644 --- a/tests/3d_examples/test_3d_heart_electromechanics/excitation-contraction.cpp +++ b/tests/3d_examples/test_3d_heart_electromechanics/excitation-contraction.cpp @@ -139,8 +139,9 @@ class HeartBody : public SolidBody HeartBody(SPHSystem &system, std::string body_name) : SolidBody(system, body_name) { - ComplexShape original_body_shape; - original_body_shape.addTriangleMeshShape(CreateHeart(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + ComplexShape original_body_shape(mesh); + mesh->addTriangleMeshShape(CreateHeart(), ShapeBooleanOps::add); body_shape_ = new LevelSetComplexShape(this, original_body_shape); } }; @@ -273,8 +274,9 @@ class MuscleBase : public BodyPartByParticle MuscleBase(SolidBody *solid_body, std::string constrained_region_name) : BodyPartByParticle(solid_body, constrained_region_name) { - body_part_shape_ = new ComplexShape(constrained_region_name); - body_part_shape_->addTriangleMeshShape(CreateBaseShape(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_part_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(CreateBaseShape(), ShapeBooleanOps::add); /** Tag the constrained particles to the base for constraint. */ tagBodyPart(); diff --git a/tests/3d_examples/test_3d_muscle_compression/src/muscle_contact.cpp b/tests/3d_examples/test_3d_muscle_compression/src/muscle_contact.cpp index bd525475b2..735606afc1 100644 --- a/tests/3d_examples/test_3d_muscle_compression/src/muscle_contact.cpp +++ b/tests/3d_examples/test_3d_muscle_compression/src/muscle_contact.cpp @@ -59,9 +59,10 @@ class Myocardium : public SolidBody Myocardium(SPHSystem &system, std::string body_name) : SolidBody(system, body_name) { - body_shape_ = new ComplexShape(body_name); - body_shape_->addTriangleMeshShape(CreateMyocardium(), ShapeBooleanOps::add); - body_shape_->addTriangleMeshShape(CreateStationaryPlate(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(CreateMyocardium(), ShapeBooleanOps::add); + mesh->addTriangleMeshShape(CreateStationaryPlate(), ShapeBooleanOps::add); } }; /** @@ -73,8 +74,9 @@ class MovingPlate : public SolidBody MovingPlate(SPHSystem &system, std::string body_name) : SolidBody(system, body_name) { - body_shape_ = new ComplexShape(body_name); - body_shape_->addTriangleMeshShape(CreateMovingPlate(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(CreateMovingPlate(), ShapeBooleanOps::add); } }; /** @@ -88,8 +90,9 @@ class Holder : public BodyPartByParticle Holder(SolidBody *solid_body, std::string constrained_region_name) : BodyPartByParticle(solid_body, constrained_region_name) { - body_part_shape_ = new ComplexShape(constrained_region_name); - body_part_shape_->addTriangleMeshShape(CreateStationaryPlate(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_part_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(CreateStationaryPlate(), ShapeBooleanOps::add); tagBodyPart(); } @@ -104,8 +107,9 @@ class SimbodyPlate : public SolidBodyPartForSimbody std::string constrained_region_name, Real solid_body_density) : SolidBodyPartForSimbody(solid_body,constrained_region_name) { - body_part_shape_ = new ComplexShape(constrained_region_name); - body_part_shape_->addTriangleMeshShape(CreateMovingPlate(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_part_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(CreateMovingPlate(), ShapeBooleanOps::add); /** tag the constrained particle. */ tagBodyPart(); } diff --git a/tests/3d_examples/test_3d_muscle_compression_soft_body_contact/src/muscle_contact_soft_body_contact.cpp b/tests/3d_examples/test_3d_muscle_compression_soft_body_contact/src/muscle_contact_soft_body_contact.cpp index db2a92b40f..7ffc827827 100644 --- a/tests/3d_examples/test_3d_muscle_compression_soft_body_contact/src/muscle_contact_soft_body_contact.cpp +++ b/tests/3d_examples/test_3d_muscle_compression_soft_body_contact/src/muscle_contact_soft_body_contact.cpp @@ -60,9 +60,10 @@ class Myocardium : public SolidBody Myocardium(SPHSystem &system, std::string body_name) : SolidBody(system, body_name) { - body_shape_ = new ComplexShape(body_name); - body_shape_->addTriangleMeshShape(CreateMyocardium(), ShapeBooleanOps::add); - body_shape_->addTriangleMeshShape(CreateStationaryPlate(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(CreateMyocardium(), ShapeBooleanOps::add); + mesh->addTriangleMeshShape(CreateStationaryPlate(), ShapeBooleanOps::add); } }; /** @@ -74,8 +75,9 @@ class MovingPlate : public SolidBody MovingPlate(SPHSystem &system, std::string body_name) : SolidBody(system, body_name, new ParticleAdaptation(1.15, 1.5)) { - body_shape_ = new ComplexShape(body_name); - body_shape_->addTriangleMeshShape(CreateMovingPlate(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(CreateMovingPlate(), ShapeBooleanOps::add); } }; /** @@ -89,8 +91,9 @@ class Holder : public BodyPartByParticle Holder(SolidBody *solid_body, std::string constrained_region_name) : BodyPartByParticle(solid_body, constrained_region_name) { - body_part_shape_ = new ComplexShape(constrained_region_name); - body_part_shape_->addTriangleMeshShape(CreateStationaryPlate(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_part_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(CreateStationaryPlate(), ShapeBooleanOps::add); tagBodyPart(); } @@ -101,8 +104,9 @@ class HolderSpring : public BodyPartByParticle HolderSpring(SolidBody *solid_body, std::string constrained_region_name) : BodyPartByParticle(solid_body, constrained_region_name) { - body_part_shape_ = new ComplexShape(constrained_region_name); - body_part_shape_->addTriangleMeshShape(CreateMovingPlate(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_part_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(CreateMovingPlate(), ShapeBooleanOps::add); } }; /** diff --git a/tests/3d_examples/test_3d_myocaridum/src/muscle_activation.cpp b/tests/3d_examples/test_3d_myocaridum/src/muscle_activation.cpp index 7f062eab5c..c5712e9271 100644 --- a/tests/3d_examples/test_3d_myocaridum/src/muscle_activation.cpp +++ b/tests/3d_examples/test_3d_myocaridum/src/muscle_activation.cpp @@ -57,8 +57,9 @@ class Myocardium : public SolidBody Myocardium(SPHSystem &system, std::string body_name) : SolidBody(system, body_name) { - body_shape_ = new ComplexShape(body_name); - body_shape_->addTriangleMeshShape(CreateMyocardium(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(CreateMyocardium(), ShapeBooleanOps::add); } }; /** @@ -72,8 +73,9 @@ class Holder : public BodyPartByParticle Holder(SolidBody *solid_body, std::string constrained_region_name) : BodyPartByParticle(solid_body, constrained_region_name) { - body_part_shape_ = new ComplexShape(constrained_region_name); - body_part_shape_->addTriangleMeshShape(CreateHolder(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_part_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(CreateHolder(), ShapeBooleanOps::add); tagBodyPart(); } diff --git a/tests/3d_examples/test_3d_network/sphere.h b/tests/3d_examples/test_3d_network/sphere.h index 1321226814..8f1fea584a 100644 --- a/tests/3d_examples/test_3d_network/sphere.h +++ b/tests/3d_examples/test_3d_network/sphere.h @@ -35,8 +35,9 @@ class MyPolygonBody : public SolidBody : SolidBody(system, body_name, new ParticleAdaptation(1.15, 1.0), new ParticleGeneratorNetwork(Vecd(-1.0, 0.0, 0.0), Vecd(-0.964, 0.0, 0.266), 15, 5.0)) { - ComplexShape original_body_shape; - original_body_shape.addTriangleMeshShape(CreateCADGeometry(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + ComplexShape original_body_shape(mesh); + mesh->addTriangleMeshShape(CreateCADGeometry(), ShapeBooleanOps::add); body_shape_ = new LevelSetComplexShape(this, original_body_shape); } }; diff --git a/tests/3d_examples/test_3d_particle_generation/case.h b/tests/3d_examples/test_3d_particle_generation/case.h index 5fb686672c..74363de68e 100644 --- a/tests/3d_examples/test_3d_particle_generation/case.h +++ b/tests/3d_examples/test_3d_particle_generation/case.h @@ -43,8 +43,9 @@ class ImportedModel : public SolidBody new ParticleGeneratorMultiResolution()) { /** Geometry definition. */ - ComplexShape original_body_shape; - original_body_shape.addTriangleMeshShape(CreateImportedModelSurface(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + ComplexShape original_body_shape(mesh); + mesh->addTriangleMeshShape(CreateImportedModelSurface(), ShapeBooleanOps::add); body_shape_ = new LevelSetComplexShape(this, original_body_shape, true); } }; diff --git a/tests/3d_examples/test_3d_particle_generator_single_resolution/particle_generator_single_resolution_3D.h b/tests/3d_examples/test_3d_particle_generator_single_resolution/particle_generator_single_resolution_3D.h index 4931e84e5a..04b17cd0b6 100644 --- a/tests/3d_examples/test_3d_particle_generator_single_resolution/particle_generator_single_resolution_3D.h +++ b/tests/3d_examples/test_3d_particle_generator_single_resolution/particle_generator_single_resolution_3D.h @@ -41,7 +41,8 @@ class ImportedModel : public SolidBody : SolidBody(system, body_name) { /** Geometry definition. */ - ComplexShape original_body_shape; + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + ComplexShape original_body_shape(mesh); original_body_shape.addTriangleMeshShape(CreateImportedModelSurface(), ShapeBooleanOps::add); body_shape_ = new LevelSetComplexShape(this, original_body_shape, true); } diff --git a/tests/3d_examples/test_3d_passive_cantilever/src/passive_cantilever.cpp b/tests/3d_examples/test_3d_passive_cantilever/src/passive_cantilever.cpp index 85513b9059..6cf5a97b0a 100644 --- a/tests/3d_examples/test_3d_passive_cantilever/src/passive_cantilever.cpp +++ b/tests/3d_examples/test_3d_passive_cantilever/src/passive_cantilever.cpp @@ -60,9 +60,10 @@ class Myocardium : public SolidBody Myocardium(SPHSystem &system, std::string body_name) : SolidBody(system, body_name) { - body_shape_ = new ComplexShape(body_name); - body_shape_->addTriangleMeshShape(CreateMyocardium(), ShapeBooleanOps::add); - body_shape_->addTriangleMeshShape(CreateHolder(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(CreateMyocardium(), ShapeBooleanOps::add); + mesh->addTriangleMeshShape(CreateHolder(), ShapeBooleanOps::add); } }; /** @@ -76,8 +77,9 @@ class Holder : public BodyPartByParticle Holder(SolidBody *solid_body, std::string constrained_region_name) : BodyPartByParticle(solid_body, constrained_region_name) { - body_part_shape_ = new ComplexShape(constrained_region_name); - body_part_shape_->addTriangleMeshShape(CreateHolder(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_part_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(CreateHolder(), ShapeBooleanOps::add); tagBodyPart(); } diff --git a/tests/3d_examples/test_3d_passive_cantilever_neohookean/src/passive_cantilever_neohookean.cpp b/tests/3d_examples/test_3d_passive_cantilever_neohookean/src/passive_cantilever_neohookean.cpp index 9a82e15a85..dc355b12af 100644 --- a/tests/3d_examples/test_3d_passive_cantilever_neohookean/src/passive_cantilever_neohookean.cpp +++ b/tests/3d_examples/test_3d_passive_cantilever_neohookean/src/passive_cantilever_neohookean.cpp @@ -55,9 +55,10 @@ class Myocardium : public SolidBody Myocardium(SPHSystem &system, std::string body_name) : SolidBody(system, body_name) { - body_shape_ = new ComplexShape(body_name); - body_shape_->addTriangleMeshShape(CreateMyocardium(), ShapeBooleanOps::add); - body_shape_->addTriangleMeshShape(CreateHolder(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(CreateMyocardium(), ShapeBooleanOps::add); + mesh->addTriangleMeshShape(CreateHolder(), ShapeBooleanOps::add); } }; /** @@ -71,8 +72,9 @@ class Holder : public BodyPartByParticle Holder(SolidBody *solid_body, std::string constrained_region_name) : BodyPartByParticle(solid_body, constrained_region_name) { - body_part_shape_ = new ComplexShape(constrained_region_name); - body_part_shape_->addTriangleMeshShape(CreateHolder(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_part_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(CreateHolder(), ShapeBooleanOps::add); tagBodyPart(); } diff --git a/tests/3d_examples/test_3d_pkj_lv_electrocontraction/case.h b/tests/3d_examples/test_3d_pkj_lv_electrocontraction/case.h index d8312cc094..bee8f8c7f5 100644 --- a/tests/3d_examples/test_3d_pkj_lv_electrocontraction/case.h +++ b/tests/3d_examples/test_3d_pkj_lv_electrocontraction/case.h @@ -164,8 +164,9 @@ class HeartBody : public SolidBody public: HeartBody(SPHSystem &system, string body_name) : SolidBody(system, body_name) { - ComplexShape original_body_shape; - original_body_shape.addTriangleMeshShape(CreateHeart(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + ComplexShape original_body_shape(mesh); + mesh->addTriangleMeshShape(CreateHeart(), ShapeBooleanOps::add); body_shape_ = new LevelSetComplexShape(this, original_body_shape); } }; @@ -298,8 +299,9 @@ class MuscleBase : public BodyPartByParticle MuscleBase(SolidBody *solid_body, string constrained_region_name) : BodyPartByParticle(solid_body, constrained_region_name) { - body_part_shape_ = new ComplexShape(constrained_region_name); - body_part_shape_->addTriangleMeshShape(CreateBaseShape(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_part_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(CreateBaseShape(), ShapeBooleanOps::add); /** Tag the constrained particles to the base for constraint. */ tagBodyPart(); @@ -379,8 +381,9 @@ class PurkinjeBody : public SolidBody PurkinjeBody(SPHSystem &system, string body_name, ParticleGenerator* particle_generator) : SolidBody(system, body_name, new ParticleAdaptation(1.05, 1), particle_generator) { - ComplexShape original_body_shape; - original_body_shape.addTriangleMeshShape(CreateHeart(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + ComplexShape original_body_shape(mesh); + mesh->addTriangleMeshShape(CreateHeart(), ShapeBooleanOps::add); body_shape_ = new LevelSetComplexShape(this, original_body_shape); /** Use the reducedtwice kernel */ particle_adaptation_->getKernel()->reduceOnce(); diff --git a/tests/3d_examples/test_3d_taylor_bar/src/case.h b/tests/3d_examples/test_3d_taylor_bar/src/case.h index 00abe19320..ede57a7a05 100644 --- a/tests/3d_examples/test_3d_taylor_bar/src/case.h +++ b/tests/3d_examples/test_3d_taylor_bar/src/case.h @@ -55,8 +55,9 @@ class Wall : public SolidBody Wall(SPHSystem& system, string body_name) : SolidBody(system, body_name, new ParticleAdaptation(1.15, 1.0)) { - body_shape_ = new ComplexShape(body_name); - body_shape_->addTriangleMeshShape(CreateHolder(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(CreateHolder(), ShapeBooleanOps::add); } }; @@ -68,8 +69,9 @@ class Column : public SolidBody Column(SPHSystem& system, std::string body_name) : SolidBody(system, body_name, new ParticleAdaptation(1.3, 1.0)) { - ComplexShape original_body_shape; - original_body_shape.addTriangleMeshShape(CreateColumn(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + ComplexShape original_body_shape(mesh); + mesh->addTriangleMeshShape(CreateColumn(), ShapeBooleanOps::add); body_shape_ = new LevelSetComplexShape(this, original_body_shape); } }; diff --git a/tests/3d_examples/test_3d_twisting_column/src/case.h b/tests/3d_examples/test_3d_twisting_column/src/case.h index c1edfd6477..eabafacc79 100644 --- a/tests/3d_examples/test_3d_twisting_column/src/case.h +++ b/tests/3d_examples/test_3d_twisting_column/src/case.h @@ -57,9 +57,10 @@ class Column : public SolidBody public: Column(SPHSystem& system, std::string body_name) : SolidBody(system, body_name, new ParticleAdaptation(1.15, 1.0)) { - body_shape_ = new ComplexShape(body_name); - body_shape_->addTriangleMeshShape(CreateCantilever(), ShapeBooleanOps::add); - body_shape_->addTriangleMeshShape(CreateHolder(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(CreateCantilever(), ShapeBooleanOps::add); + mesh->addTriangleMeshShape(CreateHolder(), ShapeBooleanOps::add); } }; /** @@ -73,8 +74,9 @@ class Holder : public BodyPartByParticle Holder(SolidBody* solid_body, std::string constrained_region_name) : BodyPartByParticle(solid_body, constrained_region_name) { - body_part_shape_ = new ComplexShape(constrained_region_name); - body_part_shape_->addTriangleMeshShape(CreateHolder(), ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_part_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(CreateHolder(), ShapeBooleanOps::add); tagBodyPart(); } }; From e3c8dbf805c2edc6fe48e363ab50be1ef4321a51 Mon Sep 17 00:00:00 2001 From: alundilong Date: Mon, 30 Aug 2021 18:56:54 +0800 Subject: [PATCH 33/40] fix bug otherwise following tests cannot pass test_3d_TAH_path_particle_relaxation position_based_boundary_conditions_particle_relaxation time_dependent_contact_particle_relaxation --- .../solid_structural_simulation_class.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp b/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp index 7db88200a4..34b2147bf3 100644 --- a/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp +++ b/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp @@ -7,8 +7,9 @@ BodyPartByParticleTriMesh::BodyPartByParticleTriMesh(SPHBody* body, string body_part_name, TriangleMeshShape* triangle_mesh_shape) : BodyPartByParticle(body, body_part_name) { - body_part_shape_ = new ComplexShape(body_part_name); - body_part_shape_->addTriangleMeshShape(triangle_mesh_shape, ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + body_part_shape_ = new ComplexShape(mesh); + mesh->addTriangleMeshShape(triangle_mesh_shape, ShapeBooleanOps::add); tagBodyPart(); } @@ -20,8 +21,9 @@ BodyPartByParticleTriMesh::~BodyPartByParticleTriMesh() ImportedModel::ImportedModel(SPHSystem &system, string body_name, TriangleMeshShape* triangle_mesh_shape, ParticleAdaptation* particle_adaptation) : SolidBody(system, body_name, particle_adaptation) { - ComplexShape original_body_shape; - original_body_shape.addTriangleMeshShape(triangle_mesh_shape, ShapeBooleanOps::add); + ComplexShapeTriangleMesh *mesh = new ComplexShapeTriangleMesh(); + ComplexShape original_body_shape(mesh); + mesh->addTriangleMeshShape(triangle_mesh_shape, ShapeBooleanOps::add); body_shape_ = new LevelSetComplexShape(this, original_body_shape, true); } From 3211ef93ffd1cdbaf1f09b2703b5473ccd1d102e Mon Sep 17 00:00:00 2001 From: Xiangyu Hu Date: Mon, 6 Sep 2021 20:48:17 +0200 Subject: [PATCH 34/40] update from bitbucket sept-06-2021 --- SPHINXsys/src/CMakeLists.txt | 2 +- .../solid_structural_simulation_class.cpp | 122 ++++-- .../solid_structural_simulation_class.h | 19 +- .../test_structural_simulation_class.h | 67 ++-- .../particle_generator_network.h | 2 +- SPHINXsys/src/shared/bodies/base_body.h | 2 +- SPHINXsys/src/shared/bodies/body_relation.cpp | 244 ++++++++---- SPHINXsys/src/shared/bodies/body_relation.h | 187 +++++---- .../src/shared/common/sph_data_containers.cpp | 24 ++ ...data_conainers.h => sph_data_containers.h} | 7 +- .../src/shared/geometries/base_geometry.h | 2 +- SPHINXsys/src/shared/io_system/in_output.h | 2 +- SPHINXsys/src/shared/meshes/base_mesh.h | 2 +- .../base_particle_dynamics.h | 2 +- .../solid_dynamics/all_solid_dynamics.h | 1 + .../solid_dynamics/contact_dynamics.cpp | 302 ++++++++++++++ .../solid_dynamics/contact_dynamics.h | 177 +++++++++ .../solid_dynamics/inelastic_dynamics.cpp | 6 +- .../solid_dynamics/solid_dynamics.cpp | 373 ++++++------------ .../solid_dynamics/solid_dynamics.h | 141 +++---- .../base_particle_generator.h | 2 +- .../src/shared/particles/base_particles.cpp | 15 + .../src/shared/particles/base_particles.h | 2 +- .../shared/particles/neighbor_relation.cpp | 21 + .../src/shared/particles/neighbor_relation.h | 15 + .../shared/particles/particle_adaptation.h | 2 +- .../src/shared/particles/particle_sorting.h | 2 +- .../src/shared/particles/solid_particles.cpp | 24 ++ .../src/shared/particles/solid_particles.h | 3 + .../src/shared/simbody_sphinxsys/xml_engine.h | 2 +- .../src/shared/sphinxsys_system/sph_system.h | 2 +- cases_high_level_simulation/CMakeLists.txt | 13 - .../test_3d_ball_position_solid_body.cpp | 52 --- .../input/ball_mass.stl | Bin 256084 -> 0 bytes cases_test/CMakeLists.txt | 13 - tests/2d_examples/CMakeLists.txt | 5 + .../test_1d_shock_tube/CMakeLists.txt | 0 .../test_1d_shock_tube/src/CMakeLists.txt | 0 .../test_1d_shock_tube/src/shock_tube.cpp | 0 .../test_1d_shock_tube/src/shock_tube.h | 0 .../test_2d_T_shaped_pipe/CMakeLists.txt | 0 .../test_2d_T_shaped_pipe/src/CMakeLists.txt | 0 .../src/T_shaped_pipe.cpp | 0 .../test_2d_airfoil/CMakeLists.txt | 0 .../test_2d_airfoil/airfoil_2d.cpp | 0 .../2d_examples}/test_2d_airfoil/airfoil_2d.h | 0 .../data/airfoil_flap_front.dat | 0 .../data/airfoil_flap_rear.dat | 0 .../test_2d_airfoil/data/airfoil_wing.dat | 0 .../test_2d_collision/CMakeLists.txt | 0 .../test_2d_collision/src/CMakeLists.txt | 0 .../test_2d_collision/src/collision.cpp | 0 .../test_2d_dambreak/CMakeLists.txt | 0 .../test_2d_dambreak/src/CMakeLists.txt | 0 .../test_2d_dambreak/src/Dambreak.cpp | 0 .../test_2d_depolarization/CMakeLists.txt | 0 .../test_2d_depolarization/src/CMakeLists.txt | 0 .../src/depolarization.cpp | 0 .../test_2d_diffusion/CMakeLists.txt | 0 .../test_2d_diffusion/src/CMakeLists.txt | 0 .../test_2d_diffusion/src/diffusion.cpp | 0 .../test_2d_elastic_gate/CMakeLists.txt | 0 .../test_2d_elastic_gate/src/CMakeLists.txt | 0 .../test_2d_elastic_gate/src/elastic_gate.cpp | 0 .../CMakeLists.txt | 0 .../src/2d_eulerian_taylor_green.cpp | 0 .../src/CMakeLists.txt | 0 .../test_2d_filling_tank/CMakeLists.txt | 0 .../test_2d_filling_tank/src/CMakeLists.txt | 0 .../test_2d_filling_tank/src/filling_tank.cpp | 0 .../CMakeLists.txt | 0 .../src/2d_flow_around_cylinder.cpp | 0 .../src/2d_flow_around_cylinder.h | 0 .../src/CMakeLists.txt | 0 .../src/data/project_parameters.dat | 0 .../CMakeLists.txt | 0 .../src/2d_free_stream_around_cylinder .cpp | 0 .../src/2d_free_stream_around_cylinder.h | 0 .../src/CMakeLists.txt | 0 .../src/data/project_parameters.dat | 0 .../2d_examples}/test_2d_fsi2/CMakeLists.txt | 0 .../test_2d_fsi2/src/CMakeLists.txt | 0 .../2d_examples}/test_2d_fsi2/src/fsi2.cpp | 0 .../2d_examples}/test_2d_fsi2/src/fsi2_case.h | 0 .../test_2d_heat_transfer/CMakeLists.txt | 0 .../test_2d_heat_transfer/src/CMakeLists.txt | 0 .../src/heat_transfer.cpp | 0 .../test_2d_hydrostatic_fsi/CMakeLists.txt | 0 .../src/CMakeLists.txt | 0 .../src/hydrostatic_fsi.cpp | 0 .../test_2d_oscillating_beam/CMakeLists.txt | 0 .../src/CMakeLists.txt | 0 .../src/oscillating_beam.cpp | 180 ++++----- .../2d_examples}/test_2d_owsc/CMakeLists.txt | 0 .../test_2d_owsc/src/CMakeLists.txt | 0 .../2d_examples}/test_2d_owsc/src/case.h | 0 .../2d_examples}/test_2d_owsc/src/owsc.cpp | 0 .../CMakeLists.txt | 0 .../data/SPHinXsys-2d.dat | 0 .../particle_generator_single_resolution.cpp | 0 .../particle_generator_single_resolution.h | 0 .../2d_examples}/test_2d_plate/CMakeLists.txt | 0 .../test_2d_plate/src/2d_plate.cpp | 0 .../test_2d_plate/src/CMakeLists.txt | 0 .../test_2d_poiseuille_flow/CMakeLists.txt | 0 .../src/CMakeLists.txt | 0 .../src/poiseuille_flow.cpp | 0 .../test_2d_self_contact}/CMakeLists.txt | 0 .../test_2d_self_contact}/src/CMakeLists.txt | 0 .../test_2d_self_contact/src/self_contact.cpp | 274 +++++++++++++ .../2d_examples/test_2d_shell}/CMakeLists.txt | 0 .../test_2d_shell/src/2d_shell.cpp | 0 .../test_2d_shell}/src/CMakeLists.txt | 0 .../test_2d_sliding}/CMakeLists.txt | 0 .../test_2d_sliding/src/CMakeLists.txt | 0 .../test_2d_sliding/src/sliding.cpp | 0 .../test_2d_square_droplet}/CMakeLists.txt | 0 .../test_2d_square_droplet/src/CMakeLists.txt | 0 .../test_2d_square_droplet/src/case.h | 0 .../test_2d_square_droplet/src/droplet.cpp | 0 .../CMakeLists.txt | 0 .../src/CMakeLists.txt | 0 .../src/static_confinement.cpp | 0 .../test_2d_taylor_green}/CMakeLists.txt | 0 .../test_2d_taylor_green/src/CMakeLists.txt | 0 .../test_2d_taylor_green/src/taylor_green.cpp | 0 .../CMakeLists.txt | 0 .../src/CMakeLists.txt | 0 .../src/fish_and_bones.h | 0 .../src/tethered_dead_fish_in_flow.cpp | 0 .../test_2d_throat}/CMakeLists.txt | 0 .../test_2d_throat/src/CMakeLists.txt | 24 ++ .../test_2d_throat/src/throat.cpp | 0 .../CMakeLists.txt | 0 .../src/CMakeLists.txt | 0 .../test_2d_two_phase_dambreak/src/case.h | 0 .../src/two_phase_dambreak.cpp | 0 .../test_2d_wetting_effects}/CMakeLists.txt | 0 .../src/CMakeLists.txt | 0 .../test_2d_wetting_effects/src/case.h | 0 .../test_2d_wetting_effects/src/wetting.cpp | 0 tests/3d_examples/CMakeLists.txt | 5 + .../3d_examples/test_3d_arch}/CMakeLists.txt | 0 .../3d_examples}/test_3d_arch/src/3d_arch.cpp | 0 .../test_3d_arch/src/CMakeLists.txt | 0 .../test_3d_dambreak}/CMakeLists.txt | 0 .../test_3d_dambreak/src/CMakeLists.txt | 0 .../test_3d_dambreak/src/Dambreak.cpp | 0 .../CMakeLists.txt | 0 .../data/heart-new.stl | Bin .../excitation-contraction.cpp | 0 .../CMakeLists.txt | 0 .../src/CMakeLists.txt | 0 .../src/muscle_contact.cpp | 1 + .../CMakeLists.txt | 0 .../src/CMakeLists.txt | 0 .../src/muscle_contact_soft_body_contact.cpp | 0 .../test_3d_myocaridum}/CMakeLists.txt | 0 .../test_3d_myocaridum/src/CMakeLists.txt | 0 .../src/muscle_activation.cpp | 0 .../test_3d_network/CMakeLists.txt | 0 .../test_3d_network/data/sphere.stl | Bin .../3d_examples}/test_3d_network/net_work.cpp | 0 .../3d_examples}/test_3d_network/sphere.h | 0 .../CMakeLists.txt | 0 .../test_3d_particle_generation/case.h | 0 .../data/teapot.stl | Bin .../input/teapot.stl | Bin .../particle_generation.cpp | 0 .../CMakeLists.txt | 0 .../data/SPHinXsys.stl | Bin .../input/SPHinXsys.stl | Bin ...article_generator_single_resolution_3D.cpp | 0 .../particle_generator_single_resolution_3D.h | 0 .../CMakeLists.txt | 0 .../src/CMakeLists.txt | 0 .../src/passive_cantilever.cpp | 0 .../CMakeLists.txt | 0 .../src/CMakeLists.txt | 0 .../src/passive_cantilever_neohookean.cpp | 0 .../CMakeLists.txt | 0 .../test_3d_pkj_lv_electrocontraction/case.h | 0 .../data/leftventricle.stl | 0 .../pkj-lv-electrocontraction.cpp | 0 .../test_3d_play_simbody}/CMakeLists.txt | 0 .../test_3d_play_simbody/src/CMakeLists.txt | 0 .../test_3d_play_simbody/src/UdfMotion.h | 0 .../test_3d_play_simbody/src/play_simbody.cpp | 0 .../3d_examples/test_3d_roof}/CMakeLists.txt | 0 .../3d_examples}/test_3d_roof/src/3d_roof.cpp | 0 .../test_3d_roof/src/CMakeLists.txt | 0 .../test_3d_self_contact/3d_self_contact.cpp | 174 ++++++++ .../test_3d_self_contact/3d_self_contact.h | 103 +++++ .../test_3d_self_contact}/CMakeLists.txt | 28 +- .../test_3d_self_contact/data/coil.stl | Bin 0 -> 2565284 bytes .../test_3d_taylor_bar}/CMakeLists.txt | 0 .../test_3d_taylor_bar/src/CMakeLists.txt | 0 .../test_3d_taylor_bar/src/case.h | 0 .../test_3d_taylor_bar/src/taylor_bar.cpp | 2 +- .../test_3d_thin_plate}/CMakeLists.txt | 0 .../test_3d_thin_plate/src/CMakeLists.txt | 0 .../src/test_3d_thin_plate.cpp | 0 .../test_3d_twisting_column/CMakeLists.txt | 3 + .../src/CMakeLists.txt | 0 .../test_3d_twisting_column/src/case.h | 2 +- .../src/twisting_column.cpp | 0 tests/CMakeLists.txt | 24 ++ tests/high_level_examples/CMakeLists.txt | 5 + .../test_3d_TAH_path/CMakeLists.txt | 0 .../test_3d_TAH_path/input/Aorta.stl | 0 .../test_3d_TAH_path/input/Diaphragm.stl | 0 .../test_3d_TAH_path/input/LA.stl | 0 .../test_3d_TAH_path/input/PA.stl | 0 .../test_3d_TAH_path/input/RA.stl | 0 .../test_3d_TAH_path/input/TAH_basic2_pos.stl | 0 .../sim_total_artificial_heart.h | 0 .../total_artificial_heart.cpp | 0 tests/unit_tests_src/CMakeLists.txt | 7 + .../for_2D_build/CMakeLists.txt | 5 + .../for_3D_build/CMakeLists.txt | 5 + .../CMakeLists.txt | 5 + .../CMakeLists.txt | 5 +- .../input/ball_mass.stl | Bin .../input/cylinder.stl | Bin .../position_based_boundary_conditions.cpp | 104 +++-- .../time_dependent_contact}/CMakeLists.txt | 3 +- .../input/mock_stent.stl | Bin .../time_dependent_contact}/input/plate.stl | Bin .../time_dependent_contact}/input/plate2.stl | Bin .../input/plate_stent.stl | Bin .../input/vessel_cylinder.stl | Bin .../time_dependent_contact.cpp | 26 +- tests/unit_tests_src/shared/CMakeLists.txt | 5 + 233 files changed, 2016 insertions(+), 831 deletions(-) create mode 100644 SPHINXsys/src/shared/common/sph_data_containers.cpp rename SPHINXsys/src/shared/common/{sph_data_conainers.h => sph_data_containers.h} (94%) create mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/contact_dynamics.cpp create mode 100644 SPHINXsys/src/shared/particle_dynamics/solid_dynamics/contact_dynamics.h delete mode 100644 cases_high_level_simulation/CMakeLists.txt delete mode 100644 cases_high_level_simulation/test_3d_ball_position_solid_body/test_3d_ball_position_solid_body.cpp delete mode 100644 cases_high_level_simulation/test_3d_unit_position_based_bc/input/ball_mass.stl delete mode 100644 cases_test/CMakeLists.txt create mode 100644 tests/2d_examples/CMakeLists.txt rename {cases_test => tests/2d_examples}/test_1d_shock_tube/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_1d_shock_tube/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_1d_shock_tube/src/shock_tube.cpp (100%) rename {cases_test => tests/2d_examples}/test_1d_shock_tube/src/shock_tube.h (100%) rename {cases_test => tests/2d_examples}/test_2d_T_shaped_pipe/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_T_shaped_pipe/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_T_shaped_pipe/src/T_shaped_pipe.cpp (100%) rename {cases_test => tests/2d_examples}/test_2d_airfoil/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_airfoil/airfoil_2d.cpp (100%) rename {cases_test => tests/2d_examples}/test_2d_airfoil/airfoil_2d.h (100%) rename {cases_test => tests/2d_examples}/test_2d_airfoil/data/airfoil_flap_front.dat (100%) rename {cases_test => tests/2d_examples}/test_2d_airfoil/data/airfoil_flap_rear.dat (100%) rename {cases_test => tests/2d_examples}/test_2d_airfoil/data/airfoil_wing.dat (100%) rename {cases_test => tests/2d_examples}/test_2d_collision/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_collision/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_collision/src/collision.cpp (100%) rename {cases_test => tests/2d_examples}/test_2d_dambreak/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_dambreak/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_dambreak/src/Dambreak.cpp (100%) rename {cases_test => tests/2d_examples}/test_2d_depolarization/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_depolarization/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_depolarization/src/depolarization.cpp (100%) rename {cases_test => tests/2d_examples}/test_2d_diffusion/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_diffusion/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_diffusion/src/diffusion.cpp (100%) rename {cases_test => tests/2d_examples}/test_2d_elastic_gate/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_elastic_gate/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_elastic_gate/src/elastic_gate.cpp (100%) rename {cases_test => tests/2d_examples}/test_2d_eulerian_taylor_green/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_eulerian_taylor_green/src/2d_eulerian_taylor_green.cpp (100%) rename {cases_test => tests/2d_examples}/test_2d_eulerian_taylor_green/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_filling_tank/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_filling_tank/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_filling_tank/src/filling_tank.cpp (100%) rename {cases_test => tests/2d_examples}/test_2d_flow_around_cylinder/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_flow_around_cylinder/src/2d_flow_around_cylinder.cpp (100%) rename {cases_test => tests/2d_examples}/test_2d_flow_around_cylinder/src/2d_flow_around_cylinder.h (100%) rename {cases_test => tests/2d_examples}/test_2d_flow_around_cylinder/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_flow_around_cylinder/src/data/project_parameters.dat (100%) rename {cases_test => tests/2d_examples}/test_2d_free_stream_around_cylinder/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_free_stream_around_cylinder/src/2d_free_stream_around_cylinder .cpp (100%) rename {cases_test => tests/2d_examples}/test_2d_free_stream_around_cylinder/src/2d_free_stream_around_cylinder.h (100%) rename {cases_test => tests/2d_examples}/test_2d_free_stream_around_cylinder/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_free_stream_around_cylinder/src/data/project_parameters.dat (100%) rename {cases_test => tests/2d_examples}/test_2d_fsi2/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_fsi2/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_fsi2/src/fsi2.cpp (100%) rename {cases_test => tests/2d_examples}/test_2d_fsi2/src/fsi2_case.h (100%) rename {cases_test => tests/2d_examples}/test_2d_heat_transfer/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_heat_transfer/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_heat_transfer/src/heat_transfer.cpp (100%) rename {cases_test => tests/2d_examples}/test_2d_hydrostatic_fsi/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_hydrostatic_fsi/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_hydrostatic_fsi/src/hydrostatic_fsi.cpp (100%) rename {cases_test => tests/2d_examples}/test_2d_oscillating_beam/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_oscillating_beam/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_oscillating_beam/src/oscillating_beam.cpp (69%) rename {cases_test => tests/2d_examples}/test_2d_owsc/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_owsc/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_owsc/src/case.h (100%) rename {cases_test => tests/2d_examples}/test_2d_owsc/src/owsc.cpp (100%) rename {cases_test => tests/2d_examples}/test_2d_particle_generator_single_resolution/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_particle_generator_single_resolution/data/SPHinXsys-2d.dat (100%) rename {cases_test => tests/2d_examples}/test_2d_particle_generator_single_resolution/particle_generator_single_resolution.cpp (100%) rename {cases_test => tests/2d_examples}/test_2d_particle_generator_single_resolution/particle_generator_single_resolution.h (100%) rename {cases_test => tests/2d_examples}/test_2d_plate/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_plate/src/2d_plate.cpp (100%) rename {cases_test => tests/2d_examples}/test_2d_plate/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_poiseuille_flow/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_poiseuille_flow/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_poiseuille_flow/src/poiseuille_flow.cpp (100%) rename {cases_test/test_2d_shell => tests/2d_examples/test_2d_self_contact}/CMakeLists.txt (100%) rename {cases_test/test_2d_shell => tests/2d_examples/test_2d_self_contact}/src/CMakeLists.txt (100%) create mode 100644 tests/2d_examples/test_2d_self_contact/src/self_contact.cpp rename {cases_test/test_2d_sliding => tests/2d_examples/test_2d_shell}/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_shell/src/2d_shell.cpp (100%) rename {cases_test/test_2d_throat => tests/2d_examples/test_2d_shell}/src/CMakeLists.txt (100%) rename {cases_test/test_2d_square_droplet => tests/2d_examples/test_2d_sliding}/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_sliding/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_sliding/src/sliding.cpp (100%) rename {cases_test/test_2d_static_confinement => tests/2d_examples/test_2d_square_droplet}/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_square_droplet/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_square_droplet/src/case.h (100%) rename {cases_test => tests/2d_examples}/test_2d_square_droplet/src/droplet.cpp (100%) rename {cases_test/test_2d_taylor_green => tests/2d_examples/test_2d_static_confinement}/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_static_confinement/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_static_confinement/src/static_confinement.cpp (100%) rename {cases_test/test_2d_tethered_dead_fish_in_flow => tests/2d_examples/test_2d_taylor_green}/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_taylor_green/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_taylor_green/src/taylor_green.cpp (100%) rename {cases_test/test_2d_throat => tests/2d_examples/test_2d_tethered_dead_fish_in_flow}/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_tethered_dead_fish_in_flow/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_tethered_dead_fish_in_flow/src/fish_and_bones.h (100%) rename {cases_test => tests/2d_examples}/test_2d_tethered_dead_fish_in_flow/src/tethered_dead_fish_in_flow.cpp (100%) rename {cases_test/test_2d_two_phase_dambreak => tests/2d_examples/test_2d_throat}/CMakeLists.txt (100%) create mode 100644 tests/2d_examples/test_2d_throat/src/CMakeLists.txt rename {cases_test => tests/2d_examples}/test_2d_throat/src/throat.cpp (100%) rename {cases_test/test_2d_wetting_effects => tests/2d_examples/test_2d_two_phase_dambreak}/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_two_phase_dambreak/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_two_phase_dambreak/src/case.h (100%) rename {cases_test => tests/2d_examples}/test_2d_two_phase_dambreak/src/two_phase_dambreak.cpp (100%) rename {cases_test/test_3d_arch => tests/2d_examples/test_2d_wetting_effects}/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_wetting_effects/src/CMakeLists.txt (100%) rename {cases_test => tests/2d_examples}/test_2d_wetting_effects/src/case.h (100%) rename {cases_test => tests/2d_examples}/test_2d_wetting_effects/src/wetting.cpp (100%) create mode 100644 tests/3d_examples/CMakeLists.txt rename {cases_test/test_3d_dambreak => tests/3d_examples/test_3d_arch}/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_arch/src/3d_arch.cpp (100%) rename {cases_test => tests/3d_examples}/test_3d_arch/src/CMakeLists.txt (100%) rename {cases_test/test_3d_muscle_compression => tests/3d_examples/test_3d_dambreak}/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_dambreak/src/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_dambreak/src/Dambreak.cpp (100%) rename {cases_test => tests/3d_examples}/test_3d_heart_electromechanics/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_heart_electromechanics/data/heart-new.stl (100%) rename {cases_test => tests/3d_examples}/test_3d_heart_electromechanics/excitation-contraction.cpp (100%) rename {cases_test/test_3d_muscle_compression_soft_body_contact => tests/3d_examples/test_3d_muscle_compression}/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_muscle_compression/src/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_muscle_compression/src/muscle_contact.cpp (99%) rename {cases_test/test_3d_myocaridum => tests/3d_examples/test_3d_muscle_compression_soft_body_contact}/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_muscle_compression_soft_body_contact/src/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_muscle_compression_soft_body_contact/src/muscle_contact_soft_body_contact.cpp (100%) rename {cases_test/test_3d_passive_cantilever => tests/3d_examples/test_3d_myocaridum}/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_myocaridum/src/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_myocaridum/src/muscle_activation.cpp (100%) rename {cases_test => tests/3d_examples}/test_3d_network/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_network/data/sphere.stl (100%) rename {cases_test => tests/3d_examples}/test_3d_network/net_work.cpp (100%) rename {cases_test => tests/3d_examples}/test_3d_network/sphere.h (100%) rename {cases_test => tests/3d_examples}/test_3d_particle_generation/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_particle_generation/case.h (100%) rename {cases_test => tests/3d_examples}/test_3d_particle_generation/data/teapot.stl (100%) rename {cases_test => tests/3d_examples}/test_3d_particle_generation/input/teapot.stl (100%) rename {cases_test => tests/3d_examples}/test_3d_particle_generation/particle_generation.cpp (100%) rename {cases_test => tests/3d_examples}/test_3d_particle_generator_single_resolution/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_particle_generator_single_resolution/data/SPHinXsys.stl (100%) rename {cases_test => tests/3d_examples}/test_3d_particle_generator_single_resolution/input/SPHinXsys.stl (100%) rename {cases_test => tests/3d_examples}/test_3d_particle_generator_single_resolution/particle_generator_single_resolution_3D.cpp (100%) rename {cases_test => tests/3d_examples}/test_3d_particle_generator_single_resolution/particle_generator_single_resolution_3D.h (100%) rename {cases_test/test_3d_passive_cantilever_neohookean => tests/3d_examples/test_3d_passive_cantilever}/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_passive_cantilever/src/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_passive_cantilever/src/passive_cantilever.cpp (100%) rename {cases_test/test_3d_play_simbody => tests/3d_examples/test_3d_passive_cantilever_neohookean}/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_passive_cantilever_neohookean/src/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_passive_cantilever_neohookean/src/passive_cantilever_neohookean.cpp (100%) rename {cases_test => tests/3d_examples}/test_3d_pkj_lv_electrocontraction/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_pkj_lv_electrocontraction/case.h (100%) rename {cases_test => tests/3d_examples}/test_3d_pkj_lv_electrocontraction/data/leftventricle.stl (100%) rename {cases_test => tests/3d_examples}/test_3d_pkj_lv_electrocontraction/pkj-lv-electrocontraction.cpp (100%) rename {cases_test/test_3d_roof => tests/3d_examples/test_3d_play_simbody}/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_play_simbody/src/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_play_simbody/src/UdfMotion.h (100%) rename {cases_test => tests/3d_examples}/test_3d_play_simbody/src/play_simbody.cpp (100%) rename {cases_test/test_3d_taylor_bar => tests/3d_examples/test_3d_roof}/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_roof/src/3d_roof.cpp (100%) rename {cases_test => tests/3d_examples}/test_3d_roof/src/CMakeLists.txt (100%) create mode 100644 tests/3d_examples/test_3d_self_contact/3d_self_contact.cpp create mode 100644 tests/3d_examples/test_3d_self_contact/3d_self_contact.h rename {cases_high_level_simulation/test_3d_ball_position_solid_body => tests/3d_examples/test_3d_self_contact}/CMakeLists.txt (74%) create mode 100644 tests/3d_examples/test_3d_self_contact/data/coil.stl rename {cases_test/test_3d_thin_plate => tests/3d_examples/test_3d_taylor_bar}/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_taylor_bar/src/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_taylor_bar/src/case.h (100%) rename {cases_test => tests/3d_examples}/test_3d_taylor_bar/src/taylor_bar.cpp (99%) rename {cases_test/test_3d_twisting_column => tests/3d_examples/test_3d_thin_plate}/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_thin_plate/src/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_thin_plate/src/test_3d_thin_plate.cpp (100%) create mode 100644 tests/3d_examples/test_3d_twisting_column/CMakeLists.txt rename {cases_test => tests/3d_examples}/test_3d_twisting_column/src/CMakeLists.txt (100%) rename {cases_test => tests/3d_examples}/test_3d_twisting_column/src/case.h (99%) rename {cases_test => tests/3d_examples}/test_3d_twisting_column/src/twisting_column.cpp (100%) create mode 100644 tests/CMakeLists.txt create mode 100644 tests/high_level_examples/CMakeLists.txt rename {cases_high_level_simulation => tests/high_level_examples}/test_3d_TAH_path/CMakeLists.txt (100%) rename {cases_high_level_simulation => tests/high_level_examples}/test_3d_TAH_path/input/Aorta.stl (100%) rename {cases_high_level_simulation => tests/high_level_examples}/test_3d_TAH_path/input/Diaphragm.stl (100%) rename {cases_high_level_simulation => tests/high_level_examples}/test_3d_TAH_path/input/LA.stl (100%) rename {cases_high_level_simulation => tests/high_level_examples}/test_3d_TAH_path/input/PA.stl (100%) rename {cases_high_level_simulation => tests/high_level_examples}/test_3d_TAH_path/input/RA.stl (100%) rename {cases_high_level_simulation => tests/high_level_examples}/test_3d_TAH_path/input/TAH_basic2_pos.stl (100%) rename {cases_high_level_simulation => tests/high_level_examples}/test_3d_TAH_path/sim_total_artificial_heart.h (100%) rename {cases_high_level_simulation => tests/high_level_examples}/test_3d_TAH_path/total_artificial_heart.cpp (100%) create mode 100644 tests/unit_tests_src/CMakeLists.txt create mode 100644 tests/unit_tests_src/for_2D_build/CMakeLists.txt create mode 100644 tests/unit_tests_src/for_3D_build/CMakeLists.txt create mode 100644 tests/unit_tests_src/for_3D_build/high_level_simulation_class/CMakeLists.txt rename {cases_high_level_simulation/test_3d_unit_position_based_bc => tests/unit_tests_src/for_3D_build/high_level_simulation_class/position_based_boundary_conditions}/CMakeLists.txt (90%) rename {cases_high_level_simulation/test_3d_ball_position_solid_body => tests/unit_tests_src/for_3D_build/high_level_simulation_class/position_based_boundary_conditions}/input/ball_mass.stl (100%) rename {cases_high_level_simulation/test_3d_unit_position_based_bc => tests/unit_tests_src/for_3D_build/high_level_simulation_class/position_based_boundary_conditions}/input/cylinder.stl (100%) rename {cases_high_level_simulation/test_3d_unit_position_based_bc => tests/unit_tests_src/for_3D_build/high_level_simulation_class/position_based_boundary_conditions}/position_based_boundary_conditions.cpp (66%) rename {cases_high_level_simulation/test_3d_unit_time_dep_contact => tests/unit_tests_src/for_3D_build/high_level_simulation_class/time_dependent_contact}/CMakeLists.txt (93%) rename {cases_high_level_simulation/test_3d_unit_time_dep_contact => tests/unit_tests_src/for_3D_build/high_level_simulation_class/time_dependent_contact}/input/mock_stent.stl (100%) rename {cases_high_level_simulation/test_3d_unit_time_dep_contact => tests/unit_tests_src/for_3D_build/high_level_simulation_class/time_dependent_contact}/input/plate.stl (100%) rename {cases_high_level_simulation/test_3d_unit_time_dep_contact => tests/unit_tests_src/for_3D_build/high_level_simulation_class/time_dependent_contact}/input/plate2.stl (100%) rename {cases_high_level_simulation/test_3d_unit_time_dep_contact => tests/unit_tests_src/for_3D_build/high_level_simulation_class/time_dependent_contact}/input/plate_stent.stl (100%) rename {cases_high_level_simulation/test_3d_unit_time_dep_contact => tests/unit_tests_src/for_3D_build/high_level_simulation_class/time_dependent_contact}/input/vessel_cylinder.stl (100%) rename cases_high_level_simulation/test_3d_unit_time_dep_contact/time_dep_contact.cpp => tests/unit_tests_src/for_3D_build/high_level_simulation_class/time_dependent_contact/time_dependent_contact.cpp (90%) create mode 100644 tests/unit_tests_src/shared/CMakeLists.txt diff --git a/SPHINXsys/src/CMakeLists.txt b/SPHINXsys/src/CMakeLists.txt index 4779204823..f4bf825a57 100644 --- a/SPHINXsys/src/CMakeLists.txt +++ b/SPHINXsys/src/CMakeLists.txt @@ -12,7 +12,7 @@ LIST(REMOVE_DUPLICATES usefuldirs) SET(usefulsubdirs ${usefuldirs}) LIST(REMOVE_ITEM usefulsubdirs ${CMAKE_CURRENT_SOURCE_DIR}) -if(DEFINED BOOST_AVAILABLE) +if(NOT ONLY_3D) ADD_SUBDIRECTORY(for_2D_build) endif() ADD_SUBDIRECTORY(for_3D_build) diff --git a/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp b/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp index 994a5f56a4..7db88200a4 100644 --- a/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp +++ b/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.cpp @@ -58,7 +58,7 @@ void expandBoundingBox(BoundingBox* original, BoundingBox* additional) } void relaxParticlesSingleResolution(In_Output* in_output, - bool write_particles_to_file, + bool write_particle_relaxation_data, ImportedModel* imported_model, ElasticSolidParticles* imported_model_particles, BodyRelationInner* imported_model_inner) @@ -78,33 +78,29 @@ void relaxParticlesSingleResolution(In_Output* in_output, //---------------------------------------------------------------------- random_imported_model_particles.parallel_exec(0.25); relaxation_step_inner.surface_bounding_.parallel_exec(); - if (write_particles_to_file) + if (write_particle_relaxation_data) { write_imported_model_to_vtu.writeToFile(0.0); } imported_model->updateCellLinkedList(); - if (write_particles_to_file) - { - cell_linked_list_recording.writeToFile(0.0); - } //---------------------------------------------------------------------- // Particle relaxation time stepping start here. //---------------------------------------------------------------------- int ite_p = 0; - while (ite_p < 500) + while (ite_p < 1000) { relaxation_step_inner.parallel_exec(); ite_p += 1; if (ite_p % 100 == 0) { - cout << fixed << setprecision(9) << "Relaxation steps for the imported model N = " << ite_p << "\n"; - if (write_particles_to_file) + std::cout << std::fixed << std::setprecision(9) << "Relaxation steps for the imported model N = " << ite_p << "\n"; + if (write_particle_relaxation_data) { - write_imported_model_to_vtu.writeToFile(Real(ite_p) * 1.0e-4); + write_imported_model_to_vtu.writeToFile(ite_p); } } } - cout << "The physics relaxation process of imported model finish !" << endl; + std::cout << "The physics relaxation process of the imported model finished !" << std::endl; } StructuralSimulationInput::StructuralSimulationInput( @@ -131,6 +127,7 @@ StructuralSimulationInput::StructuralSimulationInput( // particle_relaxation option particle_relaxation_list_ = {}; for (size_t i = 0; i < resolution_list_.size(); i++){ particle_relaxation_list_.push_back(true); } + write_particle_relaxation_data_ = false; // scale system boundaries scale_system_boundaries_ = 1; // boundary conditions @@ -141,6 +138,7 @@ StructuralSimulationInput::StructuralSimulationInput( position_solid_body_tuple_ = {}; position_scale_solid_body_tuple_ = {}; translation_solid_body_tuple_ = {}; + translation_solid_body_part_tuple_ = {}; }; /////////////////////////////////////// @@ -161,6 +159,7 @@ StructuralSimulation::StructuralSimulation(StructuralSimulationInput& input): // default system, optional: particle relaxation, scale_system_boundaries particle_relaxation_list_(input.particle_relaxation_list_), + write_particle_relaxation_data_(input.write_particle_relaxation_data_), system_resolution_(0.0), system_(SPHSystem(BoundingBox(Vec3d(0), Vec3d(0)), system_resolution_)), scale_system_boundaries_(input.scale_system_boundaries_), @@ -173,7 +172,15 @@ StructuralSimulation::StructuralSimulation(StructuralSimulationInput& input): body_indeces_fixed_constraint_(input.body_indeces_fixed_constraint_), position_solid_body_tuple_(input.position_solid_body_tuple_), position_scale_solid_body_tuple_(input.position_scale_solid_body_tuple_), - translation_solid_body_tuple_(input.translation_solid_body_tuple_) + translation_solid_body_tuple_(input.translation_solid_body_tuple_), + translation_solid_body_part_tuple_(input.translation_solid_body_part_tuple_), + + // iterators + iteration_(0), + + // data storage + von_mises_stress_max_({}) + { // scaling of translation and resolution scaleTranslationAndResolution(); @@ -199,6 +206,7 @@ StructuralSimulation::StructuralSimulation(StructuralSimulationInput& input): initializePositionSolidBody(); initializePositionScaleSolidBody(); initializeTranslateSolidBody(); + initializeTranslateSolidBodyPart(); // initialize simulation initializeSimulation(); @@ -281,7 +289,7 @@ void StructuralSimulation::initializeElasticSolidBodies() solid_body_list_.emplace_back(make_shared(system_, imported_stl_list_[i], body_mesh_list_[i], particle_adaptation_list_[i], physical_viscosity_, material_model_list_[i])); if (particle_relaxation_list_[i]) { - relaxParticlesSingleResolution(&in_output_, false, solid_body_list_[i]->getImportedModel(), solid_body_list_[i]->getElasticSolidParticles(), solid_body_list_[i]->getInnerBodyRelation()); + relaxParticlesSingleResolution(&in_output_, write_particle_relaxation_data_, solid_body_list_[i]->getImportedModel(), solid_body_list_[i]->getElasticSolidParticles(), solid_body_list_[i]->getInnerBodyRelation()); } } } @@ -354,7 +362,7 @@ void StructuralSimulation::initializeAccelerationForBodyPartInBoundingBox() { SolidBody* solid_body = solid_body_list_[get<0>(acceleration_bounding_box_tuple_[i])]->getImportedModel(); acceleration_bounding_box_.emplace_back(make_shared - (solid_body, &get<1>(acceleration_bounding_box_tuple_[i]), get<2>(acceleration_bounding_box_tuple_[i]))); + (solid_body, get<1>(acceleration_bounding_box_tuple_[i]), get<2>(acceleration_bounding_box_tuple_[i]))); } } @@ -418,9 +426,29 @@ void StructuralSimulation::initializeTranslateSolidBody() Real start_time = get<1>(translation_solid_body_tuple_[i]); Real end_time = get<2>(translation_solid_body_tuple_[i]); Vecd translation = get<3>(translation_solid_body_tuple_[i]); - BodyPartByParticleTriMesh* bp = new BodyPartByParticleTriMesh(solid_body_list_[body_index]->getImportedModel(), imported_stl_list_[body_index], &body_mesh_list_[body_index]); + BodyPartByParticleTriMesh* bp = new BodyPartByParticleTriMesh( + solid_body_list_[body_index]->getImportedModel(), imported_stl_list_[body_index], &body_mesh_list_[body_index]); + + translation_solid_body_.emplace_back(make_shared( + solid_body_list_[body_index]->getImportedModel(), bp, start_time, end_time, translation)); + } +} + +void StructuralSimulation::initializeTranslateSolidBodyPart() +{ + translation_solid_body_part_ = {}; + for (size_t i = 0; i < translation_solid_body_part_tuple_.size(); i++) + { + int body_index = get<0>(translation_solid_body_part_tuple_[i]); + Real start_time = get<1>(translation_solid_body_part_tuple_[i]); + Real end_time = get<2>(translation_solid_body_part_tuple_[i]); + Vecd translation = get<3>(translation_solid_body_part_tuple_[i]); + BoundingBox bbox = get<4>(translation_solid_body_part_tuple_[i]); + BodyPartByParticleTriMesh* bp = new BodyPartByParticleTriMesh( + solid_body_list_[body_index]->getImportedModel(), imported_stl_list_[body_index], &body_mesh_list_[body_index]); - translation_solid_body_.emplace_back(make_shared(solid_body_list_[body_index]->getImportedModel(), bp, start_time, end_time, translation)); + translation_solid_body_part_.emplace_back(make_shared( + solid_body_list_[body_index]->getImportedModel(), bp, start_time, end_time, translation, bbox)); } } @@ -546,6 +574,14 @@ void StructuralSimulation::executeTranslateSolidBody(Real dt) } } +void StructuralSimulation::executeTranslateSolidBodyPart(Real dt) +{ + for (size_t i = 0; i < translation_solid_body_part_.size(); i++) + { + translation_solid_body_part_[i]->parallel_exec(dt); + } +} + void StructuralSimulation::executeDamping(Real dt) { for (size_t i = 0; i < solid_body_list_.size(); i++) @@ -597,14 +633,29 @@ void StructuralSimulation::executeContactUpdateConfiguration() } } -void StructuralSimulation::runSimulationStep(int &ite, Real &dt, Real &integration_time) +void StructuralSimulation::initializeSimulation() +{ + GlobalStaticVariables::physical_time_ = 0.0; + + /** INITIALALIZE SYSTEM */ + system_.initializeSystemCellLinkedLists(); + system_.initializeSystemConfigurations(); + + /** INITIAL CONDITION */ + executeCorrectConfiguration(); +} + +void StructuralSimulation::runSimulationStep(Real &dt, Real &integration_time) { - if (ite % 100 == 0) cout << "N=" << ite << " Time: " << GlobalStaticVariables::physical_time_ << " dt: " << dt << "\n"; + if (iteration_ % 100 == 0) cout << "N=" << iteration_ << " Time: " << GlobalStaticVariables::physical_time_ << " dt: " << dt << "\n"; /** ACTIVE BOUNDARY CONDITIONS */ + // force (acceleration) based executeinitializeATimeStep(); executeAccelerationForBodyPartInBoundingBox(); executeSpringDamperConstraintParticleWise(); + // velocity based + executeTranslateSolidBodyPart(dt); /** CONTACT */ executeContactDensitySummation(); @@ -617,6 +668,8 @@ void StructuralSimulation::runSimulationStep(int &ite, Real &dt, Real &integrati executePositionSolidBody(dt); executePositionScaleSolidBody(dt); executeTranslateSolidBody(dt); + // velocity based + executeTranslateSolidBodyPart(dt); executeDamping(dt); @@ -624,11 +677,13 @@ void StructuralSimulation::runSimulationStep(int &ite, Real &dt, Real &integrati executePositionSolidBody(dt); executePositionScaleSolidBody(dt); executeTranslateSolidBody(dt); + // velocity based + executeTranslateSolidBodyPart(dt); executeStressRelaxationSecondHalf(dt); /** UPDATE TIME STEP SIZE, INCREMENT */ - ite++; + iteration_++; dt = system_.getSmallestTimeStepAmongSolidBodies(); integration_time += dt; GlobalStaticVariables::physical_time_ += dt; @@ -643,18 +698,9 @@ void StructuralSimulation::runSimulationStep(int &ite, Real &dt, Real &integrati void StructuralSimulation::runSimulation(Real end_time) { BodyStatesRecordingToVtu write_states(in_output_, system_.real_bodies_); - GlobalStaticVariables::physical_time_ = 0.0; - - /** INITIALALIZE SYSTEM */ - system_.initializeSystemCellLinkedLists(); - system_.initializeSystemConfigurations(); - - /** INITIAL CONDITION */ - executeCorrectConfiguration(); /** Statistics for computing time. */ write_states.writeToFile(0); - int ite = 0; Real output_period = end_time / 100.0; Real dt = 0.0; tick_count t1 = tick_count::now(); @@ -665,9 +711,12 @@ void StructuralSimulation::runSimulation(Real end_time) Real integration_time = 0.0; while (integration_time < output_period) { - runSimulationStep(ite, dt, integration_time); + runSimulationStep(dt, integration_time); } tick_count t2 = tick_count::now(); + // record data for test + von_mises_stress_max_.push_back(solid_body_list_[0].get()->getElasticSolidParticles()->getMaxVonMisesStress()); + // write data to file write_states.writeToFile(); tick_count t3 = tick_count::now(); interval += t3 - t2; @@ -678,16 +727,6 @@ void StructuralSimulation::runSimulation(Real end_time) cout << "Total wall time for computation: " << tt.seconds() << " seconds." << endl; } -void StructuralSimulation::initializeSimulation() -{ - /** INITIALALIZE SYSTEM */ - system_.initializeSystemCellLinkedLists(); - system_.initializeSystemConfigurations(); - - /** INITIAL CONDITION */ - executeCorrectConfiguration(); -} - double StructuralSimulation::runSimulationFixedDurationJS(int number_of_steps) { BodyStatesRecordingToVtu write_states(in_output_, system_.real_bodies_); @@ -696,18 +735,17 @@ double StructuralSimulation::runSimulationFixedDurationJS(int number_of_steps) /** Statistics for computing time. */ write_states.writeToFile(0); int output_period = 100; - int ite = 0; Real dt = 0.0; tick_count t1 = tick_count::now(); tick_count::interval_t interval; /** Main loop */ - while (ite < number_of_steps) + while (iteration_ < number_of_steps) { Real integration_time = 0.0; int output_step = 0; while (output_step < output_period) { - runSimulationStep(ite, dt, integration_time); + runSimulationStep(dt, integration_time); output_step++; } tick_count t2 = tick_count::now(); diff --git a/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.h b/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.h index b66783c9fa..04abf89919 100644 --- a/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.h +++ b/SPHINXsys/src/for_3D_build/high_level_simulation_class/solid_structural_simulation_class.h @@ -21,6 +21,7 @@ using SpringDamperTuple = tuple; using PositionSolidBodyTuple = tuple; using PositionScaleSolidBodyTuple = tuple; using TranslateSolidBodyTuple = tuple; +using TranslateSolidBodyPartTuple = tuple; class BodyPartByParticleTriMesh : public BodyPartByParticle { @@ -89,6 +90,7 @@ class StructuralSimulationInput Real scale_system_boundaries_; // particle relaxation vector particle_relaxation_list_; + bool write_particle_relaxation_data_; // boundary conditions vector non_zero_gravity_; vector acceleration_bounding_box_tuple_; @@ -97,6 +99,7 @@ class StructuralSimulationInput vector position_solid_body_tuple_; vector position_scale_solid_body_tuple_; vector translation_solid_body_tuple_; + vector translation_solid_body_part_tuple_; StructuralSimulationInput( string relative_input_path, @@ -124,6 +127,7 @@ class StructuralSimulation vector> contacting_body_pairs_list_; vector, array>> time_dep_contacting_body_pairs_list_; //optional: time dependent contact vector particle_relaxation_list_; // optional: particle relaxation + bool write_particle_relaxation_data_; // internal members Real system_resolution_; @@ -160,6 +164,15 @@ class StructuralSimulation // for TranslateSolidBody vector> translation_solid_body_; vector translation_solid_body_tuple_; + // for TranslateSolidBodyPart + vector> translation_solid_body_part_; + vector translation_solid_body_part_tuple_; + + // iterators + int iteration_; + + // data storage + vector von_mises_stress_max_; // for constructor, the order is important void scaleTranslationAndResolution(); @@ -179,6 +192,7 @@ class StructuralSimulation void initializePositionSolidBody(); void initializePositionScaleSolidBody(); void initializeTranslateSolidBody(); + void initializeTranslateSolidBodyPart(); // for runSimulation, the order is important void executeCorrectConfiguration(); @@ -192,15 +206,16 @@ class StructuralSimulation void executePositionSolidBody(Real dt); void executePositionScaleSolidBody(Real dt); void executeTranslateSolidBody(Real dt); + void executeTranslateSolidBodyPart(Real dt); void executeDamping(Real dt); void executeStressRelaxationSecondHalf(Real dt); void executeUpdateCellLinkedList(); void executeContactUpdateConfiguration(); - void runSimulationStep(int &ite, Real &dt, Real &integration_time); - // initialize simulation void initializeSimulation(); + void runSimulationStep(Real &dt, Real &integration_time); + public: StructuralSimulation(StructuralSimulationInput& input); ~StructuralSimulation(); diff --git a/SPHINXsys/src/for_3D_build/high_level_simulation_class/test_structural_simulation_class.h b/SPHINXsys/src/for_3D_build/high_level_simulation_class/test_structural_simulation_class.h index 6019e27559..9c9e078e77 100644 --- a/SPHINXsys/src/for_3D_build/high_level_simulation_class/test_structural_simulation_class.h +++ b/SPHINXsys/src/for_3D_build/high_level_simulation_class/test_structural_simulation_class.h @@ -8,45 +8,48 @@ class TestStructuralSimulation : StructuralSimulation TestStructuralSimulation(StructuralSimulationInput& input) : StructuralSimulation(input){}; void TestRunSimulation(Real end_time){ runSimulation(end_time); }; // input members - string Get_relative_input_path_(){ return relative_input_path_; }; - vector Get_imported_stl_list_(){ return imported_stl_list_; }; - Real Get_scale_stl_(){ return scale_stl_; }; - vector Get_translation_list_(){ return translation_list_; }; - Real Get_system_resolution_(){ return system_resolution_; }; - vector Get_resolution_list_(){ return resolution_list_; }; - vector Get_material_model_list_(){ return material_model_list_; }; - Real Get_physical_viscosity_(){ return physical_viscosity_; }; + string get_relative_input_path_(){ return relative_input_path_; }; + vector get_imported_stl_list_(){ return imported_stl_list_; }; + Real get_scale_stl_(){ return scale_stl_; }; + vector get_translation_list_(){ return translation_list_; }; + Real get_system_resolution_(){ return system_resolution_; }; + vector get_resolution_list_(){ return resolution_list_; }; + vector get_material_model_list_(){ return material_model_list_; }; + Real get_physical_viscosity_(){ return physical_viscosity_; }; // internal members - SPHSystem Get_system_(){ return system_; }; - Real Get_scale_system_boundaries_(){ return scale_system_boundaries_; }; - In_Output Get_in_output_(){ return in_output_; }; + SPHSystem get_system_(){ return system_; }; + Real get_scale_system_boundaries_(){ return scale_system_boundaries_; }; + In_Output get_in_output_(){ return in_output_; }; // other - vector Get_body_mesh_list_(){ return body_mesh_list_; }; - vector> Get_solid_body_list_(){ return solid_body_list_; }; - vector> Get_contacting_body_pairs_list_(){ return contacting_body_pairs_list_; }; - vector, array>> Get_time_dep_contacting_body_pairs_list_(){ return time_dep_contacting_body_pairs_list_; }; - vector> Get_contact_list_(){ return contact_list_; }; - vector> Get_contact_density_list_(){ return contact_density_list_; }; - vector> Get_contact_force_list_(){ return contact_force_list_; }; + vector get_body_mesh_list_(){ return body_mesh_list_; }; + vector> get_solid_body_list_(){ return solid_body_list_; }; + vector> get_contacting_body_pairs_list_(){ return contacting_body_pairs_list_; }; + vector, array>> get_time_dep_contacting_body_pairs_list_(){ return time_dep_contacting_body_pairs_list_; }; + vector> get_contact_list_(){ return contact_list_; }; + vector> get_contact_density_list_(){ return contact_density_list_; }; + vector> get_contact_force_list_(){ return contact_force_list_; }; // for initializeATimeStep - vector> Get_initialize_gravity_(){ return initialize_gravity_; }; - vector Get_non_zero_gravity_(){ return non_zero_gravity_; }; + vector> get_initialize_gravity_(){ return initialize_gravity_; }; + vector get_non_zero_gravity_(){ return non_zero_gravity_; }; // for AccelerationForBodyPartInBoundingBox - vector> Get_acceleration_bounding_box_(){ return acceleration_bounding_box_; }; - vector Get_acceleration_bounding_box_tuple_(){ return acceleration_bounding_box_tuple_; }; + vector> get_acceleration_bounding_box_(){ return acceleration_bounding_box_; }; + vector get_acceleration_bounding_box_tuple_(){ return acceleration_bounding_box_tuple_; }; // for SpringDamperConstraintParticleWise - vector> Get_spring_damper_constraint_(){ return spring_damper_constraint_; }; - vector Get_spring_damper_tuple_(){ return spring_damper_tuple_; }; + vector> get_spring_damper_constraint_(){ return spring_damper_constraint_; }; + vector get_spring_damper_tuple_(){ return spring_damper_tuple_; }; // for ConstrainSolidBodyRegion - vector> Get_fixed_constraint_(){ return fixed_constraint_; }; - vector Get_body_indeces_fixed_constraint_(){ return body_indeces_fixed_constraint_; }; + vector> get_fixed_constraint_(){ return fixed_constraint_; }; + vector get_body_indeces_fixed_constraint_(){ return body_indeces_fixed_constraint_; }; // for PositionSolidBody - vector> Get_position_solid_body_(){ return position_solid_body_; }; - vector Get_position_solid_body_tuple_(){ return position_solid_body_tuple_; }; + vector> get_position_solid_body_(){ return position_solid_body_; }; + vector get_position_solid_body_tuple_(){ return position_solid_body_tuple_; }; // for PositionScaleSolidBody - vector> Get_position_scale_solid_body_(){ return position_scale_solid_body_; }; - vector Get_position_scale_solid_body_tuple_(){ return position_scale_solid_body_tuple_; }; + vector> get_position_scale_solid_body_(){ return position_scale_solid_body_; }; + vector get_position_scale_solid_body_tuple_(){ return position_scale_solid_body_tuple_; }; // for TranslateSolidBody - vector> Get_translation_solid_body_(){ return translation_solid_body_; }; - vector Get_translation_solid_body_tuple_(){ return translation_solid_body_tuple_; }; + vector> get_translation_solid_body_(){ return translation_solid_body_; }; + vector get_translation_solid_body_tuple_(){ return translation_solid_body_tuple_; }; + + // get data + vector get_von_mises_stress_max_(){ return von_mises_stress_max_; }; }; \ No newline at end of file diff --git a/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.h b/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.h index d5fd520b65..fa446e517d 100644 --- a/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.h +++ b/SPHINXsys/src/for_3D_build/particle_generator/particle_generator_network.h @@ -31,7 +31,7 @@ #define PARTICLE_GENERATOR_NETWORK_H -#include "sph_data_conainers.h" +#include "sph_data_containers.h" #include "base_particle_generator.h" #include "generative_structures.h" diff --git a/SPHINXsys/src/shared/bodies/base_body.h b/SPHINXsys/src/shared/bodies/base_body.h index f6903912f6..166989f73e 100644 --- a/SPHINXsys/src/shared/bodies/base_body.h +++ b/SPHINXsys/src/shared/bodies/base_body.h @@ -37,7 +37,7 @@ #define BASE_BODY_H #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" #include "particle_adaptation.h" #include "all_particle_generators.h" #include "particle_sorting.h" diff --git a/SPHINXsys/src/shared/bodies/body_relation.cpp b/SPHINXsys/src/shared/bodies/body_relation.cpp index 11569dbc45..cab90d06c3 100644 --- a/SPHINXsys/src/shared/bodies/body_relation.cpp +++ b/SPHINXsys/src/shared/bodies/body_relation.cpp @@ -13,10 +13,10 @@ namespace SPH { //=================================================================================================// - SPHBodyRelation::SPHBodyRelation(SPHBody* sph_body) + SPHBodyRelation::SPHBodyRelation(SPHBody *sph_body) : sph_body_(sph_body), base_particles_(sph_body->base_particles_) {} //=================================================================================================// - BaseBodyRelationInner::BaseBodyRelationInner(RealBody* real_body) + BaseBodyRelationInner::BaseBodyRelationInner(RealBody *real_body) : SPHBodyRelation(real_body), real_body_(real_body) { subscribeToBody(); @@ -29,37 +29,45 @@ namespace SPH inner_configuration_.resize(updated_size, Neighborhood()); } //=================================================================================================// - void BaseBodyRelationInner::resetNeighborhoodCurrentSize() + void BaseBodyRelationInner::resetNeighborhoodCurrentSize() { - parallel_for(blocked_range(0, base_particles_->total_real_particles_), - [&](const blocked_range& r) { - for (size_t num = r.begin(); num != r.end(); ++num) { + parallel_for( + blocked_range(0, base_particles_->total_real_particles_), + [&](const blocked_range &r) + { + for (size_t num = r.begin(); num != r.end(); ++num) + { inner_configuration_[num].current_size_ = 0; } - }, ap); + }, + ap); } //=================================================================================================// - BodyRelationInner::BodyRelationInner(RealBody* real_body) + BodyRelationInner::BodyRelationInner(RealBody *real_body) : BaseBodyRelationInner(real_body), get_inner_neighbor_(real_body), - cell_linked_list_(dynamic_cast(real_body->cell_linked_list_)) {} + cell_linked_list_(dynamic_cast(real_body->cell_linked_list_)) {} //=================================================================================================// void BodyRelationInner::updateConfiguration() { resetNeighborhoodCurrentSize(); - cell_linked_list_->searchNeighborsByParticles(base_particles_->total_real_particles_, *base_particles_, - inner_configuration_, get_particle_index_, get_single_search_depth_, get_inner_neighbor_); + cell_linked_list_ + ->searchNeighborsByParticles(base_particles_->total_real_particles_, + *base_particles_, inner_configuration_, + get_particle_index_, get_single_search_depth_, + get_inner_neighbor_); } //=================================================================================================// BodyRelationInnerVariableSmoothingLength:: - BodyRelationInnerVariableSmoothingLength(RealBody* real_body) + BodyRelationInnerVariableSmoothingLength(RealBody *real_body) : BaseBodyRelationInner(real_body), total_levels_(0), - get_inner_neighbor_variable_smoothing_length_(real_body) + get_inner_neighbor_variable_smoothing_length_(real_body) { - MultilevelCellLinkedList* multi_level_cell_linked_list = - dynamic_cast(real_body->cell_linked_list_); + MultilevelCellLinkedList *multi_level_cell_linked_list = + dynamic_cast(real_body->cell_linked_list_); cell_linked_list_levels_ = multi_level_cell_linked_list->getMeshLevels(); total_levels_ = cell_linked_list_levels_.size(); - for (size_t l = 0; l != total_levels_; ++l) { + for (size_t l = 0; l != total_levels_; ++l) + { get_multi_level_search_depth_.push_back( new SearchDepthVariableSmoothingLength(real_body, cell_linked_list_levels_[l])); } @@ -68,26 +76,64 @@ namespace SPH void BodyRelationInnerVariableSmoothingLength::updateConfiguration() { resetNeighborhoodCurrentSize(); - for (size_t l = 0; l != total_levels_; ++l) { - cell_linked_list_levels_[l]->searchNeighborsByParticles(base_particles_->total_real_particles_, - *base_particles_, inner_configuration_, get_particle_index_, - *get_multi_level_search_depth_[l], get_inner_neighbor_variable_smoothing_length_); + for (size_t l = 0; l != total_levels_; ++l) + { + cell_linked_list_levels_[l] + ->searchNeighborsByParticles(base_particles_->total_real_particles_, + *base_particles_, inner_configuration_, get_particle_index_, + *get_multi_level_search_depth_[l], + get_inner_neighbor_variable_smoothing_length_); } - } + } + //=================================================================================================// + SolidBodyRelationSelfContact:: + SolidBodyRelationSelfContact(RealBody *real_body) + : BaseBodyRelationInner(real_body), + body_surface_layer_(ShapeSurfaceLayer(real_body)), + body_part_particles_(body_surface_layer_.body_part_particles_), + get_body_part_particle_index_(body_part_particles_), + get_self_contact_neighbor_(real_body), + cell_linked_list_(dynamic_cast(real_body->cell_linked_list_)) {} + //=================================================================================================// + void SolidBodyRelationSelfContact::resetNeighborhoodCurrentSize() + { + parallel_for( + blocked_range(0, body_part_particles_.size()), + [&](const blocked_range &r) + { + for (size_t num = r.begin(); num != r.end(); ++num) + { + size_t index_i = get_body_part_particle_index_(num); + inner_configuration_[index_i].current_size_ = 0; + } + }, + ap); + } //=================================================================================================// - BaseBodyRelationContact::BaseBodyRelationContact(SPHBody* sph_body, RealBodyVector contact_sph_bodies) + void SolidBodyRelationSelfContact::updateConfiguration() + { + resetNeighborhoodCurrentSize(); + size_t total_real_particles = body_part_particles_.size(); + cell_linked_list_ + ->searchNeighborsByParticles(total_real_particles, + *base_particles_, inner_configuration_, + get_body_part_particle_index_, get_single_search_depth_, + get_self_contact_neighbor_); + } + //=================================================================================================// + BaseBodyRelationContact::BaseBodyRelationContact(SPHBody *sph_body, RealBodyVector contact_sph_bodies) : SPHBodyRelation(sph_body), contact_bodies_(contact_sph_bodies) { subscribeToBody(); updateConfigurationMemories(); } //=================================================================================================// - BaseBodyRelationContact::BaseBodyRelationContact(SPHBody* sph_body, BodyPartVector contact_body_parts) + BaseBodyRelationContact::BaseBodyRelationContact(SPHBody *sph_body, BodyPartVector contact_body_parts) : SPHBodyRelation(sph_body) { - for(size_t k = 0; k != contact_body_parts.size(); ++k) + for (size_t k = 0; k != contact_body_parts.size(); ++k) { - contact_bodies_.push_back(dynamic_cast(contact_body_parts[k]->getBody())); + contact_bodies_.push_back(dynamic_cast(contact_body_parts[k]->getBody())); } subscribeToBody(); updateConfigurationMemories(); @@ -97,30 +143,36 @@ namespace SPH { size_t updated_size = sph_body_->base_particles_->real_particles_bound_; contact_configuration_.resize(contact_bodies_.size()); - for (size_t k = 0; k != contact_bodies_.size(); ++k) { + for (size_t k = 0; k != contact_bodies_.size(); ++k) + { contact_configuration_[k].resize(updated_size, Neighborhood()); } } //=================================================================================================// - void BaseBodyRelationContact::resetNeighborhoodCurrentSize() + void BaseBodyRelationContact::resetNeighborhoodCurrentSize() { - for (size_t k = 0; k != contact_bodies_.size(); ++k) { - parallel_for(blocked_range(0, base_particles_->total_real_particles_), - [&](const blocked_range& r) { - for (size_t num = r.begin(); num != r.end(); ++num) { + for (size_t k = 0; k != contact_bodies_.size(); ++k) + { + parallel_for( + blocked_range(0, base_particles_->total_real_particles_), + [&](const blocked_range &r) + { + for (size_t num = r.begin(); num != r.end(); ++num) + { contact_configuration_[k][num].current_size_ = 0; } - }, ap); + }, + ap); } } //=================================================================================================// - BodyRelationContact::BodyRelationContact(SPHBody* sph_body, RealBodyVector contact_sph_bodies) + BodyRelationContact::BodyRelationContact(SPHBody *sph_body, RealBodyVector contact_sph_bodies) : BaseBodyRelationContact(sph_body, contact_sph_bodies) { initialization(); } //=================================================================================================// - BodyRelationContact::BodyRelationContact(SPHBody* sph_body, BodyPartVector contact_body_parts) + BodyRelationContact::BodyRelationContact(SPHBody *sph_body, BodyPartVector contact_body_parts) : BaseBodyRelationContact(sph_body, contact_body_parts) { initialization(); @@ -130,8 +182,8 @@ namespace SPH { for (size_t k = 0; k != contact_bodies_.size(); ++k) { - CellLinkedList* target_cell_linked_list = - dynamic_cast(contact_bodies_[k]->cell_linked_list_); + CellLinkedList *target_cell_linked_list = + dynamic_cast(contact_bodies_[k]->cell_linked_list_); target_cell_linked_lists_.push_back(target_cell_linked_list); get_search_depths_.push_back(new SearchDepthMultiResolution(sph_body_, target_cell_linked_list)); get_contact_neighbors_.push_back(new NeighborRelationContact(sph_body_, contact_bodies_[k])); @@ -142,32 +194,51 @@ namespace SPH { resetNeighborhoodCurrentSize(); size_t total_real_particles = base_particles_->total_real_particles_; - for (size_t k = 0; k != contact_bodies_.size(); ++k) { - target_cell_linked_lists_[k]->searchNeighborsByParticles(total_real_particles, - *base_particles_, contact_configuration_[k], - get_particle_index_, *get_search_depths_[k], *get_contact_neighbors_[k]); + for (size_t k = 0; k != contact_bodies_.size(); ++k) + { + target_cell_linked_lists_[k] + ->searchNeighborsByParticles(total_real_particles, + *base_particles_, contact_configuration_[k], + get_particle_index_, *get_search_depths_[k], + *get_contact_neighbors_[k]); } } //=================================================================================================// - SolidBodyRelationContact::SolidBodyRelationContact(SPHBody* sph_body, RealBodyVector contact_sph_bodies) - : BaseBodyRelationContact(sph_body, contact_sph_bodies), - body_part_particles_(body_surface_layer_.body_part_particles_), - get_body_part_particle_index_(body_part_particles_), - body_surface_layer_(ShapeSurfaceLayer(sph_body)) + SolidBodyRelationContact::SolidBodyRelationContact(SPHBody *sph_body, RealBodyVector contact_bodies) + : BaseBodyRelationContact(sph_body, contact_bodies), + body_surface_layer_(ShapeSurfaceLayer(sph_body)), + body_part_particles_(body_surface_layer_.body_part_particles_), + get_body_part_particle_index_(body_part_particles_) + { + initialization(); + } + //=================================================================================================// + SolidBodyRelationContact:: + SolidBodyRelationContact(SolidBodyRelationSelfContact *solid_body_relation_self_contact, + RealBodyVector contact_bodies) + : BaseBodyRelationContact(solid_body_relation_self_contact->real_body_, contact_bodies), + body_surface_layer_(solid_body_relation_self_contact->body_surface_layer_), + body_part_particles_(body_surface_layer_.body_part_particles_), + get_body_part_particle_index_(body_part_particles_) { initialization(); } //=================================================================================================// - void SolidBodyRelationContact::resetNeighborhoodCurrentSize() + void SolidBodyRelationContact::resetNeighborhoodCurrentSize() { - for (size_t k = 0; k != contact_bodies_.size(); ++k) { - parallel_for(blocked_range(0, body_part_particles_.size()), - [&](const blocked_range& r) { - for (size_t num = r.begin(); num != r.end(); ++num) { + for (size_t k = 0; k != contact_bodies_.size(); ++k) + { + parallel_for( + blocked_range(0, body_part_particles_.size()), + [&](const blocked_range &r) + { + for (size_t num = r.begin(); num != r.end(); ++num) + { size_t index_i = get_body_part_particle_index_(num); contact_configuration_[k][index_i].current_size_ = 0; } - }, ap); + }, + ap); } } //=================================================================================================// @@ -175,8 +246,8 @@ namespace SPH { for (size_t k = 0; k != contact_bodies_.size(); ++k) { - CellLinkedList* target_cell_linked_list = - dynamic_cast(contact_bodies_[k]->cell_linked_list_); + CellLinkedList *target_cell_linked_list = + dynamic_cast(contact_bodies_[k]->cell_linked_list_); target_cell_linked_lists_.push_back(target_cell_linked_list); get_search_depths_.push_back(new SearchDepthMultiResolution(sph_body_, target_cell_linked_list)); get_contact_neighbors_.push_back(new NeighborRelationSolidContact(sph_body_, contact_bodies_[k])); @@ -187,10 +258,13 @@ namespace SPH { resetNeighborhoodCurrentSize(); size_t total_real_particles = body_part_particles_.size(); - for (size_t k = 0; k != contact_bodies_.size(); ++k) { - target_cell_linked_lists_[k]->searchNeighborsByParticles(total_real_particles, - *base_particles_, contact_configuration_[k], - get_body_part_particle_index_,*get_search_depths_[k], *get_contact_neighbors_[k]); + for (size_t k = 0; k != contact_bodies_.size(); ++k) + { + target_cell_linked_lists_[k] + ->searchNeighborsByParticles(total_real_particles, + *base_particles_, contact_configuration_[k], + get_body_part_particle_index_, *get_search_depths_[k], + *get_contact_neighbors_[k]); } } //=================================================================================================// @@ -199,25 +273,27 @@ namespace SPH generative_structure_->buildParticleConfiguration(*base_particles_, inner_configuration_); } //=================================================================================================// - BodyPartRelationContact::BodyPartRelationContact(BodyPart* body_part, RealBodyVector contact_bodies) + BodyPartRelationContact::BodyPartRelationContact(BodyPart *body_part, RealBodyVector contact_bodies) : BodyRelationContact(body_part->getBody(), contact_bodies), body_part_(body_part), - body_part_particles_(dynamic_cast(body_part)->body_part_particles_), - get_body_part_particle_index_(dynamic_cast(body_part)->body_part_particles_) + body_part_particles_(dynamic_cast(body_part)->body_part_particles_), + get_body_part_particle_index_(dynamic_cast(body_part)->body_part_particles_) { } //=================================================================================================// void BodyPartRelationContact::updateConfiguration() { size_t number_of_particles = body_part_particles_.size(); - for (size_t k = 0; k != contact_bodies_.size(); ++k) + for (size_t k = 0; k != contact_bodies_.size(); ++k) { - target_cell_linked_lists_[k]->searchNeighborsByParticles(number_of_particles, - *base_particles_, contact_configuration_[k], - get_body_part_particle_index_, *get_search_depths_[k], *get_contact_neighbors_[k]); + target_cell_linked_lists_[k] + ->searchNeighborsByParticles(number_of_particles, + *base_particles_, contact_configuration_[k], + get_body_part_particle_index_, *get_search_depths_[k], + *get_contact_neighbors_[k]); } } //=================================================================================================// - BodyRelationContactToBodyPart::BodyRelationContactToBodyPart(RealBody* real_body, BodyPartVector contact_body_parts) + BodyRelationContactToBodyPart::BodyRelationContactToBodyPart(RealBody *real_body, BodyPartVector contact_body_parts) : BodyRelationContact(real_body, contact_body_parts), contact_body_parts_(contact_body_parts) { for (size_t k = 0; k != contact_bodies_.size(); ++k) @@ -229,33 +305,39 @@ namespace SPH void BodyRelationContactToBodyPart::updateConfiguration() { size_t number_of_particles = base_particles_->total_real_particles_; - for (size_t k = 0; k != contact_body_parts_.size(); ++k) + for (size_t k = 0; k != contact_body_parts_.size(); ++k) { - target_cell_linked_lists_[k]->searchNeighborsByParticles(number_of_particles, - *base_particles_, contact_configuration_[k], - get_particle_index_, *get_search_depths_[k], *get_part_contact_neighbors_[k]); + target_cell_linked_lists_[k] + ->searchNeighborsByParticles(number_of_particles, + *base_particles_, contact_configuration_[k], + get_particle_index_, *get_search_depths_[k], + *get_part_contact_neighbors_[k]); } } //=================================================================================================// - ComplexBodyRelation::ComplexBodyRelation(BaseBodyRelationInner* inner_relation, BaseBodyRelationContact* contact_relation) : - SPHBodyRelation(inner_relation->sph_body_), - inner_relation_(inner_relation), contact_relation_(contact_relation), - contact_bodies_(contact_relation->contact_bodies_), - inner_configuration_(inner_relation->inner_configuration_), - contact_configuration_(contact_relation->contact_configuration_) + ComplexBodyRelation:: + ComplexBodyRelation(BaseBodyRelationInner *inner_relation, BaseBodyRelationContact *contact_relation) + : SPHBodyRelation(inner_relation->sph_body_), + inner_relation_(inner_relation), contact_relation_(contact_relation), + contact_bodies_(contact_relation->contact_bodies_), + inner_configuration_(inner_relation->inner_configuration_), + contact_configuration_(contact_relation->contact_configuration_) { updateConfigurationMemories(); } //=================================================================================================// - ComplexBodyRelation::ComplexBodyRelation(RealBody* real_body, RealBodyVector contact_bodies) : - ComplexBodyRelation(new BodyRelationInner(real_body), new BodyRelationContact(real_body, contact_bodies)) {} + ComplexBodyRelation::ComplexBodyRelation(RealBody *real_body, RealBodyVector contact_bodies) + : ComplexBodyRelation(new BodyRelationInner(real_body), + new BodyRelationContact(real_body, contact_bodies)) {} //=================================================================================================// ComplexBodyRelation:: - ComplexBodyRelation(BaseBodyRelationInner* inner_relation, RealBodyVector contact_bodies) : - ComplexBodyRelation(inner_relation, new BodyRelationContact(inner_relation->sph_body_, contact_bodies)) {} + ComplexBodyRelation(BaseBodyRelationInner *inner_relation, RealBodyVector contact_bodies) + : ComplexBodyRelation(inner_relation, + new BodyRelationContact(inner_relation->sph_body_, contact_bodies)) {} //=================================================================================================// - ComplexBodyRelation::ComplexBodyRelation(RealBody* real_body, BodyPartVector contact_body_parts) - :ComplexBodyRelation(new BodyRelationInner(real_body), new BodyRelationContactToBodyPart(real_body, contact_body_parts)) {} + ComplexBodyRelation::ComplexBodyRelation(RealBody *real_body, BodyPartVector contact_body_parts) + : ComplexBodyRelation(new BodyRelationInner(real_body), + new BodyRelationContactToBodyPart(real_body, contact_body_parts)) {} //=================================================================================================// void ComplexBodyRelation::updateConfigurationMemories() { diff --git a/SPHINXsys/src/shared/bodies/body_relation.h b/SPHINXsys/src/shared/bodies/body_relation.h index 0ae0dbacef..3fb9dadc96 100644 --- a/SPHINXsys/src/shared/bodies/body_relation.h +++ b/SPHINXsys/src/shared/bodies/body_relation.h @@ -28,7 +28,6 @@ * -- Add reduced body relation for network. Chi ZHANG */ - #ifndef BODY_RELATION_H #define BODY_RELATION_H @@ -43,21 +42,21 @@ namespace SPH /** a small functor for obtaining particle index for container index */ struct SPHBodyParticlesIndex { - size_t operator () (size_t particle_index) const { return particle_index; }; + size_t operator()(size_t particle_index) const { return particle_index; }; }; /** a small functor for obtaining particle index for body part container index */ struct BodyPartParticlesIndex { - IndexVector& body_part_particles_; - BodyPartParticlesIndex(IndexVector& body_part_particles) : body_part_particles_(body_part_particles) {}; - size_t operator () (size_t particle_entry) const {return body_part_particles_[particle_entry]; }; + IndexVector &body_part_particles_; + BodyPartParticlesIndex(IndexVector &body_part_particles) : body_part_particles_(body_part_particles){}; + size_t operator()(size_t particle_entry) const { return body_part_particles_[particle_entry]; }; }; /** a small functor for obtaining search range for the simplest case */ - struct SearchDepthSingleResolution + struct SearchDepthSingleResolution { - int operator () (size_t particle_index) const { return 1; }; + int operator()(size_t particle_index) const { return 1; }; }; /** @brief a small functor for obtaining search depth across resolution @@ -66,13 +65,13 @@ namespace SPH struct SearchDepthMultiResolution { int search_depth_; - SearchDepthMultiResolution(SPHBody* body, CellLinkedList* target_cell_linked_list) : search_depth_(1) + SearchDepthMultiResolution(SPHBody *body, CellLinkedList *target_cell_linked_list) : search_depth_(1) { - Real inv_grid_spacing_ = 1.0 / target_cell_linked_list->GridSpacing(); - Kernel* kernel_ = body->particle_adaptation_->getKernel(); - search_depth_ = 1 + (int)floor(kernel_->CutOffRadius() * inv_grid_spacing_); + Real inv_grid_spacing_ = 1.0 / target_cell_linked_list->GridSpacing(); + Kernel *kernel_ = body->particle_adaptation_->getKernel(); + search_depth_ = 1 + (int)floor(kernel_->CutOffRadius() * inv_grid_spacing_); }; - int operator () (size_t particle_index) const { return search_depth_; }; + int operator()(size_t particle_index) const { return search_depth_; }; }; /** @brief a small functor for obtaining search depth for variable smoothing length @@ -81,18 +80,18 @@ namespace SPH struct SearchDepthVariableSmoothingLength { Real inv_grid_spacing_; - Kernel* kernel_; - StdLargeVec& h_ratio_; - SearchDepthVariableSmoothingLength(SPHBody* body, CellLinkedList* target_cell_linked_list) : - inv_grid_spacing_(1.0 / target_cell_linked_list->GridSpacing()), - kernel_(body->particle_adaptation_->getKernel()), - h_ratio_(*body->base_particles_->getVariableByName("SmoothingLengthRatio")) {}; - int operator () (size_t particle_index) const - { - return 1 + (int)floor(kernel_->CutOffRadius(h_ratio_[particle_index]) * inv_grid_spacing_); + Kernel *kernel_; + StdLargeVec &h_ratio_; + SearchDepthVariableSmoothingLength(SPHBody *body, CellLinkedList *target_cell_linked_list) + : inv_grid_spacing_(1.0 / target_cell_linked_list->GridSpacing()), + kernel_(body->particle_adaptation_->getKernel()), + h_ratio_(*body->base_particles_->getVariableByName("SmoothingLengthRatio")){}; + int operator()(size_t particle_index) const + { + return 1 + (int)floor(kernel_->CutOffRadius(h_ratio_[particle_index]) * inv_grid_spacing_); }; }; - + /** * @class SPHBodyRelation * @brief The abstract class for all relations within a SPH body or with its contact SPH bodies @@ -100,11 +99,11 @@ namespace SPH class SPHBodyRelation { public: - SPHBody* sph_body_; - BaseParticles* base_particles_; + SPHBody *sph_body_; + BaseParticles *base_particles_; - SPHBodyRelation(SPHBody* sph_body); - virtual ~SPHBodyRelation() {}; + SPHBodyRelation(SPHBody *sph_body); + virtual ~SPHBodyRelation(){}; void subscribeToBody() { sph_body_->body_relations_.push_back(this); }; virtual void updateConfigurationMemories() = 0; @@ -119,12 +118,13 @@ namespace SPH { protected: virtual void resetNeighborhoodCurrentSize(); + public: - RealBody* real_body_; + RealBody *real_body_; ParticleConfiguration inner_configuration_; /**< inner configuration for the neighbor relations. */ - BaseBodyRelationInner(RealBody* real_body); - virtual ~BaseBodyRelationInner() {}; + BaseBodyRelationInner(RealBody *real_body); + virtual ~BaseBodyRelationInner(){}; virtual void updateConfigurationMemories() override; }; @@ -139,11 +139,11 @@ namespace SPH SPHBodyParticlesIndex get_particle_index_; SearchDepthSingleResolution get_single_search_depth_; NeighborRelationInner get_inner_neighbor_; - CellLinkedList* cell_linked_list_; + CellLinkedList *cell_linked_list_; public: - BodyRelationInner(RealBody* real_body); - virtual ~BodyRelationInner() {}; + BodyRelationInner(RealBody *real_body); + virtual ~BodyRelationInner(){}; virtual void updateConfiguration() override; }; @@ -157,14 +157,39 @@ namespace SPH protected: size_t total_levels_; SPHBodyParticlesIndex get_particle_index_; - StdVec get_multi_level_search_depth_; + StdVec get_multi_level_search_depth_; NeighborRelationInnerVariableSmoothingLength get_inner_neighbor_variable_smoothing_length_; - StdVec cell_linked_list_levels_; + StdVec cell_linked_list_levels_; + + public: + BodyRelationInnerVariableSmoothingLength(RealBody *real_body); + virtual ~BodyRelationInnerVariableSmoothingLength(){}; + + virtual void updateConfiguration() override; + }; + + /** + * @class SolidBodyRelationSelfContact + * @brief The relation for self contact of a solid body + */ + class SolidBodyRelationSelfContact : public BaseBodyRelationInner + { public: - BodyRelationInnerVariableSmoothingLength(RealBody* real_body); - virtual ~BodyRelationInnerVariableSmoothingLength() {}; + ShapeSurfaceLayer body_surface_layer_; + + SolidBodyRelationSelfContact(RealBody *real_body); + virtual ~SolidBodyRelationSelfContact(){}; virtual void updateConfiguration() override; + + protected: + IndexVector &body_part_particles_; + BodyPartParticlesIndex get_body_part_particle_index_; + SearchDepthSingleResolution get_single_search_depth_; + NeighborRelationSelfContact get_self_contact_neighbor_; + CellLinkedList *cell_linked_list_; + + virtual void resetNeighborhoodCurrentSize() override; }; /** @@ -174,22 +199,23 @@ namespace SPH class BaseBodyRelationContact : public SPHBodyRelation { protected: - StdVec target_cell_linked_lists_; - StdVec get_search_depths_; - StdVec get_contact_neighbors_; + StdVec target_cell_linked_lists_; + StdVec get_search_depths_; + StdVec get_contact_neighbors_; virtual void resetNeighborhoodCurrentSize(); + public: RealBodyVector contact_bodies_; ContatcParticleConfiguration contact_configuration_; /**< Configurations for particle interaction between bodies. */ - BaseBodyRelationContact(SPHBody* body, RealBodyVector contact_bodies); - BaseBodyRelationContact(SPHBody* body, BodyPartVector contact_body_parts); - virtual ~BaseBodyRelationContact() {}; + BaseBodyRelationContact(SPHBody *body, RealBodyVector contact_bodies); + BaseBodyRelationContact(SPHBody *body, BodyPartVector contact_body_parts); + virtual ~BaseBodyRelationContact(){}; virtual void updateConfigurationMemories() override; }; - + /** * @class BodyRelationContact * @brief The relation between a SPH body and its contact SPH bodies @@ -200,10 +226,11 @@ namespace SPH SPHBodyParticlesIndex get_particle_index_; void initialization(); + public: - BodyRelationContact(SPHBody* body, RealBodyVector contact_bodies); - BodyRelationContact(SPHBody* body, BodyPartVector contact_body_parts); - virtual ~BodyRelationContact() {}; + BodyRelationContact(SPHBody *body, RealBodyVector contact_bodies); + BodyRelationContact(SPHBody *body, BodyPartVector contact_body_parts); + virtual ~BodyRelationContact(){}; virtual void updateConfiguration() override; }; @@ -213,21 +240,23 @@ namespace SPH */ class SolidBodyRelationContact : public BaseBodyRelationContact { - protected: - IndexVector& body_part_particles_; - BodyPartParticlesIndex get_body_part_particle_index_; - - void initialization(); - virtual void resetNeighborhoodCurrentSize() override; public: ShapeSurfaceLayer body_surface_layer_; - SolidBodyRelationContact(SPHBody* body, RealBodyVector contact_bodies); - virtual ~SolidBodyRelationContact() {}; + SolidBodyRelationContact(SPHBody *body, RealBodyVector contact_bodies); + SolidBodyRelationContact(SolidBodyRelationSelfContact *solid_body_relation_self_contact, + RealBodyVector contact_bodies); + virtual ~SolidBodyRelationContact(){}; virtual void updateConfiguration() override; - }; + protected: + IndexVector &body_part_particles_; + BodyPartParticlesIndex get_body_part_particle_index_; + void initialization(); + virtual void resetNeighborhoodCurrentSize() override; + }; + /** * @class GenerativeBodyRelationInner * @brief The relation within a reduced SPH body, viz. network @@ -235,16 +264,17 @@ namespace SPH class GenerativeBodyRelationInner : public BodyRelationInner { protected: - GenerativeStructure* generative_structure_; + GenerativeStructure *generative_structure_; + public: - GenerativeBodyRelationInner(RealBody* real_body) - : BodyRelationInner(real_body), + GenerativeBodyRelationInner(RealBody *real_body) + : BodyRelationInner(real_body), generative_structure_(real_body->generative_structure_){}; - virtual ~GenerativeBodyRelationInner() {}; + virtual ~GenerativeBodyRelationInner(){}; virtual void updateConfiguration() override; }; - + /** * @class BodyPartRelationContact * @brief The relation between a Body part with a SPH body. @@ -253,12 +283,12 @@ namespace SPH { public: - BodyPart* body_part_; - IndexVector& body_part_particles_; + BodyPart *body_part_; + IndexVector &body_part_particles_; BodyPartParticlesIndex get_body_part_particle_index_; - BodyPartRelationContact(BodyPart* body_part, RealBodyVector contact_bodies); - virtual ~BodyPartRelationContact() {}; + BodyPartRelationContact(BodyPart *body_part, RealBodyVector contact_bodies); + virtual ~BodyPartRelationContact(){}; virtual void updateConfiguration() override; }; @@ -272,10 +302,10 @@ namespace SPH public: BodyPartVector contact_body_parts_; - StdVec get_part_contact_neighbors_; + StdVec get_part_contact_neighbors_; - BodyRelationContactToBodyPart(RealBody* real_body, BodyPartVector contact_body_parts); - virtual ~BodyRelationContactToBodyPart() {}; + BodyRelationContactToBodyPart(RealBody *real_body, BodyPartVector contact_body_parts); + virtual ~BodyRelationContactToBodyPart(){}; virtual void updateConfiguration() override; }; @@ -289,23 +319,24 @@ namespace SPH class ComplexBodyRelation : public SPHBodyRelation { public: - BaseBodyRelationInner* inner_relation_; - BaseBodyRelationContact* contact_relation_; + BaseBodyRelationInner *inner_relation_; + BaseBodyRelationContact *contact_relation_; RealBodyVector contact_bodies_; - ParticleConfiguration& inner_configuration_; - ContatcParticleConfiguration& contact_configuration_; - - ComplexBodyRelation(BaseBodyRelationInner* inner_relation, BaseBodyRelationContact* contact_relation); - ComplexBodyRelation(RealBody* real_body, RealBodyVector contact_bodies); - ComplexBodyRelation(BaseBodyRelationInner* inner_relation, RealBodyVector contact_bodies); - ComplexBodyRelation(RealBody* real_body, BodyPartVector contact_body_parts); - virtual ~ComplexBodyRelation() { + ParticleConfiguration &inner_configuration_; + ContatcParticleConfiguration &contact_configuration_; + + ComplexBodyRelation(BaseBodyRelationInner *inner_relation, BaseBodyRelationContact *contact_relation); + ComplexBodyRelation(RealBody *real_body, RealBodyVector contact_bodies); + ComplexBodyRelation(BaseBodyRelationInner *inner_relation, RealBodyVector contact_bodies); + ComplexBodyRelation(RealBody *real_body, BodyPartVector contact_body_parts); + virtual ~ComplexBodyRelation() + { delete inner_relation_; delete contact_relation_; }; virtual void updateConfigurationMemories() override; - virtual void updateConfiguration() override; + virtual void updateConfiguration() override; }; } #endif //BODY_RELATION_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/common/sph_data_containers.cpp b/SPHINXsys/src/shared/common/sph_data_containers.cpp new file mode 100644 index 0000000000..770b9bc56e --- /dev/null +++ b/SPHINXsys/src/shared/common/sph_data_containers.cpp @@ -0,0 +1,24 @@ +/** + * @file sph_data_containers.cpp + * @brief Global functions related to sph_data_containers.h + * @author Bence Rochlitz, Luhui Han, Chi ZHang and Xiangyu Hu +*/ + +#include "sph_data_containers.h" + +namespace SPH { +//=================================================================================================// +bool checkIfPointInBoundingBox(Vec3d point, BoundingBox& bbox) +{ + return point[0] >= bbox.first[0] && point[0] <= bbox.second[0] && + point[1] >= bbox.first[1] && point[1] <= bbox.second[1] && + point[2] >= bbox.first[2] && point[2] <= bbox.second[2]; +} +//=================================================================================================// +bool checkIfPointInBoundingBox(Vec2d point, BoundingBox& bbox) +{ + return point[0] >= bbox.first[0] && point[0] <= bbox.second[0] && + point[1] >= bbox.first[1] && point[1] <= bbox.second[1]; +} +//=================================================================================================// +} \ No newline at end of file diff --git a/SPHINXsys/src/shared/common/sph_data_conainers.h b/SPHINXsys/src/shared/common/sph_data_containers.h similarity index 94% rename from SPHINXsys/src/shared/common/sph_data_conainers.h rename to SPHINXsys/src/shared/common/sph_data_containers.h index f32a75835c..4ea3e0f971 100644 --- a/SPHINXsys/src/shared/common/sph_data_conainers.h +++ b/SPHINXsys/src/shared/common/sph_data_containers.h @@ -1,5 +1,5 @@ /** - * @file sph_data_conainers.h + * @file sph_data_containers.h * @brief Set up of basic data structure. * @author Luhui Han, Chi ZHang and Xiangyu Hu */ @@ -8,6 +8,7 @@ #define SPH_DATA_CONTAINERS_H #include "base_data_package.h" +#include "base_data_type.h" namespace SPH { /** @@ -24,6 +25,10 @@ namespace SPH { /** Bounding box for system, body, body part and shape, first: lower bound, second: upper bound. */ typedef std::pair BoundingBox; + /** Check if a point is inside the bounding box */ + bool checkIfPointInBoundingBox(Vec3d point, BoundingBox& bbox); + bool checkIfPointInBoundingBox(Vec2d point, BoundingBox& bbox); + /** Generalized particle data type */ typedef std::tuple*>, StdVec*>, StdVec*>, StdVec*>> ParticleData; diff --git a/SPHINXsys/src/shared/geometries/base_geometry.h b/SPHINXsys/src/shared/geometries/base_geometry.h index a2469aae18..67e956854f 100644 --- a/SPHINXsys/src/shared/geometries/base_geometry.h +++ b/SPHINXsys/src/shared/geometries/base_geometry.h @@ -36,7 +36,7 @@ #include "base_particles.h" #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" #include diff --git a/SPHINXsys/src/shared/io_system/in_output.h b/SPHINXsys/src/shared/io_system/in_output.h index 7a2844563f..7f9fd24485 100644 --- a/SPHINXsys/src/shared/io_system/in_output.h +++ b/SPHINXsys/src/shared/io_system/in_output.h @@ -30,7 +30,7 @@ #define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" #include "all_physical_dynamics.h" #include "xml_engine.h" diff --git a/SPHINXsys/src/shared/meshes/base_mesh.h b/SPHINXsys/src/shared/meshes/base_mesh.h index e9f6cfdf56..366452e9d7 100644 --- a/SPHINXsys/src/shared/meshes/base_mesh.h +++ b/SPHINXsys/src/shared/meshes/base_mesh.h @@ -35,7 +35,7 @@ #define BASE_MESH_H #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" #include "my_memory_pool.h" #include diff --git a/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.h index 4cfdd96a22..3161d952f6 100644 --- a/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.h +++ b/SPHINXsys/src/shared/particle_dynamics/base_particle_dynamics.h @@ -33,7 +33,7 @@ #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" #include "all_particles.h" #include "all_materials.h" #include "neighbor_relation.h" diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/all_solid_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/all_solid_dynamics.h index 5249b900eb..1c525063b7 100644 --- a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/all_solid_dynamics.h +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/all_solid_dynamics.h @@ -4,6 +4,7 @@ solid dynamics used in SPHinXsys. **/ #pragma once #include "solid_dynamics.h" +#include "contact_dynamics.h" #include "thin_structure_dynamics.h" #include "fluid_structure_interaction.h" #include "thin_structure_math.h" diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/contact_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/contact_dynamics.cpp new file mode 100644 index 0000000000..008964bf34 --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/contact_dynamics.cpp @@ -0,0 +1,302 @@ +/** + * @file contact_dynamics.cpp + * @author Chi Zhang and Xiangyu Hu + */ + +#include "contact_dynamics.h" + +using namespace SimTK; + +namespace SPH +{ + namespace solid_dynamics + { + //=================================================================================================// + SelfContactDensitySummation:: + SelfContactDensitySummation(SolidBodyRelationSelfContact* self_contact_relation) + : PartInteractionDynamicsByParticle(self_contact_relation->sph_body_, + &self_contact_relation->body_surface_layer_), + SolidDataInner(self_contact_relation), + mass_(particles_->mass_), contact_density_(particles_->contact_density_) {} + //=================================================================================================// + void SelfContactDensitySummation::Interaction(size_t index_i, Real dt) + { + Real sigma = 0.0; + const Neighborhood &inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + sigma += inner_neighborhood.W_ij_[n] * mass_[inner_neighborhood.j_[n]]; + } + contact_density_[index_i] = sigma; + } + //=================================================================================================// + ContactDensitySummation:: + ContactDensitySummation(SolidBodyRelationContact *solid_body_contact_relation) + : PartInteractionDynamicsByParticle(solid_body_contact_relation->sph_body_, + &solid_body_contact_relation->body_surface_layer_), + ContactDynamicsData(solid_body_contact_relation), + mass_(particles_->mass_), contact_density_(particles_->contact_density_) + { + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + contact_mass_.push_back(&(contact_particles_[k]->mass_)); + } + } + //=================================================================================================// + void ContactDensitySummation::Interaction(size_t index_i, Real dt) + { + /** Contact interaction. */ + Real sigma = 0.0; + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + StdLargeVec &contact_mass_k = *(contact_mass_[k]); + Neighborhood &contact_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + sigma += contact_neighborhood.W_ij_[n] * contact_mass_k[contact_neighborhood.j_[n]]; + } + } + contact_density_[index_i] = sigma; + } + //=================================================================================================// + SelfContactForce:: + SelfContactForce(SolidBodyRelationSelfContact* self_contact_relation) + : PartInteractionDynamicsByParticle(self_contact_relation->sph_body_, + &self_contact_relation->body_surface_layer_), + SolidDataInner(self_contact_relation), + mass_(particles_->mass_), contact_density_(particles_->contact_density_), Vol_(particles_->Vol_), + dvel_dt_prior_(particles_->dvel_dt_prior_), contact_force_(particles_->contact_force_) {} + //=================================================================================================// + void SelfContactForce::Interaction(size_t index_i, Real dt) + { + Real Vol_i = Vol_[index_i]; + Real p_i = contact_density_[index_i] * material_->ContactStiffness(); + + /** Inner interaction. */ + Vecd force(0.0); + const Neighborhood &inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + const Vecd& e_ij = inner_neighborhood.e_ij_[n]; + Real p_star = 0.5 * (p_i + contact_density_[index_j] * material_->ContactStiffness()); + //force to mimic pressure + force -= 2.0 * p_star * e_ij * Vol_i * Vol_[index_j] * inner_neighborhood.dW_ij_[n]; + } + contact_force_[index_i] = force; + dvel_dt_prior_[index_i] += force / mass_[index_i]; + } + //=================================================================================================// + DynamicSelfContactForce:: + DynamicSelfContactForce(SolidBodyRelationSelfContact* self_contact_relation, Real penalty_strength) + : PartInteractionDynamicsByParticle(self_contact_relation->sph_body_, + &self_contact_relation->body_surface_layer_), + SolidDataInner(self_contact_relation), + Vol_(particles_->Vol_), mass_(particles_->mass_), + vel_n_(particles_->vel_n_), dvel_dt_prior_(particles_->dvel_dt_prior_), + contact_force_(particles_->contact_force_), penalty_strength_(penalty_strength), + contact_impedance_(material_->ReferenceDensity()* sqrt(material_->ContactStiffness())), + contact_reference_pressure_(material_->ReferenceDensity()* material_->ContactStiffness()) + { + particle_spacing_j1_ = 1.0 / body_->particle_adaptation_->ReferenceSpacing(); + particle_spacing_ratio2_ = + 1.0 / (body_->particle_adaptation_->ReferenceSpacing() * particle_spacing_j1_); + particle_spacing_ratio2_ *= 0.1 * particle_spacing_ratio2_; + } + //=================================================================================================// + void DynamicSelfContactForce::Interaction(size_t index_i, Real dt) + { + Real Vol_i = Vol_[index_i]; + Vecd vel_i = vel_n_[index_i]; + + /** Contact interaction. */ + Vecd force(0.0); + Neighborhood& inner_neighborhood = inner_configuration_[index_i]; + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + { + size_t index_j = inner_neighborhood.j_[n]; + Vecd e_ij = inner_neighborhood.e_ij_[n]; + + Real impedance_p = contact_impedance_ * (SimTK::dot(vel_i - vel_n_[index_j], -e_ij)); + Real overlap = inner_neighborhood.r_ij_[n]; + Real delta = 2.0 * overlap * particle_spacing_j1_; + Real beta = delta < 1.0 ? (1.0 - delta) * (1.0 - delta) * particle_spacing_ratio2_ : 0.0; + Real penalty_p = penalty_strength_* beta* overlap* contact_reference_pressure_; + + //force due to pressure + force -= 2.0 * (impedance_p + penalty_p) * e_ij * + Vol_i * Vol_[index_j] * inner_neighborhood.dW_ij_[n]; + } + contact_force_[index_i] = force; + dvel_dt_prior_[index_i] += force / mass_[index_i]; + } + //=================================================================================================// + ContactForce::ContactForce(SolidBodyRelationContact *solid_body_contact_relation) + : PartInteractionDynamicsByParticle(solid_body_contact_relation->sph_body_, + &solid_body_contact_relation->body_surface_layer_), + ContactDynamicsData(solid_body_contact_relation), + contact_density_(particles_->contact_density_), + Vol_(particles_->Vol_), mass_(particles_->mass_), + dvel_dt_prior_(particles_->dvel_dt_prior_), + contact_force_(particles_->contact_force_) + { + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); + contact_contact_density_.push_back(&(contact_particles_[k]->contact_density_)); + } + } + //=================================================================================================// + void ContactForce::Interaction(size_t index_i, Real dt) + { + Real Vol_i = Vol_[index_i]; + Real p_i = contact_density_[index_i] * material_->ContactStiffness(); + /** Contact interaction. */ + Vecd force(0.0); + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + StdLargeVec &contact_density_k = *(contact_contact_density_[k]); + StdLargeVec &Vol_k = *(contact_Vol_[k]); + Solid *solid_k = contact_material_[k]; + + Neighborhood &contact_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + Vecd e_ij = contact_neighborhood.e_ij_[n]; + + Real p_star = 0.5 * (p_i + contact_density_k[index_j] * solid_k->ContactStiffness()); + //force due to pressure + force -= 2.0 * p_star * e_ij * Vol_i * Vol_k[index_j] * contact_neighborhood.dW_ij_[n]; + } + } + contact_force_[index_i] = force; + dvel_dt_prior_[index_i] += force / mass_[index_i]; + } + //=================================================================================================// + DynamicContactForce:: + DynamicContactForce(SolidBodyRelationContact *solid_body_contact_relation, Real penalty_strength) + : PartInteractionDynamicsByParticle(solid_body_contact_relation->sph_body_, + &solid_body_contact_relation->body_surface_layer_), + ContactDynamicsData(solid_body_contact_relation), + Vol_(particles_->Vol_), mass_(particles_->mass_), + vel_n_(particles_->vel_n_), dvel_dt_prior_(particles_->dvel_dt_prior_), + contact_force_(particles_->contact_force_), penalty_strength_(penalty_strength) + { + Real impedance = material_->ReferenceDensity() * sqrt(material_->ContactStiffness()); + Real reference_pressure = material_->ReferenceDensity() * material_->ContactStiffness(); + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); + contact_vel_n_.push_back(&(contact_particles_[k]->vel_n_)); + Real contact_impedance = + contact_material_[k]->ReferenceDensity() * sqrt(contact_material_[k]->ContactStiffness()); + contact_impedance_.push_back(2.0 * impedance * contact_impedance / (impedance + contact_impedance)); + Real contact_reference_pressure = + contact_material_[k]->ReferenceDensity() * contact_material_[k]->ContactStiffness(); + contact_reference_pressure_.push_back(2.0 * reference_pressure * contact_reference_pressure / + (reference_pressure + contact_reference_pressure)); + } + } + //=================================================================================================// + void DynamicContactForce::Interaction(size_t index_i, Real dt) + { + Real Vol_i = Vol_[index_i]; + Vecd vel_i = vel_n_[index_i]; + + /** Contact interaction. */ + Vecd force(0.0); + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + Real particle_spacing_j1 = 1.0 / contact_bodies_[k]->particle_adaptation_->ReferenceSpacing(); + Real particle_spacing_ratio2 = + 1.0 / (this->body_->particle_adaptation_->ReferenceSpacing() * particle_spacing_j1); + particle_spacing_ratio2 *= 0.1 * particle_spacing_ratio2; + + StdLargeVec &Vol_k = *(contact_Vol_[k]); + StdLargeVec &vel_n_k = *(contact_vel_n_[k]); + + Neighborhood &contact_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + Vecd e_ij = contact_neighborhood.e_ij_[n]; + + Real impedance_p = 0.5 * contact_impedance_[k] * (SimTK::dot(vel_i - vel_n_k[index_j], -e_ij)); + Real overlap = contact_neighborhood.r_ij_[n]; + Real delta = 2.0 * overlap * particle_spacing_j1; + Real beta = delta < 1.0 ? (1.0 - delta) * (1.0 - delta) * particle_spacing_ratio2 : 0.0; + Real penalty_p = penalty_strength_ * beta * overlap * contact_reference_pressure_[k]; + + //force due to pressure + force -= 2.0 * (impedance_p + penalty_p) * e_ij * + Vol_i * Vol_k[index_j] * contact_neighborhood.dW_ij_[n]; + } + } + + contact_force_[index_i] = force; + dvel_dt_prior_[index_i] += force / mass_[index_i]; + } + //=================================================================================================// + ContactForceWithWall:: + ContactForceWithWall(SolidBodyRelationContact *solid_body_contact_relation, Real penalty_strength) + : PartInteractionDynamicsByParticle(solid_body_contact_relation->sph_body_, + &solid_body_contact_relation->body_surface_layer_), + ContactDynamicsData(solid_body_contact_relation), + Vol_(particles_->Vol_), mass_(particles_->mass_), + vel_n_(particles_->vel_n_), dvel_dt_prior_(particles_->dvel_dt_prior_), + contact_force_(particles_->contact_force_), penalty_strength_(penalty_strength) + { + impedance_ = material_->ReferenceDensity() * sqrt(material_->ContactStiffness()); + reference_pressure_ = material_->ReferenceDensity() * material_->ContactStiffness(); + for (size_t k = 0; k != contact_particles_.size(); ++k) + { + contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); + contact_vel_n_.push_back(&(contact_particles_[k]->vel_n_)); + contact_n_.push_back(&(contact_particles_[k]->n_)); + } + } + //=================================================================================================// + void ContactForceWithWall::Interaction(size_t index_i, Real dt) + { + Real Vol_i = Vol_[index_i]; + Vecd vel_i = vel_n_[index_i]; + + /** Contact interaction. */ + Vecd force(0.0); + for (size_t k = 0; k < contact_configuration_.size(); ++k) + { + Real particle_spacing_j1 = 1.0 / contact_bodies_[k]->particle_adaptation_->ReferenceSpacing(); + Real particle_spacing_ratio2 = + 1.0 / (body_->particle_adaptation_->ReferenceSpacing() * particle_spacing_j1); + particle_spacing_ratio2 *= 0.1 * particle_spacing_ratio2; + + StdLargeVec &Vol_k = *(contact_Vol_[k]); + StdLargeVec &n_k = *(contact_n_[k]); + StdLargeVec &vel_n_k = *(contact_vel_n_[k]); + + Neighborhood &contact_neighborhood = (*contact_configuration_[k])[index_i]; + for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) + { + size_t index_j = contact_neighborhood.j_[n]; + Vecd e_ij = contact_neighborhood.e_ij_[n]; + Vecd n_k_j = n_k[index_j]; + + Real impedance_p = 0.5 * impedance_ * (SimTK::dot(vel_i - vel_n_k[index_j], -n_k_j)); + Real overlap = contact_neighborhood.r_ij_[n] * SimTK::dot(n_k_j, e_ij); + Real delta = 2.0 * overlap * particle_spacing_j1; + Real beta = delta < 1.0 ? (1.0 - delta) * (1.0 - delta) * particle_spacing_ratio2 : 0.0; + Real penalty_p = penalty_strength_ * beta * overlap * reference_pressure_; + + //force due to pressure + force -= 2.0 * (impedance_p + penalty_p) * dot(e_ij, n_k_j) * + n_k_j * Vol_i * Vol_k[index_j] * contact_neighborhood.dW_ij_[n]; + } + } + + contact_force_[index_i] = force; + dvel_dt_prior_[index_i] += force / mass_[index_i]; + } + //=================================================================================================// + } +} diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/contact_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/contact_dynamics.h new file mode 100644 index 0000000000..7b38bb5a8e --- /dev/null +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/contact_dynamics.h @@ -0,0 +1,177 @@ +/* -------------------------------------------------------------------------* +* SPHinXsys * +* --------------------------------------------------------------------------* +* SPHinXsys (pronunciation: s'finksis) is an acronym from Smoothed Particle * +* Hydrodynamics for industrial compleX systems. It provides C++ APIs for * +* physical accurate simulation and aims to model coupled industrial dynamic * +* systems including fluid, solid, multi-body dynamics and beyond with SPH * +* (smoothed particle hydrodynamics), a meshless computational method using * +* particle discretization. * +* * +* SPHinXsys is partially funded by German Research Foundation * +* (Deutsche Forschungsgemeinschaft) DFG HU1527/6-1, HU1527/10-1 * +* and HU1527/12-1. * +* * +* Portions copyright (c) 2017-2020 Technical University of Munich and * +* the authors' affiliations. * +* * +* Licensed under the Apache License, Version 2.0 (the "License"); you may * +* not use this file except in compliance with the License. You may obtain a * +* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * +* * +* --------------------------------------------------------------------------*/ +/** +* @file contact_dynamics.h +* @brief Here, we define the algorithm classes for solid contact dynamics. +* @details We consider here a weakly compressible solids. +* @author Chi Zhang and Xiangyu Hu +*/ + +#ifndef CONTACT_DYNAMICS_H +#define CONTACT_DYNAMICS_H + + +#include "solid_dynamics.h" + +namespace SPH +{ + namespace solid_dynamics + { + /** + * @class SelfContactDensitySummation + * @brief Computing the summation density due to solid self-contact model. + */ + class SelfContactDensitySummation : + public PartInteractionDynamicsByParticle, public SolidDataInner + { + public: + explicit SelfContactDensitySummation(SolidBodyRelationSelfContact* self_contact_relation); + virtual ~SelfContactDensitySummation() {}; + protected: + StdLargeVec& mass_, & contact_density_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class ContactDensitySummation + * @brief Computing the summation density due to solid-solid contact model. + */ + class ContactDensitySummation : + public PartInteractionDynamicsByParticle, public ContactDynamicsData + { + public: + explicit ContactDensitySummation(SolidBodyRelationContact* solid_body_contact_relation); + virtual ~ContactDensitySummation() {}; + protected: + StdLargeVec& mass_, & contact_density_; + StdVec*> contact_mass_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class SelfContactForce + * @brief Computing the self-contact force. + */ + class SelfContactForce : + public PartInteractionDynamicsByParticle, public SolidDataInner + { + public: + explicit SelfContactForce(SolidBodyRelationSelfContact* self_contact_relation); + virtual ~SelfContactForce() {}; + protected: + StdLargeVec& mass_, & contact_density_, & Vol_; + StdLargeVec& dvel_dt_prior_, & contact_force_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class ContactForce + * @brief Computing the contact force. + */ + class ContactForce : + public PartInteractionDynamicsByParticle, public ContactDynamicsData + { + public: + explicit ContactForce(SolidBodyRelationContact* solid_body_contact_relation); + virtual ~ContactForce() {}; + protected: + StdLargeVec& contact_density_, & Vol_, & mass_; + StdLargeVec& dvel_dt_prior_, & contact_force_; + StdVec*> contact_contact_density_, contact_Vol_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class DynamicContactForce + * @brief Computing the contact force for problems dominated by the contact dynamic process itself. + * For example, the high speed impact problems in which the detailed contact behavior is crucial for + * physical sound solutions. Therefore, for simple low speed problem in which contact force is + * used merely prevent penetration. We can still use the simple formulation in the class ContactForce. + * The idea is to introduce conact force based on Riemann problem like formulation, + * in which the artificial dissipation is the main interaction force to prevent + * penetration. Furthermore, a penalty type force is used as supplementary to prevent penetration + * when the contact velocity is small. + */ + class DynamicContactForce : + public PartInteractionDynamicsByParticle, public ContactDynamicsData + { + public: + explicit DynamicContactForce(SolidBodyRelationContact* solid_body_contact_relation, Real penalty_strength = 1.0); + virtual ~DynamicContactForce() {}; + protected: + StdLargeVec& Vol_, & mass_; + StdLargeVec& vel_n_, & dvel_dt_prior_, & contact_force_; + StdVec*> contact_Vol_; + StdVec*>contact_vel_n_; + Real penalty_strength_; + StdVec contact_impedance_, contact_reference_pressure_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + /** + * @class ContactForceWithWall + * @brief Computing the contact force with a rigid wall. + * Note that the body surface of the wall should be + * updated before computing the contact force. + */ + class ContactForceWithWall : + public PartInteractionDynamicsByParticle, public ContactDynamicsData + { + public: + explicit ContactForceWithWall(SolidBodyRelationContact* solid_body_contact_relation, Real penalty_strength = 1.0); + virtual ~ContactForceWithWall() {}; + protected: + StdLargeVec& Vol_, & mass_; + StdLargeVec& vel_n_, & dvel_dt_prior_, & contact_force_; + StdVec*> contact_Vol_; + StdVec*>contact_vel_n_, contact_n_; + Real penalty_strength_; + Real impedance_, reference_pressure_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + + + class DynamicSelfContactForce : + public PartInteractionDynamicsByParticle, public SolidDataInner + { + public: + explicit DynamicSelfContactForce(SolidBodyRelationSelfContact* self_contact_relation, Real penalty_strength = 1.0); + virtual ~DynamicSelfContactForce() {}; + protected: + StdLargeVec& Vol_, & mass_; + StdLargeVec& vel_n_, & dvel_dt_prior_, & contact_force_; + Real penalty_strength_; + Real particle_spacing_j1_, particle_spacing_ratio2_; + Real contact_impedance_, contact_reference_pressure_; + + virtual void Interaction(size_t index_i, Real dt = 0.0) override; + }; + } +} +#endif //CONTACT_DYNAMICS_H \ No newline at end of file diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.cpp index 575dad9691..bcf0bffe30 100644 --- a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.cpp +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/inelastic_dynamics.cpp @@ -17,7 +17,7 @@ namespace SPH StressRelaxationFirstHalf(body_inner_relation), plastic_solid_(dynamic_cast(material_)) { - numerical_dissipation_factor_ = 0.25; + numerical_dissipation_factor_ = 0.5; } //=================================================================================================// void PlasticStressRelaxationFirstHalf::Initialization(size_t index_i, Real dt) @@ -26,7 +26,9 @@ namespace SPH F_[index_i] += dF_dt_[index_i] * dt * 0.5; rho_n_[index_i] = rho0_ / SimTK::det(F_[index_i]); - stress_PK1_[index_i] = plastic_solid_->PlasticConstitutiveRelation(F_[index_i], index_i, dt) * B_[index_i]; + stress_PK1_[index_i] = plastic_solid_->PlasticConstitutiveRelation(F_[index_i], index_i, dt) + + F_[index_i] * numerical_dissipation_factor_ + * material_->NumericalDampingRightCauchy(F_[index_i], dF_dt_[index_i], smoothing_length_, index_i); } //=================================================================================================// } diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.cpp b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.cpp index 5a49032de5..c1a89225a0 100644 --- a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.cpp +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.cpp @@ -12,202 +12,6 @@ namespace SPH { namespace solid_dynamics { - //=================================================================================================// - ContactDensitySummation:: - ContactDensitySummation(SolidBodyRelationContact *solid_body_contact_relation) - : PartInteractionDynamicsByParticle(solid_body_contact_relation->sph_body_, - &solid_body_contact_relation->body_surface_layer_), - ContactDynamicsData(solid_body_contact_relation), - mass_(particles_->mass_), contact_density_(particles_->contact_density_) - { - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - contact_mass_.push_back(&(contact_particles_[k]->mass_)); - } - } - //=================================================================================================// - void ContactDensitySummation::Interaction(size_t index_i, Real dt) - { - /** Contact interaction. */ - Real sigma = 0.0; - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - StdLargeVec &contact_mass_k = *(contact_mass_[k]); - Neighborhood &contact_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - sigma += contact_neighborhood.W_ij_[n] * contact_mass_k[contact_neighborhood.j_[n]]; - } - } - contact_density_[index_i] = sigma; - } - //=================================================================================================// - ContactForce::ContactForce(SolidBodyRelationContact *solid_body_contact_relation) - : PartInteractionDynamicsByParticle(solid_body_contact_relation->sph_body_, - &solid_body_contact_relation->body_surface_layer_), - ContactDynamicsData(solid_body_contact_relation), - contact_density_(particles_->contact_density_), - Vol_(particles_->Vol_), mass_(particles_->mass_), - dvel_dt_prior_(particles_->dvel_dt_prior_), - contact_force_(particles_->contact_force_) - { - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); - contact_contact_density_.push_back(&(contact_particles_[k]->contact_density_)); - } - } - //=================================================================================================// - void ContactForce::Interaction(size_t index_i, Real dt) - { - Real Vol_i = Vol_[index_i]; - Real p_i = contact_density_[index_i] * material_->ContactStiffness(); - /** Contact interaction. */ - Vecd force(0.0); - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - StdLargeVec &contact_density_k = *(contact_contact_density_[k]); - StdLargeVec &Vol_k = *(contact_Vol_[k]); - Solid *solid_k = contact_material_[k]; - - Neighborhood &contact_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - Vecd e_ij = contact_neighborhood.e_ij_[n]; - - Real p_star = 0.5 * (p_i + contact_density_k[index_j] * solid_k->ContactStiffness()); - //force due to pressure - force -= 2.0 * p_star * e_ij * Vol_i * Vol_k[index_j] * contact_neighborhood.dW_ij_[n]; - } - } - contact_force_[index_i] = force; - dvel_dt_prior_[index_i] += force / mass_[index_i]; - } - //=================================================================================================// - DynamicContactForce:: - DynamicContactForce(SolidBodyRelationContact *solid_body_contact_relation, Real penalty_strength) - : PartInteractionDynamicsByParticle(solid_body_contact_relation->sph_body_, - &solid_body_contact_relation->body_surface_layer_), - ContactDynamicsData(solid_body_contact_relation), - Vol_(particles_->Vol_), mass_(particles_->mass_), - vel_n_(particles_->vel_n_), dvel_dt_prior_(particles_->dvel_dt_prior_), - contact_force_(particles_->contact_force_), penalty_strength_(penalty_strength) - { - Real impedence = material_->ReferenceDensity() * sqrt(material_->ContactStiffness()); - Real reference_pressure = material_->ReferenceDensity() * material_->ContactStiffness(); - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); - contact_vel_n_.push_back(&(contact_particles_[k]->vel_n_)); - Real contact_impedence = - contact_material_[k]->ReferenceDensity() * sqrt(contact_material_[k]->ContactStiffness()); - contact_impedence_.push_back(2.0 * impedence * contact_impedence / (impedence + contact_impedence)); - Real contact_reference_pressure = - contact_material_[k]->ReferenceDensity() * contact_material_[k]->ContactStiffness(); - contact_reference_pressure_.push_back(2.0 * reference_pressure * contact_reference_pressure / - (reference_pressure + contact_reference_pressure)); - } - } - //=================================================================================================// - void DynamicContactForce::Interaction(size_t index_i, Real dt) - { - Real Vol_i = Vol_[index_i]; - Vecd vel_i = vel_n_[index_i]; - - /** Contact interaction. */ - Vecd force(0.0); - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - Real particle_spacing_j1 = 1.0 / contact_bodies_[k]->particle_adaptation_->ReferenceSpacing(); - Real particle_spacing_ratio2 = - 1.0 / (this->body_->particle_adaptation_->ReferenceSpacing() * particle_spacing_j1); - particle_spacing_ratio2 *= 0.1 * particle_spacing_ratio2; - - StdLargeVec &Vol_k = *(contact_Vol_[k]); - StdLargeVec &vel_n_k = *(contact_vel_n_[k]); - - Neighborhood &contact_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - Vecd e_ij = contact_neighborhood.e_ij_[n]; - - Real impedence_p = 0.5 * contact_impedence_[k] * (SimTK::dot(vel_i - vel_n_k[index_j], -e_ij)); - Real overlap = contact_neighborhood.r_ij_[n]; - Real delta = 2.0 * overlap * particle_spacing_j1; - Real beta = delta < 1.0 ? (1.0 - delta) * (1.0 - delta) * particle_spacing_ratio2 : 0.0; - Real penalty_p = penalty_strength_ * beta * overlap * contact_reference_pressure_[k]; - - //force due to pressure - force -= 2.0 * (impedence_p + penalty_p) * e_ij * - Vol_i * Vol_k[index_j] * contact_neighborhood.dW_ij_[n]; - } - } - - contact_force_[index_i] = force; - dvel_dt_prior_[index_i] += force / mass_[index_i]; - } - //=================================================================================================// - ContactForceWithWall:: - ContactForceWithWall(SolidBodyRelationContact *solid_body_contact_relation, Real penalty_strength) - : PartInteractionDynamicsByParticle(solid_body_contact_relation->sph_body_, - &solid_body_contact_relation->body_surface_layer_), - ContactDynamicsData(solid_body_contact_relation), - Vol_(particles_->Vol_), mass_(particles_->mass_), - vel_n_(particles_->vel_n_), dvel_dt_prior_(particles_->dvel_dt_prior_), - contact_force_(particles_->contact_force_), penalty_strength_(penalty_strength) - { - impedence_ = material_->ReferenceDensity() * sqrt(material_->ContactStiffness()); - reference_pressure_ = material_->ReferenceDensity() * material_->ContactStiffness(); - for (size_t k = 0; k != contact_particles_.size(); ++k) - { - contact_Vol_.push_back(&(contact_particles_[k]->Vol_)); - contact_vel_n_.push_back(&(contact_particles_[k]->vel_n_)); - contact_n_.push_back(&(contact_particles_[k]->n_)); - } - } - //=================================================================================================// - void ContactForceWithWall::Interaction(size_t index_i, Real dt) - { - Real Vol_i = Vol_[index_i]; - Vecd vel_i = vel_n_[index_i]; - - /** Contact interaction. */ - Vecd force(0.0); - for (size_t k = 0; k < contact_configuration_.size(); ++k) - { - Real particle_spacing_j1 = 1.0 / contact_bodies_[k]->particle_adaptation_->ReferenceSpacing(); - Real particle_spacing_ratio2 = - 1.0 / (body_->particle_adaptation_->ReferenceSpacing() * particle_spacing_j1); - particle_spacing_ratio2 *= 0.1 * particle_spacing_ratio2; - - StdLargeVec &Vol_k = *(contact_Vol_[k]); - StdLargeVec &n_k = *(contact_n_[k]); - StdLargeVec &vel_n_k = *(contact_vel_n_[k]); - - Neighborhood &contact_neighborhood = (*contact_configuration_[k])[index_i]; - for (size_t n = 0; n != contact_neighborhood.current_size_; ++n) - { - size_t index_j = contact_neighborhood.j_[n]; - Vecd e_ij = contact_neighborhood.e_ij_[n]; - Vecd n_k_j = n_k[index_j]; - - Real impedence_p = 0.5 * impedence_ * (SimTK::dot(vel_i - vel_n_k[index_j], -n_k_j)); - Real overlap = contact_neighborhood.r_ij_[n] * SimTK::dot(n_k_j, e_ij); - Real delta = 2.0 * overlap * particle_spacing_j1; - Real beta = delta < 1.0 ? (1.0 - delta) * (1.0 - delta) * particle_spacing_ratio2 : 0.0; - Real penalty_p = penalty_strength_ * beta * overlap * reference_pressure_; - - //force due to pressure - force -= 2.0 * (impedence_p + penalty_p) * dot(e_ij, n_k_j) * - n_k_j * Vol_i * Vol_k[index_j] * contact_neighborhood.dW_ij_[n]; - } - } - - contact_force_[index_i] = force; - dvel_dt_prior_[index_i] += force / mass_[index_i]; - } //=================================================================================================// AcousticTimeStepSize::AcousticTimeStepSize(SolidBody *body, Real CFL) : ParticleDynamicsReduce(body), @@ -343,9 +147,8 @@ namespace SPH //=================================================================================================// Vecd PositionScaleSolidBody::getDisplacement(size_t index_i, Real dt) { - Vecd displacement; - try - { + Vecd displacement(0); + try { // displacement from the initial position Vecd pos_final = pos_0_center_ + end_scale_ * (pos_0_[index_i] - pos_0_center_); displacement = (pos_final - pos_n_[index_i]) * dt / @@ -380,25 +183,46 @@ namespace SPH } } //=================================================================================================// + bool checkIfPointInBoundingBox(Vecd point, BoundingBox& bbox) + { + if (point.size() >= 3 && bbox.first.size() >= 3 && bbox.second.size() >= 3) + { + return point[0] >= bbox.first[0] && point[0] <= bbox.second[0] && + point[1] >= bbox.first[1] && point[1] <= bbox.second[1] && + point[2] >= bbox.first[2] && point[2] <= bbox.second[2]; + } + if (point.size() >= 2 && bbox.first.size() >= 2 && bbox.second.size() >= 2) + { + return point[0] >= bbox.first[0] && point[0] <= bbox.second[0] && + point[1] >= bbox.first[1] && point[1] <= bbox.second[1]; + } + if (point.size() >= 1 && bbox.first.size() >= 1 && bbox.second.size() >= 1) + { + return point[0] >= bbox.first[0] && point[0] <= bbox.second[0]; + } + throw runtime_error(string("checkIfPointInBoundingBox: Vecd point or BoundingBox& bbox has a dimension of <1")); + return false; + } + //=================================================================================================// TranslateSolidBody:: TranslateSolidBody(SPHBody* body, BodyPartByParticle* body_part, Real start_time, Real end_time, Vecd translation): PartSimpleDynamicsByParticle(body, body_part), SolidDataSimple(body), - pos_n_(particles_->pos_n_), pos_0_(particles_->pos_0_), + pos_n_(particles_->pos_n_), pos_0_(particles_->pos_0_), pos_end_({}), vel_n_(particles_->vel_n_), dvel_dt_(particles_->dvel_dt_), - vel_ave_(particles_->vel_ave_), dvel_dt_ave_(particles_->dvel_dt_ave_), start_time_(start_time), end_time_(end_time), translation_(translation) - {} + { + // record the particle positions that should be reached at end time + for (size_t index_i = 0; index_i < pos_n_.size(); index_i++) + { + pos_end_.push_back(pos_n_[index_i] + translation_); + } + } //=================================================================================================// Vecd TranslateSolidBody::getDisplacement(size_t index_i, Real dt) { Vecd displacement(0); - // if we are out of the time interval, return 0 - if (GlobalStaticVariables::physical_time_ < start_time_ || GlobalStaticVariables::physical_time_ > end_time_) return displacement; try { - // distance left to reach the final position - Vecd translation_left = translation_ * (end_time_ - GlobalStaticVariables::physical_time_) / (end_time_ - start_time_); - // displacement is a portion of distance left, scaled by dt and remaining time - displacement = 0.5 * translation_left * dt / (end_time_ - GlobalStaticVariables::physical_time_); + displacement = (pos_end_[index_i] - pos_n_[index_i]) * dt / (end_time_ - GlobalStaticVariables::physical_time_); } catch(out_of_range& e){ throw runtime_error(string("TranslateSolidBody::getDisplacement: particle index out of bounds") + to_string(index_i)); @@ -408,23 +232,44 @@ namespace SPH //=================================================================================================// void TranslateSolidBody::Update(size_t index_i, Real dt) { - try + // only apply in the defined time period + if (GlobalStaticVariables::physical_time_ >= start_time_ && GlobalStaticVariables::physical_time_ <= end_time_) { - // only apply in the defined time period - if (GlobalStaticVariables::physical_time_ >= start_time_ && - GlobalStaticVariables::physical_time_ <= end_time_) - { - pos_n_[index_i] = pos_n_[index_i] + getDisplacement(index_i, dt); // displacement from the initial position + try { + pos_n_[index_i] = pos_n_[index_i] + 0.5 * getDisplacement(index_i, dt); // displacement from the initial position, 0.5x because it's executed twice vel_n_[index_i] = getVelocity(); - dvel_dt_[index_i] = getAcceleration(); - /** the average values are prescirbed also. */ - vel_ave_[index_i] = vel_n_[index_i]; - dvel_dt_ave_[index_i] = dvel_dt_[index_i]; + } + catch(out_of_range& e){ + throw runtime_error(string("TranslateSolidBody::Update: particle index out of bounds") + to_string(index_i)); } } - catch (out_of_range &e) - { - throw runtime_error(string("TranslateSolidBody::Update: particle index out of bounds") + to_string(index_i)); + } + //=================================================================================================// + TranslateSolidBodyPart:: + TranslateSolidBodyPart(SPHBody* body, BodyPartByParticle* body_part, Real start_time, Real end_time, Vecd translation, BoundingBox bbox): + TranslateSolidBody(body, body_part, start_time, end_time, translation), bbox_(bbox) + {} + //=================================================================================================// + void TranslateSolidBodyPart::Update(size_t index_i, Real dt) + { + + try { + Vecd point = pos_0_[index_i]; + if (checkIfPointInBoundingBox(point, bbox_)) + { + if (GlobalStaticVariables::physical_time_ >= start_time_ && GlobalStaticVariables::physical_time_ <= end_time_) + { + vel_n_[index_i] = getDisplacement(index_i, dt) / dt; + } + else + { + vel_n_[index_i] = 0; + dvel_dt_[index_i] = 0; + } + } + } + catch(out_of_range& e){ + throw runtime_error(string("TranslateSolidBodyPart::Update: particle index out of bounds") + to_string(index_i)); } } //=================================================================================================// @@ -578,12 +423,12 @@ namespace SPH } //=================================================================================================// AccelerationForBodyPartInBoundingBox:: - AccelerationForBodyPartInBoundingBox(SolidBody *body, BoundingBox *bounding_box, Vecd acceleration) - : ParticleDynamicsSimple(body), SolidDataSimple(body), - pos_n_(particles_->pos_n_), - dvel_dt_prior_(particles_->dvel_dt_prior_), - bounding_box_(bounding_box), - acceleration_(acceleration) {} + AccelerationForBodyPartInBoundingBox(SolidBody* body, BoundingBox& bounding_box, Vecd acceleration) : + ParticleDynamicsSimple(body), SolidDataSimple(body), + pos_n_(particles_->pos_n_), + dvel_dt_prior_(particles_->dvel_dt_prior_), + bounding_box_(bounding_box), + acceleration_(acceleration){} //=================================================================================================// void AccelerationForBodyPartInBoundingBox::setupDynamics(Real dt) { @@ -595,11 +440,7 @@ namespace SPH if (pos_n_.size() > index_i) { Vecd point = pos_n_[index_i]; - if (point.size() >= 3 && bounding_box_ != nullptr && bounding_box_->first.size() >= 3 && - bounding_box_->second.size() >= 3 && point[0] >= bounding_box_->first[0] && - point[0] <= bounding_box_->second[0] && - point[1] >= bounding_box_->first[1] && point[1] <= bounding_box_->second[1] && - point[2] >= bounding_box_->first[2] && point[2] <= bounding_box_->second[2]) + if (checkIfPointInBoundingBox(point, bounding_box_)) { dvel_dt_prior_[index_i] += acceleration_; } @@ -674,10 +515,8 @@ namespace SPH F_[index_i] += dF_dt_[index_i] * dt * 0.5; rho_n_[index_i] = rho0_ / det(F_[index_i]); //obtain the first Piola-Kirchhoff stress from the second Piola-Kirchhoff stress - //it seems using reproducing correction here increases convergence rate - //near the free surface - stress_PK1_[index_i] = F_[index_i] * (material_->ConstitutiveRelation(F_[index_i], index_i) + - material_->NumericalDampingRightCauchy(F_[index_i], dF_dt_[index_i], smoothing_length_, index_i)) * B_[index_i]; + //it seems using reproducing correction here increases convergence rate near the free surface + stress_PK1_[index_i] = F_[index_i] * material_->ConstitutiveRelation(F_[index_i], index_i) * B_[index_i]; } //=================================================================================================// void StressRelaxationFirstHalf::Interaction(size_t index_i, Real dt) @@ -685,12 +524,19 @@ namespace SPH //including gravity and force from fluid Vecd acceleration = dvel_dt_prior_[index_i] + force_from_fluid_[index_i] / mass_[index_i]; Neighborhood &inner_neighborhood = inner_configuration_[index_i]; - for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) + for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) { size_t index_j = inner_neighborhood.j_[n]; Vecd e_ij = inner_neighborhood.e_ij_[n]; - acceleration += (stress_PK1_[index_i] + stress_PK1_[index_j]) * inner_neighborhood.dW_ij_[n] * - e_ij * Vol_[index_j] * inv_rho0_; + Real r_ij = inner_neighborhood.r_ij_[n]; + Real dim_r_ij_1 = Dimensions / r_ij; + Vecd pos_jump = pos_n_[index_i] - pos_n_[index_j]; + Vecd vel_jump = vel_n_[index_i] - vel_n_[index_j]; + Real strain_rate = SimTK::dot(pos_jump, vel_jump) * dim_r_ij_1 * dim_r_ij_1; + Real weight = inner_neighborhood.W_ij_[n] * inv_W0_; + Matd numerical_stress_ij = 0.5 * (F_[index_i] + F_[index_j]) * material_->NumericalDamping(strain_rate, smoothing_length_); + acceleration += (stress_PK1_[index_i] + stress_PK1_[index_j] + numerical_dissipation_factor_ * weight * numerical_stress_ij) + * inner_neighborhood.dW_ij_[n] * e_ij * Vol_[index_j] * inv_rho0_; } dvel_dt_[index_i] = acceleration; @@ -701,10 +547,35 @@ namespace SPH vel_n_[index_i] += dvel_dt_[index_i] * dt; } //=================================================================================================// + KirchhoffParticleStressRelaxationFirstHalf:: + KirchhoffParticleStressRelaxationFirstHalf(BaseBodyRelationInner *body_inner_relation) + : StressRelaxationFirstHalf(body_inner_relation) {}; + //=================================================================================================// + void KirchhoffParticleStressRelaxationFirstHalf::Initialization(size_t index_i, Real dt) + { + pos_n_[index_i] += vel_n_[index_i] * dt * 0.5; + F_[index_i] += dF_dt_[index_i] * dt * 0.5; + rho_n_[index_i] = rho0_ / det(F_[index_i]); + Real J = det(F_[index_i]); + Real one_over_J = 1.0 / J; + rho_n_[index_i] = rho0_ * one_over_J; + Real J_to_minus_2_over_dimension = pow(one_over_J, 2.0 * one_over_dimensions_); + Matd normalized_b = (F_[index_i] * ~F_[index_i]) * J_to_minus_2_over_dimension; + Matd deviatoric_b = normalized_b - Matd(1.0) * normalized_b.trace() * one_over_dimensions_; + Matd inverse_F_T = ~SimTK::inverse(F_[index_i]); + //obtain the first Piola-Kirchhoff stress from the Kirchhoff stress + //it seems using reproducing correction here increases convergence rate + //near the free surface however, this correction is not used for the numerical disspation + stress_PK1_[index_i] = (Matd(1.0) * material_->VolumetricKirchhoff(J) + + material_->DeviatoricKirchhoff(deviatoric_b)) * inverse_F_T * B_[index_i] + + material_->NumericalDampingLeftCauchy(F_[index_i], dF_dt_[index_i], smoothing_length_, index_i) * + inverse_F_T; + } + //=================================================================================================// KirchhoffStressRelaxationFirstHalf:: KirchhoffStressRelaxationFirstHalf(BaseBodyRelationInner *body_inner_relation) : StressRelaxationFirstHalf(body_inner_relation), - J_to_minus_2_over_dimension_(*particles_->createAVariable("DeterminantTerm")), + J_to_minus_2_over_dimension_(*particles_->createAVariable("DeterminantTerm")), stress_on_particle_(*particles_->createAVariable("StressOnParticle")), inverse_F_T_(*particles_->createAVariable("InverseTransposedDeformation")){}; //=================================================================================================// @@ -715,12 +586,12 @@ namespace SPH Real J = det(F_[index_i]); Real one_over_J = 1.0 / J; rho_n_[index_i] = rho0_ * one_over_J; - J_to_minus_2_over_dimension_[index_i] = B_[index_i] * pow(one_over_J * one_over_J, one_over_dimensions_); + J_to_minus_2_over_dimension_[index_i] = pow(one_over_J, 2.0 * one_over_dimensions_); inverse_F_T_[index_i] = ~SimTK::inverse(F_[index_i]); - stress_on_particle_[index_i] = (Matd(1.0) * material_->VolumetricKirchhoff(J) + - material_->NumericalDampingLeftCauchy(F_[index_i], dF_dt_[index_i], smoothing_length_, index_i)) * B_[index_i] - - Matd(1.0) * material_->ShearModulus() * J_to_minus_2_over_dimension_[index_i] * - (F_[index_i] * ~F_[index_i]).trace() * one_over_dimensions_; + stress_on_particle_[index_i] = inverse_F_T_[index_i] * (material_->VolumetricKirchhoff(J) * B_[index_i] - + Matd(correction_factor_) * material_->ShearModulus() * J_to_minus_2_over_dimension_[index_i] * + (F_[index_i] * ~F_[index_i]).trace() * one_over_dimensions_) + + material_->NumericalDampingLeftCauchy(F_[index_i], dF_dt_[index_i], smoothing_length_, index_i) * inverse_F_T_[index_i]; stress_PK1_[index_i] = F_[index_i] * material_->ConstitutiveRelation(F_[index_i], index_i); } //=================================================================================================// @@ -732,13 +603,11 @@ namespace SPH for (size_t n = 0; n != inner_neighborhood.current_size_; ++n) { size_t index_j = inner_neighborhood.j_[n]; - Vecd extension = (pos_n_[index_i] - pos_n_[index_j]) / inner_neighborhood.r_ij_[n]; - Matd stress_ij = material_->ShearModulus() * - (J_to_minus_2_over_dimension_[index_i] + J_to_minus_2_over_dimension_[index_j]) * - SimTK::outer(extension, extension); - acceleration += ((stress_on_particle_[index_i] + stress_on_particle_[index_j] + stress_ij) * - (inverse_F_T_[index_i] + inverse_F_T_[index_j]) * 0.5) * - inner_neighborhood.dW_ij_[n] * inner_neighborhood.e_ij_[n] * Vol_[index_j] * inv_rho0_; + Vecd shear_force_ij = correction_factor_ * material_->ShearModulus() * + (J_to_minus_2_over_dimension_[index_i] + J_to_minus_2_over_dimension_[index_j]) * + (pos_n_[index_i] - pos_n_[index_j]) / inner_neighborhood.r_ij_[n]; + acceleration += ((stress_on_particle_[index_i] + stress_on_particle_[index_j]) * inner_neighborhood.e_ij_[n] + shear_force_ij) * + inner_neighborhood.dW_ij_[n] * Vol_[index_j] * inv_rho0_; } dvel_dt_[index_i] = acceleration; } diff --git a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.h b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.h index dbbd58597b..69a8f507ef 100644 --- a/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.h +++ b/SPHINXsys/src/shared/particle_dynamics/solid_dynamics/solid_dynamics.h @@ -33,9 +33,7 @@ #include "all_particle_dynamics.h" #include "elastic_solid.h" -#include "weakly_compressible_fluid.h" #include "base_kernel.h" -#include "all_fluid_dynamics.h" namespace SPH { @@ -67,92 +65,6 @@ namespace SPH virtual ~SolidDynamicsInitialCondition() {}; }; - /** - * @class ContactDensitySummation - * @brief Computing the summation density due to solid-solid contact model. - */ - class ContactDensitySummation : - public PartInteractionDynamicsByParticle, public ContactDynamicsData - { - public: - ContactDensitySummation(SolidBodyRelationContact* solid_body_contact_relation); - virtual ~ContactDensitySummation() {}; - protected: - StdLargeVec& mass_, & contact_density_; - StdVec*> contact_mass_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class ContactForce - * @brief Computing the contact force. - */ - class ContactForce : - public PartInteractionDynamicsByParticle, public ContactDynamicsData - { - public: - ContactForce(SolidBodyRelationContact* solid_body_contact_relation); - virtual ~ContactForce() {}; - protected: - StdLargeVec& contact_density_, & Vol_, & mass_; - StdLargeVec& dvel_dt_prior_, & contact_force_; - StdVec*> contact_contact_density_, contact_Vol_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class DynamicContactForce - * @brief Computing the contact force for problems dominainted by the contact dynamic process itself. - * For example, the high speed impact problems in which the detailed contact behavior is crutial for - * physical sound solutions. Therefore, for simple low speed problem in which contact force is - * used merely prevent penetration. We can still use the simple formualation in the class ContactForce. - * The idea is to introduce conact force based on Riemann problem like formulation, - * in which the artficial dissipation is the main interaction force to prevent - * penetration. Furthermore, a panelty type force is used as supplementrary to prevent penetration - * when the contact velocity is small. - */ - class DynamicContactForce : - public PartInteractionDynamicsByParticle, public ContactDynamicsData - { - public: - DynamicContactForce(SolidBodyRelationContact* solid_body_contact_relation, Real penalty_strength = 1.0); - virtual ~DynamicContactForce() {}; - protected: - StdLargeVec& Vol_, & mass_; - StdLargeVec& vel_n_, & dvel_dt_prior_, & contact_force_; - StdVec*> contact_Vol_; - StdVec*>contact_vel_n_; - Real penalty_strength_; - StdVec contact_impedence_, contact_reference_pressure_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - - /** - * @class ContactForceWithWall - * @brief Computing the contact force with a rigid wall. - * Note that the body surface of the wall should be - * updated beforce computing the contact force. - */ - class ContactForceWithWall : - public PartInteractionDynamicsByParticle, public ContactDynamicsData - { - public: - ContactForceWithWall(SolidBodyRelationContact* solid_body_contact_relation, Real penalty_strength = 1.0); - virtual ~ContactForceWithWall() {}; - protected: - StdLargeVec& Vol_, & mass_; - StdLargeVec& vel_n_, & dvel_dt_prior_, & contact_force_; - StdVec*> contact_Vol_; - StdVec*>contact_vel_n_, contact_n_; - Real penalty_strength_; - Real impedence_, reference_pressure_; - - virtual void Interaction(size_t index_i, Real dt = 0.0) override; - }; - /** * @class CorrectConfiguration * @brief obtain the corrected initial configuration in strong form @@ -241,7 +153,7 @@ namespace SPH virtual void Update(size_t index_i, Real dt = 0.0) override; }; - /** + /** * @class TranslateSolidBody * @brief Translates the body in a given time interval -translation driven boundary condition; only moving the body; end position irrelevant; * Note the average values for FSI are prescirbed also. @@ -252,11 +164,10 @@ namespace SPH public: TranslateSolidBody(SPHBody* body, BodyPartByParticle* body_part, Real start_time, Real end_time, Vecd translation); virtual ~TranslateSolidBody() {}; - StdLargeVec& GetParticlePos0(){ return pos_0_; }; - StdLargeVec& GetParticlePosN(){ return pos_n_; }; protected: StdLargeVec& pos_n_, &pos_0_; - StdLargeVec& vel_n_, &dvel_dt_, &vel_ave_, &dvel_dt_ave_; + StdLargeVec pos_end_; + StdLargeVec& vel_n_, &dvel_dt_; Real start_time_, end_time_; Vecd translation_; Vecd getDisplacement(size_t index_i, Real dt); @@ -266,6 +177,22 @@ namespace SPH virtual void Update(size_t index_i, Real dt = 0.0) override; }; + /** + * @class TranslateSolidBodyPart + * @brief Translates the body in a given time interval -translation driven boundary condition; only moving the body; end position irrelevant; + * Only the particles in a given Bounding Box are translated. The Bounding Box is defined for the undeformed shape. + * Note the average values for FSI are prescirbed also. + */ + class TranslateSolidBodyPart: public TranslateSolidBody + { + public: + TranslateSolidBodyPart(SPHBody* body, BodyPartByParticle* body_part, Real start_time, Real end_time, Vecd translation, BoundingBox bbox); + virtual ~TranslateSolidBodyPart() {}; + protected: + BoundingBox bbox_; + virtual void Update(size_t index_i, Real dt = 0.0) override; + }; + /** * @class ConstrainSolidBodyRegionVelocity * @brief Constrain the velocity of a solid body part. @@ -397,11 +324,11 @@ namespace SPH : public ParticleDynamicsSimple, public SolidDataSimple { public: - AccelerationForBodyPartInBoundingBox(SolidBody* body, BoundingBox* bounding_box, Vecd acceleration); + AccelerationForBodyPartInBoundingBox(SolidBody* body, BoundingBox& bounding_box, Vecd acceleration); virtual ~AccelerationForBodyPartInBoundingBox() {}; protected: StdLargeVec& pos_n_,& dvel_dt_prior_; - BoundingBox* bounding_box_; + BoundingBox bounding_box_; Vecd acceleration_; virtual void setupDynamics(Real dt = 0.0) override; virtual void Update(size_t index_i, Real dt = 0.0) override; @@ -519,12 +446,31 @@ namespace SPH virtual void Update(size_t index_i, Real dt = 0.0) override; }; + /** + * @class KirchhoffParticleStressRelaxationFirstHalf + */ + class KirchhoffParticleStressRelaxationFirstHalf : public StressRelaxationFirstHalf + { + public: + KirchhoffParticleStressRelaxationFirstHalf(BaseBodyRelationInner* body_inner_relation); + virtual ~KirchhoffParticleStressRelaxationFirstHalf() {}; + protected: + const Real one_over_dimensions_ = 1.0 / (Real)Dimensions; + + virtual void Initialization(size_t index_i, Real dt = 0.0) override; + }; + /** * @class KirchhoffStressRelaxationFirstHalf - * @brief Decompose the stress into particle stress includes isetropic stress + * @brief Decompose the stress into particle stress includes isotropic stress * and the stress due to non-homogeneous material properties. * The preliminary shear stress is introduced by particle pair to avoid - * sprurious stress and deforamtion. + * spurious stress and deformation. + * Note that, for the shear stress term, + * due to the mismatch of the divergence contribution between + * the pair-wise second-order derivative Laplacian formulation + * and particle-wise first-order gradient formulation, + * a correction factor slight large than one is introduced. */ class KirchhoffStressRelaxationFirstHalf : public StressRelaxationFirstHalf { @@ -532,9 +478,10 @@ namespace SPH KirchhoffStressRelaxationFirstHalf(BaseBodyRelationInner* body_inner_relation); virtual ~KirchhoffStressRelaxationFirstHalf() {}; protected: - StdLargeVec& J_to_minus_2_over_dimension_; + StdLargeVec& J_to_minus_2_over_dimension_; StdLargeVec& stress_on_particle_, & inverse_F_T_; const Real one_over_dimensions_ = 1.0 / (Real)Dimensions; + const Real correction_factor_ = 1.05; virtual void Initialization(size_t index_i, Real dt = 0.0) override; virtual void Interaction(size_t index_i, Real dt = 0.0) override; diff --git a/SPHINXsys/src/shared/particle_generator/base_particle_generator.h b/SPHINXsys/src/shared/particle_generator/base_particle_generator.h index d60cd2a27a..36ee2bc547 100644 --- a/SPHINXsys/src/shared/particle_generator/base_particle_generator.h +++ b/SPHINXsys/src/shared/particle_generator/base_particle_generator.h @@ -34,7 +34,7 @@ #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" namespace SPH { diff --git a/SPHINXsys/src/shared/particles/base_particles.cpp b/SPHINXsys/src/shared/particles/base_particles.cpp index a007b58ee3..5ac814f803 100644 --- a/SPHINXsys/src/shared/particles/base_particles.cpp +++ b/SPHINXsys/src/shared/particles/base_particles.cpp @@ -233,6 +233,14 @@ namespace SPH void BaseParticles::writePltFileHeader(std::ofstream& output_file) { output_file << " VARIABLES = \"x\",\"y\",\"z\",\"ID\""; + + for (size_t l = 0; l != variables_to_write_[indexInteger].size(); ++l) + { + std::string variable_name = variables_to_write_[indexInteger][l].first; + output_file << ",\"" << variable_name << "\""; + }; + + for (size_t l = 0; l != variables_to_write_[indexVector].size(); ++l) { std::string variable_name = variables_to_write_[indexVector][l].first; output_file << ",\"" << variable_name << "_x\"" << ",\"" << variable_name << "_y\"" << ",\"" << variable_name << "_z\""; @@ -249,6 +257,13 @@ namespace SPH Vec3d particle_position = upgradeToVector3D(pos_n_[index_i]); output_file << particle_position[0] << " " << particle_position[1] << " " << particle_position[2] << " " << index_i << " "; + + for (std::pair& name_index : variables_to_write_[indexInteger]) + { + std::string variable_name = name_index.first; + StdLargeVec& variable = *(std::get(all_particle_data_)[name_index.second]); + output_file << variable[index_i] << " "; + }; for (std::pair& name_index : variables_to_write_[indexVector]) { diff --git a/SPHINXsys/src/shared/particles/base_particles.h b/SPHINXsys/src/shared/particles/base_particles.h index 3b0b8243ca..888f30546d 100644 --- a/SPHINXsys/src/shared/particles/base_particles.h +++ b/SPHINXsys/src/shared/particles/base_particles.h @@ -34,7 +34,7 @@ #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" #include "xml_engine.h" #include diff --git a/SPHINXsys/src/shared/particles/neighbor_relation.cpp b/SPHINXsys/src/shared/particles/neighbor_relation.cpp index 3c62e17c24..1f6237493c 100644 --- a/SPHINXsys/src/shared/particles/neighbor_relation.cpp +++ b/SPHINXsys/src/shared/particles/neighbor_relation.cpp @@ -110,6 +110,27 @@ namespace SPH } }; //=================================================================================================// + NeighborRelationSelfContact:: + NeighborRelationSelfContact(SPHBody* body) : NeighborRelation(), + pos_0_(*body->base_particles_->getVariableByName("InitialPosition")) + { + kernel_ = body->particle_adaptation_->getKernel(); + } + //=================================================================================================// + void NeighborRelationSelfContact::operator () (Neighborhood& neighborhood, + Vecd& displacement, size_t i_index, size_t j_index) const + { + Real distance0 = (pos_0_[i_index] - pos_0_[j_index]).norm(); + Real distance = displacement.norm(); + if (distance < kernel_->CutOffRadius() && distance0 > kernel_->CutOffRadius()) + { + neighborhood.current_size_ >= neighborhood.allocated_size_ ? + createRelation(neighborhood, distance, displacement, j_index) + : initializeRelation(neighborhood, distance, displacement, j_index); + neighborhood.current_size_++; + } + }; + //=================================================================================================// NeighborRelationContact:: NeighborRelationContact(SPHBody* body, SPHBody* contact_body) : NeighborRelation() { diff --git a/SPHINXsys/src/shared/particles/neighbor_relation.h b/SPHINXsys/src/shared/particles/neighbor_relation.h index cbd6c989d6..9d438db673 100644 --- a/SPHINXsys/src/shared/particles/neighbor_relation.h +++ b/SPHINXsys/src/shared/particles/neighbor_relation.h @@ -119,6 +119,21 @@ namespace SPH { protected: StdLargeVec& h_ratio_; }; + + /** + * @class NeighborRelationSelfContact + * @brief A solid contact neighbor relation functor between particles i and j. + */ + class NeighborRelationSelfContact : public NeighborRelation + { + public: + explicit NeighborRelationSelfContact(SPHBody* body); + virtual ~NeighborRelationSelfContact() {}; + void operator () (Neighborhood& neighborhood, + Vecd& displacement, size_t i_index, size_t j_index) const; + protected: + StdLargeVec& pos_0_; + }; /** * @class NeighborRelationContact diff --git a/SPHINXsys/src/shared/particles/particle_adaptation.h b/SPHINXsys/src/shared/particles/particle_adaptation.h index 1720f7490a..05087d47f4 100644 --- a/SPHINXsys/src/shared/particles/particle_adaptation.h +++ b/SPHINXsys/src/shared/particles/particle_adaptation.h @@ -31,7 +31,7 @@ #define PARTICLE_ADAPTATION_H #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" namespace SPH { diff --git a/SPHINXsys/src/shared/particles/particle_sorting.h b/SPHINXsys/src/shared/particles/particle_sorting.h index 937960f252..4c05f89ff1 100644 --- a/SPHINXsys/src/shared/particles/particle_sorting.h +++ b/SPHINXsys/src/shared/particles/particle_sorting.h @@ -34,7 +34,7 @@ #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" /** this is a reformulation of tbb parallel_sort for particle data */ namespace tbb { diff --git a/SPHINXsys/src/shared/particles/solid_particles.cpp b/SPHINXsys/src/shared/particles/solid_particles.cpp index b0421c9cfc..290be5ecd8 100644 --- a/SPHINXsys/src/shared/particles/solid_particles.cpp +++ b/SPHINXsys/src/shared/particles/solid_particles.cpp @@ -96,6 +96,30 @@ namespace SPH { addAVariableNameToList(variables_to_restart_, "DeformationGradient"); } //=================================================================================================// + StdLargeVec ElasticSolidParticles::getVonMisesStress() + { + StdLargeVec von_Mises_stress_vector = {}; + for (size_t index_i = 0; index_i < pos_0_.size(); index_i++) + { + von_Mises_stress_vector.push_back(von_Mises_stress(index_i)); + } + return von_Mises_stress_vector; + } + //=================================================================================================// + Real ElasticSolidParticles::getMaxVonMisesStress() + { + Real von_Mises_stress_max = 0; + for (size_t index_i = 0; index_i < pos_0_.size(); index_i++) + { + Real von_Mises_stress_i = von_Mises_stress(index_i); + if (von_Mises_stress_max < von_Mises_stress_i) + { + von_Mises_stress_max = von_Mises_stress_i; + } + } + return von_Mises_stress_max; + } + //=================================================================================================// void ElasticSolidParticles::writeParticlesToVtuFile(std::ofstream& output_file) { SolidParticles::writeParticlesToVtuFile(output_file); diff --git a/SPHINXsys/src/shared/particles/solid_particles.h b/SPHINXsys/src/shared/particles/solid_particles.h index a87f0e9b08..84a408afa3 100644 --- a/SPHINXsys/src/shared/particles/solid_particles.h +++ b/SPHINXsys/src/shared/particles/solid_particles.h @@ -101,6 +101,9 @@ namespace SPH { StdLargeVec dF_dt_; /**< deformation tensor change rate */ StdLargeVec stress_PK1_; /**< first Piola-Kirchhoff stress tensor */ + StdLargeVec getVonMisesStress(); + Real getMaxVonMisesStress(); + virtual void writeParticlesToVtuFile(std::ofstream &output_file) override; virtual ElasticSolidParticles* ThisObjectPtr() override {return this;}; diff --git a/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.h b/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.h index ffa0dea909..0ad1258db8 100644 --- a/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.h +++ b/SPHINXsys/src/shared/simbody_sphinxsys/xml_engine.h @@ -33,7 +33,7 @@ #define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" #include "SimTKcommon.h" #include "SimTKcommon/internal/Xml.h" diff --git a/SPHINXsys/src/shared/sphinxsys_system/sph_system.h b/SPHINXsys/src/shared/sphinxsys_system/sph_system.h index 8be4730d7c..b16e75093f 100644 --- a/SPHINXsys/src/shared/sphinxsys_system/sph_system.h +++ b/SPHINXsys/src/shared/sphinxsys_system/sph_system.h @@ -19,7 +19,7 @@ namespace po = boost::program_options; #endif #include "base_data_package.h" -#include "sph_data_conainers.h" +#include "sph_data_containers.h" #include #include diff --git a/cases_high_level_simulation/CMakeLists.txt b/cases_high_level_simulation/CMakeLists.txt deleted file mode 100644 index db75fcf4c5..0000000000 --- a/cases_high_level_simulation/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -SUBDIRLIST(SUBDIRS ${CMAKE_CURRENT_SOURCE_DIR}) - -if(DEFINED BOOST_AVAILABLE) - FOREACH(subdir ${SUBDIRS}) - ADD_SUBDIRECTORY(${subdir}) - ENDFOREACH() -else() - FOREACH(subdir ${SUBDIRS}) - if(subdir MATCHES "test_3d+") - ADD_SUBDIRECTORY(${subdir}) - endif() - ENDFOREACH() -endif() \ No newline at end of file diff --git a/cases_high_level_simulation/test_3d_ball_position_solid_body/test_3d_ball_position_solid_body.cpp b/cases_high_level_simulation/test_3d_ball_position_solid_body/test_3d_ball_position_solid_body.cpp deleted file mode 100644 index 421dff8142..0000000000 --- a/cases_high_level_simulation/test_3d_ball_position_solid_body/test_3d_ball_position_solid_body.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "sphinxsys.h" -#include "solid_structural_simulation_class.h" -using namespace SPH; - -int main() -{ - /** INPUT PARAMETERS */ - Real scale_stl = 0.001 / 4; // diameter of 0.025 m - Real resolution_mass = 8; - Real poisson = 0.35; - Real Youngs_modulus = 1e5; - Real physical_viscosity = 200; - - // SI setup - designed - Real rho_0 = 122231; - Real end_time_simulation = 0.5; - Real end_time_position = end_time_simulation * 0.8; - //Vecd final_position_center = Vecd(0, 0, 0.1); - - /** STL IMPORT PARAMETERS */ - string relative_input_path = "./input/"; //path definition for linux - //string relative_input_path = "C:/SPHinXsys_Virtonomy/SPHinXsys/cases_high_level_simulation/test_3d_ball_position_solid_body/input/"; - vector imported_stl_list = { "ball_mass.stl" }; - vector translation_list = { Vec3d(0) }; - vector resolution_list = { resolution_mass}; - - LinearElasticSolid material = LinearElasticSolid(rho_0, Youngs_modulus, poisson); - vector material_model_list = { material }; - - /** INPUT DECLERATION */ - StructuralSimulationInput input - { - relative_input_path, - imported_stl_list, - scale_stl, - translation_list, - resolution_list, - material_model_list, - physical_viscosity, - {} - }; - Vecd translation_vector = Vec3d(0,0,200)*scale_stl; - input.scale_system_boundaries_ = 10; - input.translation_solid_body_tuple_ = { TranslateSolidBodyTuple(0, 0.0, end_time_position, translation_vector ) }; - - /** SIMULATION MODEL */ - StructuralSimulation sim (input); - /** START SIMULATION */ - sim.runSimulation(end_time_simulation); - - return 0; -} diff --git a/cases_high_level_simulation/test_3d_unit_position_based_bc/input/ball_mass.stl b/cases_high_level_simulation/test_3d_unit_position_based_bc/input/ball_mass.stl deleted file mode 100644 index e278103000ef90f924201de3abe560a1a9100dba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 256084 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 diff --git a/cases_test/CMakeLists.txt b/cases_test/CMakeLists.txt deleted file mode 100644 index db75fcf4c5..0000000000 --- a/cases_test/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -SUBDIRLIST(SUBDIRS ${CMAKE_CURRENT_SOURCE_DIR}) - -if(DEFINED BOOST_AVAILABLE) - FOREACH(subdir ${SUBDIRS}) - ADD_SUBDIRECTORY(${subdir}) - ENDFOREACH() -else() - FOREACH(subdir ${SUBDIRS}) - if(subdir MATCHES "test_3d+") - ADD_SUBDIRECTORY(${subdir}) - endif() - ENDFOREACH() -endif() \ No newline at end of file diff --git a/tests/2d_examples/CMakeLists.txt b/tests/2d_examples/CMakeLists.txt new file mode 100644 index 0000000000..25d1ef3df4 --- /dev/null +++ b/tests/2d_examples/CMakeLists.txt @@ -0,0 +1,5 @@ +SUBDIRLIST(SUBDIRS ${CMAKE_CURRENT_SOURCE_DIR}) + +FOREACH(subdir ${SUBDIRS}) + ADD_SUBDIRECTORY(${subdir}) +ENDFOREACH() diff --git a/cases_test/test_1d_shock_tube/CMakeLists.txt b/tests/2d_examples/test_1d_shock_tube/CMakeLists.txt similarity index 100% rename from cases_test/test_1d_shock_tube/CMakeLists.txt rename to tests/2d_examples/test_1d_shock_tube/CMakeLists.txt diff --git a/cases_test/test_1d_shock_tube/src/CMakeLists.txt b/tests/2d_examples/test_1d_shock_tube/src/CMakeLists.txt similarity index 100% rename from cases_test/test_1d_shock_tube/src/CMakeLists.txt rename to tests/2d_examples/test_1d_shock_tube/src/CMakeLists.txt diff --git a/cases_test/test_1d_shock_tube/src/shock_tube.cpp b/tests/2d_examples/test_1d_shock_tube/src/shock_tube.cpp similarity index 100% rename from cases_test/test_1d_shock_tube/src/shock_tube.cpp rename to tests/2d_examples/test_1d_shock_tube/src/shock_tube.cpp diff --git a/cases_test/test_1d_shock_tube/src/shock_tube.h b/tests/2d_examples/test_1d_shock_tube/src/shock_tube.h similarity index 100% rename from cases_test/test_1d_shock_tube/src/shock_tube.h rename to tests/2d_examples/test_1d_shock_tube/src/shock_tube.h diff --git a/cases_test/test_2d_T_shaped_pipe/CMakeLists.txt b/tests/2d_examples/test_2d_T_shaped_pipe/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_T_shaped_pipe/CMakeLists.txt rename to tests/2d_examples/test_2d_T_shaped_pipe/CMakeLists.txt diff --git a/cases_test/test_2d_T_shaped_pipe/src/CMakeLists.txt b/tests/2d_examples/test_2d_T_shaped_pipe/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_T_shaped_pipe/src/CMakeLists.txt rename to tests/2d_examples/test_2d_T_shaped_pipe/src/CMakeLists.txt diff --git a/cases_test/test_2d_T_shaped_pipe/src/T_shaped_pipe.cpp b/tests/2d_examples/test_2d_T_shaped_pipe/src/T_shaped_pipe.cpp similarity index 100% rename from cases_test/test_2d_T_shaped_pipe/src/T_shaped_pipe.cpp rename to tests/2d_examples/test_2d_T_shaped_pipe/src/T_shaped_pipe.cpp diff --git a/cases_test/test_2d_airfoil/CMakeLists.txt b/tests/2d_examples/test_2d_airfoil/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_airfoil/CMakeLists.txt rename to tests/2d_examples/test_2d_airfoil/CMakeLists.txt diff --git a/cases_test/test_2d_airfoil/airfoil_2d.cpp b/tests/2d_examples/test_2d_airfoil/airfoil_2d.cpp similarity index 100% rename from cases_test/test_2d_airfoil/airfoil_2d.cpp rename to tests/2d_examples/test_2d_airfoil/airfoil_2d.cpp diff --git a/cases_test/test_2d_airfoil/airfoil_2d.h b/tests/2d_examples/test_2d_airfoil/airfoil_2d.h similarity index 100% rename from cases_test/test_2d_airfoil/airfoil_2d.h rename to tests/2d_examples/test_2d_airfoil/airfoil_2d.h diff --git a/cases_test/test_2d_airfoil/data/airfoil_flap_front.dat b/tests/2d_examples/test_2d_airfoil/data/airfoil_flap_front.dat similarity index 100% rename from cases_test/test_2d_airfoil/data/airfoil_flap_front.dat rename to tests/2d_examples/test_2d_airfoil/data/airfoil_flap_front.dat diff --git a/cases_test/test_2d_airfoil/data/airfoil_flap_rear.dat b/tests/2d_examples/test_2d_airfoil/data/airfoil_flap_rear.dat similarity index 100% rename from cases_test/test_2d_airfoil/data/airfoil_flap_rear.dat rename to tests/2d_examples/test_2d_airfoil/data/airfoil_flap_rear.dat diff --git a/cases_test/test_2d_airfoil/data/airfoil_wing.dat b/tests/2d_examples/test_2d_airfoil/data/airfoil_wing.dat similarity index 100% rename from cases_test/test_2d_airfoil/data/airfoil_wing.dat rename to tests/2d_examples/test_2d_airfoil/data/airfoil_wing.dat diff --git a/cases_test/test_2d_collision/CMakeLists.txt b/tests/2d_examples/test_2d_collision/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_collision/CMakeLists.txt rename to tests/2d_examples/test_2d_collision/CMakeLists.txt diff --git a/cases_test/test_2d_collision/src/CMakeLists.txt b/tests/2d_examples/test_2d_collision/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_collision/src/CMakeLists.txt rename to tests/2d_examples/test_2d_collision/src/CMakeLists.txt diff --git a/cases_test/test_2d_collision/src/collision.cpp b/tests/2d_examples/test_2d_collision/src/collision.cpp similarity index 100% rename from cases_test/test_2d_collision/src/collision.cpp rename to tests/2d_examples/test_2d_collision/src/collision.cpp diff --git a/cases_test/test_2d_dambreak/CMakeLists.txt b/tests/2d_examples/test_2d_dambreak/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_dambreak/CMakeLists.txt rename to tests/2d_examples/test_2d_dambreak/CMakeLists.txt diff --git a/cases_test/test_2d_dambreak/src/CMakeLists.txt b/tests/2d_examples/test_2d_dambreak/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_dambreak/src/CMakeLists.txt rename to tests/2d_examples/test_2d_dambreak/src/CMakeLists.txt diff --git a/cases_test/test_2d_dambreak/src/Dambreak.cpp b/tests/2d_examples/test_2d_dambreak/src/Dambreak.cpp similarity index 100% rename from cases_test/test_2d_dambreak/src/Dambreak.cpp rename to tests/2d_examples/test_2d_dambreak/src/Dambreak.cpp diff --git a/cases_test/test_2d_depolarization/CMakeLists.txt b/tests/2d_examples/test_2d_depolarization/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_depolarization/CMakeLists.txt rename to tests/2d_examples/test_2d_depolarization/CMakeLists.txt diff --git a/cases_test/test_2d_depolarization/src/CMakeLists.txt b/tests/2d_examples/test_2d_depolarization/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_depolarization/src/CMakeLists.txt rename to tests/2d_examples/test_2d_depolarization/src/CMakeLists.txt diff --git a/cases_test/test_2d_depolarization/src/depolarization.cpp b/tests/2d_examples/test_2d_depolarization/src/depolarization.cpp similarity index 100% rename from cases_test/test_2d_depolarization/src/depolarization.cpp rename to tests/2d_examples/test_2d_depolarization/src/depolarization.cpp diff --git a/cases_test/test_2d_diffusion/CMakeLists.txt b/tests/2d_examples/test_2d_diffusion/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_diffusion/CMakeLists.txt rename to tests/2d_examples/test_2d_diffusion/CMakeLists.txt diff --git a/cases_test/test_2d_diffusion/src/CMakeLists.txt b/tests/2d_examples/test_2d_diffusion/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_diffusion/src/CMakeLists.txt rename to tests/2d_examples/test_2d_diffusion/src/CMakeLists.txt diff --git a/cases_test/test_2d_diffusion/src/diffusion.cpp b/tests/2d_examples/test_2d_diffusion/src/diffusion.cpp similarity index 100% rename from cases_test/test_2d_diffusion/src/diffusion.cpp rename to tests/2d_examples/test_2d_diffusion/src/diffusion.cpp diff --git a/cases_test/test_2d_elastic_gate/CMakeLists.txt b/tests/2d_examples/test_2d_elastic_gate/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_elastic_gate/CMakeLists.txt rename to tests/2d_examples/test_2d_elastic_gate/CMakeLists.txt diff --git a/cases_test/test_2d_elastic_gate/src/CMakeLists.txt b/tests/2d_examples/test_2d_elastic_gate/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_elastic_gate/src/CMakeLists.txt rename to tests/2d_examples/test_2d_elastic_gate/src/CMakeLists.txt diff --git a/cases_test/test_2d_elastic_gate/src/elastic_gate.cpp b/tests/2d_examples/test_2d_elastic_gate/src/elastic_gate.cpp similarity index 100% rename from cases_test/test_2d_elastic_gate/src/elastic_gate.cpp rename to tests/2d_examples/test_2d_elastic_gate/src/elastic_gate.cpp diff --git a/cases_test/test_2d_eulerian_taylor_green/CMakeLists.txt b/tests/2d_examples/test_2d_eulerian_taylor_green/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_eulerian_taylor_green/CMakeLists.txt rename to tests/2d_examples/test_2d_eulerian_taylor_green/CMakeLists.txt diff --git a/cases_test/test_2d_eulerian_taylor_green/src/2d_eulerian_taylor_green.cpp b/tests/2d_examples/test_2d_eulerian_taylor_green/src/2d_eulerian_taylor_green.cpp similarity index 100% rename from cases_test/test_2d_eulerian_taylor_green/src/2d_eulerian_taylor_green.cpp rename to tests/2d_examples/test_2d_eulerian_taylor_green/src/2d_eulerian_taylor_green.cpp diff --git a/cases_test/test_2d_eulerian_taylor_green/src/CMakeLists.txt b/tests/2d_examples/test_2d_eulerian_taylor_green/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_eulerian_taylor_green/src/CMakeLists.txt rename to tests/2d_examples/test_2d_eulerian_taylor_green/src/CMakeLists.txt diff --git a/cases_test/test_2d_filling_tank/CMakeLists.txt b/tests/2d_examples/test_2d_filling_tank/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_filling_tank/CMakeLists.txt rename to tests/2d_examples/test_2d_filling_tank/CMakeLists.txt diff --git a/cases_test/test_2d_filling_tank/src/CMakeLists.txt b/tests/2d_examples/test_2d_filling_tank/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_filling_tank/src/CMakeLists.txt rename to tests/2d_examples/test_2d_filling_tank/src/CMakeLists.txt diff --git a/cases_test/test_2d_filling_tank/src/filling_tank.cpp b/tests/2d_examples/test_2d_filling_tank/src/filling_tank.cpp similarity index 100% rename from cases_test/test_2d_filling_tank/src/filling_tank.cpp rename to tests/2d_examples/test_2d_filling_tank/src/filling_tank.cpp diff --git a/cases_test/test_2d_flow_around_cylinder/CMakeLists.txt b/tests/2d_examples/test_2d_flow_around_cylinder/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_flow_around_cylinder/CMakeLists.txt rename to tests/2d_examples/test_2d_flow_around_cylinder/CMakeLists.txt diff --git a/cases_test/test_2d_flow_around_cylinder/src/2d_flow_around_cylinder.cpp b/tests/2d_examples/test_2d_flow_around_cylinder/src/2d_flow_around_cylinder.cpp similarity index 100% rename from cases_test/test_2d_flow_around_cylinder/src/2d_flow_around_cylinder.cpp rename to tests/2d_examples/test_2d_flow_around_cylinder/src/2d_flow_around_cylinder.cpp diff --git a/cases_test/test_2d_flow_around_cylinder/src/2d_flow_around_cylinder.h b/tests/2d_examples/test_2d_flow_around_cylinder/src/2d_flow_around_cylinder.h similarity index 100% rename from cases_test/test_2d_flow_around_cylinder/src/2d_flow_around_cylinder.h rename to tests/2d_examples/test_2d_flow_around_cylinder/src/2d_flow_around_cylinder.h diff --git a/cases_test/test_2d_flow_around_cylinder/src/CMakeLists.txt b/tests/2d_examples/test_2d_flow_around_cylinder/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_flow_around_cylinder/src/CMakeLists.txt rename to tests/2d_examples/test_2d_flow_around_cylinder/src/CMakeLists.txt diff --git a/cases_test/test_2d_flow_around_cylinder/src/data/project_parameters.dat b/tests/2d_examples/test_2d_flow_around_cylinder/src/data/project_parameters.dat similarity index 100% rename from cases_test/test_2d_flow_around_cylinder/src/data/project_parameters.dat rename to tests/2d_examples/test_2d_flow_around_cylinder/src/data/project_parameters.dat diff --git a/cases_test/test_2d_free_stream_around_cylinder/CMakeLists.txt b/tests/2d_examples/test_2d_free_stream_around_cylinder/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_free_stream_around_cylinder/CMakeLists.txt rename to tests/2d_examples/test_2d_free_stream_around_cylinder/CMakeLists.txt diff --git a/cases_test/test_2d_free_stream_around_cylinder/src/2d_free_stream_around_cylinder .cpp b/tests/2d_examples/test_2d_free_stream_around_cylinder/src/2d_free_stream_around_cylinder .cpp similarity index 100% rename from cases_test/test_2d_free_stream_around_cylinder/src/2d_free_stream_around_cylinder .cpp rename to tests/2d_examples/test_2d_free_stream_around_cylinder/src/2d_free_stream_around_cylinder .cpp diff --git a/cases_test/test_2d_free_stream_around_cylinder/src/2d_free_stream_around_cylinder.h b/tests/2d_examples/test_2d_free_stream_around_cylinder/src/2d_free_stream_around_cylinder.h similarity index 100% rename from cases_test/test_2d_free_stream_around_cylinder/src/2d_free_stream_around_cylinder.h rename to tests/2d_examples/test_2d_free_stream_around_cylinder/src/2d_free_stream_around_cylinder.h diff --git a/cases_test/test_2d_free_stream_around_cylinder/src/CMakeLists.txt b/tests/2d_examples/test_2d_free_stream_around_cylinder/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_free_stream_around_cylinder/src/CMakeLists.txt rename to tests/2d_examples/test_2d_free_stream_around_cylinder/src/CMakeLists.txt diff --git a/cases_test/test_2d_free_stream_around_cylinder/src/data/project_parameters.dat b/tests/2d_examples/test_2d_free_stream_around_cylinder/src/data/project_parameters.dat similarity index 100% rename from cases_test/test_2d_free_stream_around_cylinder/src/data/project_parameters.dat rename to tests/2d_examples/test_2d_free_stream_around_cylinder/src/data/project_parameters.dat diff --git a/cases_test/test_2d_fsi2/CMakeLists.txt b/tests/2d_examples/test_2d_fsi2/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_fsi2/CMakeLists.txt rename to tests/2d_examples/test_2d_fsi2/CMakeLists.txt diff --git a/cases_test/test_2d_fsi2/src/CMakeLists.txt b/tests/2d_examples/test_2d_fsi2/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_fsi2/src/CMakeLists.txt rename to tests/2d_examples/test_2d_fsi2/src/CMakeLists.txt diff --git a/cases_test/test_2d_fsi2/src/fsi2.cpp b/tests/2d_examples/test_2d_fsi2/src/fsi2.cpp similarity index 100% rename from cases_test/test_2d_fsi2/src/fsi2.cpp rename to tests/2d_examples/test_2d_fsi2/src/fsi2.cpp diff --git a/cases_test/test_2d_fsi2/src/fsi2_case.h b/tests/2d_examples/test_2d_fsi2/src/fsi2_case.h similarity index 100% rename from cases_test/test_2d_fsi2/src/fsi2_case.h rename to tests/2d_examples/test_2d_fsi2/src/fsi2_case.h diff --git a/cases_test/test_2d_heat_transfer/CMakeLists.txt b/tests/2d_examples/test_2d_heat_transfer/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_heat_transfer/CMakeLists.txt rename to tests/2d_examples/test_2d_heat_transfer/CMakeLists.txt diff --git a/cases_test/test_2d_heat_transfer/src/CMakeLists.txt b/tests/2d_examples/test_2d_heat_transfer/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_heat_transfer/src/CMakeLists.txt rename to tests/2d_examples/test_2d_heat_transfer/src/CMakeLists.txt diff --git a/cases_test/test_2d_heat_transfer/src/heat_transfer.cpp b/tests/2d_examples/test_2d_heat_transfer/src/heat_transfer.cpp similarity index 100% rename from cases_test/test_2d_heat_transfer/src/heat_transfer.cpp rename to tests/2d_examples/test_2d_heat_transfer/src/heat_transfer.cpp diff --git a/cases_test/test_2d_hydrostatic_fsi/CMakeLists.txt b/tests/2d_examples/test_2d_hydrostatic_fsi/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_hydrostatic_fsi/CMakeLists.txt rename to tests/2d_examples/test_2d_hydrostatic_fsi/CMakeLists.txt diff --git a/cases_test/test_2d_hydrostatic_fsi/src/CMakeLists.txt b/tests/2d_examples/test_2d_hydrostatic_fsi/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_hydrostatic_fsi/src/CMakeLists.txt rename to tests/2d_examples/test_2d_hydrostatic_fsi/src/CMakeLists.txt diff --git a/cases_test/test_2d_hydrostatic_fsi/src/hydrostatic_fsi.cpp b/tests/2d_examples/test_2d_hydrostatic_fsi/src/hydrostatic_fsi.cpp similarity index 100% rename from cases_test/test_2d_hydrostatic_fsi/src/hydrostatic_fsi.cpp rename to tests/2d_examples/test_2d_hydrostatic_fsi/src/hydrostatic_fsi.cpp diff --git a/cases_test/test_2d_oscillating_beam/CMakeLists.txt b/tests/2d_examples/test_2d_oscillating_beam/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_oscillating_beam/CMakeLists.txt rename to tests/2d_examples/test_2d_oscillating_beam/CMakeLists.txt diff --git a/cases_test/test_2d_oscillating_beam/src/CMakeLists.txt b/tests/2d_examples/test_2d_oscillating_beam/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_oscillating_beam/src/CMakeLists.txt rename to tests/2d_examples/test_2d_oscillating_beam/src/CMakeLists.txt diff --git a/cases_test/test_2d_oscillating_beam/src/oscillating_beam.cpp b/tests/2d_examples/test_2d_oscillating_beam/src/oscillating_beam.cpp similarity index 69% rename from cases_test/test_2d_oscillating_beam/src/oscillating_beam.cpp rename to tests/2d_examples/test_2d_oscillating_beam/src/oscillating_beam.cpp index 7b2a2c7cfb..70bff875c8 100644 --- a/cases_test/test_2d_oscillating_beam/src/oscillating_beam.cpp +++ b/tests/2d_examples/test_2d_oscillating_beam/src/oscillating_beam.cpp @@ -1,25 +1,18 @@ /* ---------------------------------------------------------------------------* -* SPHinXsys: 2D oscillation beam example-one body version * +* SPHinXsys: 2D oscillation beam example * * ----------------------------------------------------------------------------* * This is the one of the basic test cases, also the first case for * * understanding SPH method for solid simulation. * * In this case, the constraint of the beam is implemented with * * internal constrained subregion. * * ----------------------------------------------------------------------------*/ -/** - * @brief SPHinXsys Library. - */ +// SPHinXsys Library. #include "sphinxsys.h" -/** - * @brief Namespace cite here. - */ +// Namespace cite here. using namespace SPH; - //------------------------------------------------------------------------------ //global parameters for the case //------------------------------------------------------------------------------ - -//for geometry Real PL = 0.2; //beam length Real PH = 0.02; //for thick plate; =0.01 for thin plate Real SL = 0.06; //depth of the insert @@ -29,27 +22,25 @@ Real BW = resolution_ref * 4; //boundary width /** Domain bounds of the system. */ BoundingBox system_domain_bounds(Vec2d(-SL - BW, -PL / 2.0), Vec2d(PL + 3.0 * BW, PL / 2.0)); - -//for material properties of the beam +//---------------------------------------------------------------------- +// Global parameters for material properties. +//---------------------------------------------------------------------- Real rho0_s = 1.0e3; //reference density Real Youngs_modulus = 2.0e6; //reference Youngs modulus Real poisson = 0.3975; //Poisson ratio - -//for initial condition on velocity +//---------------------------------------------------------------------- +// Global parameters for initial condition +//---------------------------------------------------------------------- Real kl = 1.875; Real M = sin(kl) + sinh(kl); Real N = cos(kl) + cosh(kl); Real Q = 2.0 * (cos(kl)*sinh(kl) - sin(kl)*cosh(kl)); Real vf = 0.05; Real R = PL / (0.5 * Pi); - -/** -* @brief define geometry and initial conditions of SPH bodies -*/ -/** -* @brief create a beam base shape -*/ -std::vector CreatBeamBaseShape() +//---------------------------------------------------------------------- +// Geometries used in the case. +//---------------------------------------------------------------------- +std::vector creatBeamBaseShape() { //geometry std::vector beam_base_shape; @@ -58,13 +49,10 @@ std::vector CreatBeamBaseShape() beam_base_shape.push_back(Vecd(0.0, PH / 2 + BW)); beam_base_shape.push_back(Vecd(0.0, -PH / 2 - BW)); beam_base_shape.push_back(Vecd(-SL - BW, -PH / 2 - BW)); - + return beam_base_shape; } -/** -* @brief create a beam shape -*/ -std::vector CreatBeamShape() +std::vector creatBeamShape() { std::vector beam_shape; beam_shape.push_back(Vecd(-SL, -PH / 2)); @@ -75,9 +63,9 @@ std::vector CreatBeamShape() return beam_shape; } -/** -* @brief define the beam body -*/ +//---------------------------------------------------------------------- +// Bodies used in the case. +//---------------------------------------------------------------------- class Beam : public SolidBody { public: @@ -85,53 +73,16 @@ class Beam : public SolidBody : SolidBody(system, body_name) { /** Geometry definition. */ - std::vector beam_base_shape = CreatBeamBaseShape(); + std::vector beam_base_shape = creatBeamBaseShape(); body_shape_ = new ComplexShape(body_name); body_shape_->addAPolygon(beam_base_shape, ShapeBooleanOps::add); - std::vector beam_shape = CreatBeamShape(); + std::vector beam_shape = creatBeamShape(); body_shape_->addAPolygon(beam_shape, ShapeBooleanOps::add); } }; -/** - * @brief Define beam material. - */ -class BeamMaterial : public LinearElasticSolid -{ -public: - BeamMaterial() : LinearElasticSolid() - { - rho0_ = rho0_s; - youngs_modulus_ = Youngs_modulus; - poisson_ratio_ = poisson; - - assignDerivedMaterialParameters(); - } -}; - -/** - * application dependent initial condition - */ -class BeamInitialCondition - : public solid_dynamics::ElasticDynamicsInitialCondition -{ -public: - BeamInitialCondition(SolidBody *beam) - : solid_dynamics::ElasticDynamicsInitialCondition(beam) {}; -protected: - void Update(size_t index_i, Real dt) override { - /** initial velocity profile */ - Real x = pos_n_[index_i][0] / PL; - if (x > 0.0) { - vel_n_[index_i][1] - = vf * material_->ReferenceSoundSpeed()*(M*(cos(kl*x) - cosh(kl*x)) - N * (sin(kl*x) - sinh(kl*x))) / Q; - } - }; -}; -/** -* @brief define the beam base which will be constrained. -* NOTE: this class can only be instanced after body particles -* have been generated -*/ +//---------------------------------------------------------------------- +// Body parts usually for impose constraints. +//---------------------------------------------------------------------- class BeamBase : public BodyPartByParticle { public: @@ -139,18 +90,17 @@ class BeamBase : public BodyPartByParticle : BodyPartByParticle(solid_body, constrained_region_name) { /* Geometry definition */ - std::vector beam_base_shape = CreatBeamBaseShape(); + std::vector beam_base_shape = creatBeamBaseShape(); body_part_shape_ = new ComplexShape(constrained_region_name); body_part_shape_->addAPolygon(beam_base_shape, ShapeBooleanOps::add); - std::vector beam_shape = CreatBeamShape(); + std::vector beam_shape = creatBeamShape(); body_part_shape_->addAPolygon(beam_shape, ShapeBooleanOps::sub); //tag the particles within the body part tagBodyPart(); } }; - -//define an observer body +// an observer body class BeamObserver : public FictitiousBody { public: @@ -160,53 +110,84 @@ class BeamObserver : public FictitiousBody body_input_points_volumes_.push_back(std::make_pair(Vecd(PL, 0.0), 0.0)); } }; +//---------------------------------------------------------------------- +// Materials used in the case. +//---------------------------------------------------------------------- +class BeamMaterial : public LinearElasticSolid +{ +public: + BeamMaterial() : LinearElasticSolid() + { + rho0_ = rho0_s; + youngs_modulus_ = Youngs_modulus; + poisson_ratio_ = poisson; + + assignDerivedMaterialParameters(); + } +}; +//---------------------------------------------------------------------- +// Application dependent initial condition. +//---------------------------------------------------------------------- +class BeamInitialCondition + : public solid_dynamics::ElasticDynamicsInitialCondition +{ +public: + explicit BeamInitialCondition(SolidBody *beam) + : solid_dynamics::ElasticDynamicsInitialCondition(beam){}; + +protected: + void Update(size_t index_i, Real dt) override + { + /** initial velocity profile */ + Real x = pos_n_[index_i][0] / PL; + if (x > 0.0) + { + vel_n_[index_i][1] = vf * material_->ReferenceSoundSpeed() / Q * + (M * (cos(kl * x) - cosh(kl * x)) - N * (sin(kl * x) - sinh(kl * x))); + } + }; +}; //------------------------------------------------------------------------------ //the main program //------------------------------------------------------------------------------ - int main() { - - //build up context -- a SPHSystem + //---------------------------------------------------------------------- + // Build up -- a SPHSystem + //---------------------------------------------------------------------- SPHSystem system(system_domain_bounds, resolution_ref); - - //the oscillating beam + //---------------------------------------------------------------------- + // Creating body, materials and particles. + //---------------------------------------------------------------------- Beam *beam_body = new Beam(system, "BeamBody"); - //Configuration of solid materials BeamMaterial *beam_material = new BeamMaterial(); - //creat particles for the elastic body ElasticSolidParticles beam_particles(beam_body, beam_material); BeamObserver *beam_observer = new BeamObserver(system, "BeamObserver"); - //create observer particles BaseParticles observer_particles(beam_observer); - - /** topology */ + //---------------------------------------------------------------------- + // Define body relation map. + // The contact map gives the topological connections between the bodies. + // Basically the the range of bodies to build neighbor particle lists. + //---------------------------------------------------------------------- BodyRelationInner* beam_body_inner = new BodyRelationInner(beam_body); BodyRelationContact* beam_observer_contact = new BodyRelationContact(beam_observer, { beam_body }); - //----------------------------------------------------------------------------- //this section define all numerical methods will be used in this case //----------------------------------------------------------------------------- - /** initial condition */ + // initial condition BeamInitialCondition beam_initial_velocity(beam_body); //corrected strong configuration solid_dynamics::CorrectConfiguration beam_corrected_configuration_in_strong_form(beam_body_inner); - //time step size calculation solid_dynamics::AcousticTimeStepSize computing_time_step_size(beam_body); - //stress relaxation for the beam - solid_dynamics::StressRelaxationFirstHalf - stress_relaxation_first_half(beam_body_inner); - solid_dynamics::StressRelaxationSecondHalf - stress_relaxation_second_half(beam_body_inner); - + solid_dynamics::StressRelaxationFirstHalf stress_relaxation_first_half(beam_body_inner); + solid_dynamics::StressRelaxationSecondHalf stress_relaxation_second_half(beam_body_inner); // clamping a solid body part. This is softer than a driect constraint solid_dynamics::ClampConstrainSolidBodyRegion clamp_constrain_beam_base(beam_body_inner, new BeamBase(beam_body, "BeamBase")); - //----------------------------------------------------------------------------- //outputs //----------------------------------------------------------------------------- @@ -214,14 +195,13 @@ int main() BodyStatesRecordingToVtu write_beam_states(in_output, system.real_bodies_); ObservedQuantityRecording write_beam_tip_displacement("Position", in_output, beam_observer_contact); - /** - * @brief Setup geomtry and initial conditions - */ + //----------------------------------------------------------------------------- + // Setup particle configuration and initial conditions + //----------------------------------------------------------------------------- system.initializeSystemCellLinkedLists(); system.initializeSystemConfigurations(); beam_initial_velocity.exec(); beam_corrected_configuration_in_strong_form.parallel_exec(); - //----------------------------------------------------------------------------- //from here the time stepping begines //----------------------------------------------------------------------------- diff --git a/cases_test/test_2d_owsc/CMakeLists.txt b/tests/2d_examples/test_2d_owsc/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_owsc/CMakeLists.txt rename to tests/2d_examples/test_2d_owsc/CMakeLists.txt diff --git a/cases_test/test_2d_owsc/src/CMakeLists.txt b/tests/2d_examples/test_2d_owsc/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_owsc/src/CMakeLists.txt rename to tests/2d_examples/test_2d_owsc/src/CMakeLists.txt diff --git a/cases_test/test_2d_owsc/src/case.h b/tests/2d_examples/test_2d_owsc/src/case.h similarity index 100% rename from cases_test/test_2d_owsc/src/case.h rename to tests/2d_examples/test_2d_owsc/src/case.h diff --git a/cases_test/test_2d_owsc/src/owsc.cpp b/tests/2d_examples/test_2d_owsc/src/owsc.cpp similarity index 100% rename from cases_test/test_2d_owsc/src/owsc.cpp rename to tests/2d_examples/test_2d_owsc/src/owsc.cpp diff --git a/cases_test/test_2d_particle_generator_single_resolution/CMakeLists.txt b/tests/2d_examples/test_2d_particle_generator_single_resolution/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_particle_generator_single_resolution/CMakeLists.txt rename to tests/2d_examples/test_2d_particle_generator_single_resolution/CMakeLists.txt diff --git a/cases_test/test_2d_particle_generator_single_resolution/data/SPHinXsys-2d.dat b/tests/2d_examples/test_2d_particle_generator_single_resolution/data/SPHinXsys-2d.dat similarity index 100% rename from cases_test/test_2d_particle_generator_single_resolution/data/SPHinXsys-2d.dat rename to tests/2d_examples/test_2d_particle_generator_single_resolution/data/SPHinXsys-2d.dat diff --git a/cases_test/test_2d_particle_generator_single_resolution/particle_generator_single_resolution.cpp b/tests/2d_examples/test_2d_particle_generator_single_resolution/particle_generator_single_resolution.cpp similarity index 100% rename from cases_test/test_2d_particle_generator_single_resolution/particle_generator_single_resolution.cpp rename to tests/2d_examples/test_2d_particle_generator_single_resolution/particle_generator_single_resolution.cpp diff --git a/cases_test/test_2d_particle_generator_single_resolution/particle_generator_single_resolution.h b/tests/2d_examples/test_2d_particle_generator_single_resolution/particle_generator_single_resolution.h similarity index 100% rename from cases_test/test_2d_particle_generator_single_resolution/particle_generator_single_resolution.h rename to tests/2d_examples/test_2d_particle_generator_single_resolution/particle_generator_single_resolution.h diff --git a/cases_test/test_2d_plate/CMakeLists.txt b/tests/2d_examples/test_2d_plate/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_plate/CMakeLists.txt rename to tests/2d_examples/test_2d_plate/CMakeLists.txt diff --git a/cases_test/test_2d_plate/src/2d_plate.cpp b/tests/2d_examples/test_2d_plate/src/2d_plate.cpp similarity index 100% rename from cases_test/test_2d_plate/src/2d_plate.cpp rename to tests/2d_examples/test_2d_plate/src/2d_plate.cpp diff --git a/cases_test/test_2d_plate/src/CMakeLists.txt b/tests/2d_examples/test_2d_plate/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_plate/src/CMakeLists.txt rename to tests/2d_examples/test_2d_plate/src/CMakeLists.txt diff --git a/cases_test/test_2d_poiseuille_flow/CMakeLists.txt b/tests/2d_examples/test_2d_poiseuille_flow/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_poiseuille_flow/CMakeLists.txt rename to tests/2d_examples/test_2d_poiseuille_flow/CMakeLists.txt diff --git a/cases_test/test_2d_poiseuille_flow/src/CMakeLists.txt b/tests/2d_examples/test_2d_poiseuille_flow/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_poiseuille_flow/src/CMakeLists.txt rename to tests/2d_examples/test_2d_poiseuille_flow/src/CMakeLists.txt diff --git a/cases_test/test_2d_poiseuille_flow/src/poiseuille_flow.cpp b/tests/2d_examples/test_2d_poiseuille_flow/src/poiseuille_flow.cpp similarity index 100% rename from cases_test/test_2d_poiseuille_flow/src/poiseuille_flow.cpp rename to tests/2d_examples/test_2d_poiseuille_flow/src/poiseuille_flow.cpp diff --git a/cases_test/test_2d_shell/CMakeLists.txt b/tests/2d_examples/test_2d_self_contact/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_shell/CMakeLists.txt rename to tests/2d_examples/test_2d_self_contact/CMakeLists.txt diff --git a/cases_test/test_2d_shell/src/CMakeLists.txt b/tests/2d_examples/test_2d_self_contact/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_shell/src/CMakeLists.txt rename to tests/2d_examples/test_2d_self_contact/src/CMakeLists.txt diff --git a/tests/2d_examples/test_2d_self_contact/src/self_contact.cpp b/tests/2d_examples/test_2d_self_contact/src/self_contact.cpp new file mode 100644 index 0000000000..a10bd1529c --- /dev/null +++ b/tests/2d_examples/test_2d_self_contact/src/self_contact.cpp @@ -0,0 +1,274 @@ +/** +* @file self_contact.cpp +* @brief This is the case file for the test of dynamic self contact. +* @author Xiangyu Hu +*/ + +#include "sphinxsys.h" +using namespace SPH; + +//------------------------------------------------------------------------------ +//global parameters for the case +//------------------------------------------------------------------------------ +Real PL = 0.2; //beam length +Real PH = 0.01; //for thick plate; 0.01 for thin plate +Real SL = 0.04; //depth of the insert +Real resolution_ref = PH / 10.0; +Real BW = resolution_ref * 4; //boundary width, at least three particles +// Domain bounds of the system. +BoundingBox system_domain_bounds(Vec2d(-SL - BW, -PL / 2.0), + Vec2d(PL + 3.0 * BW, PL / 2.0)); +//---------------------------------------------------------------------- +// Global parameters for material properties. +//---------------------------------------------------------------------- +Real rho0_s = 1.0e3; //reference density +Real Youngs_modulus = 1.0e5; //reference Youngs modulus +Real poisson = 0.45; //Poisson ratio +//---------------------------------------------------------------------- +// Global parameters for initial condition +//---------------------------------------------------------------------- +Real kl = 1.875; +Real M = sin(kl) + sinh(kl); +Real N = cos(kl) + cosh(kl); +Real Q = 2.0 * (cos(kl) * sinh(kl) - sin(kl) * cosh(kl)); +Real vf = 0.15; +Real R = PL / (0.5 * Pi); +//---------------------------------------------------------------------- +// Geometries used in the case. +//---------------------------------------------------------------------- +std::vector creatBeamBaseShape() +{ + //geometry + std::vector beam_base_shape; + beam_base_shape.push_back(Vecd(-SL - BW, -PH / 2 - BW)); + beam_base_shape.push_back(Vecd(-SL - BW, PH / 2 + BW)); + beam_base_shape.push_back(Vecd(0.0, PH / 2 + BW)); + beam_base_shape.push_back(Vecd(0.0, -PH / 2 - BW)); + beam_base_shape.push_back(Vecd(-SL - BW, -PH / 2 - BW)); + + return beam_base_shape; +} +std::vector creatBeamShape() +{ + std::vector beam_shape; + beam_shape.push_back(Vecd(-SL, -PH / 2)); + beam_shape.push_back(Vecd(-SL, PH / 2)); + beam_shape.push_back(Vecd(PL, PH / 2)); + beam_shape.push_back(Vecd(PL, -PH / 2)); + beam_shape.push_back(Vecd(-SL, -PH / 2)); + + return beam_shape; +} +//---------------------------------------------------------------------- +// Bodies used in the case. +//---------------------------------------------------------------------- +class Beam : public SolidBody +{ +public: + Beam(SPHSystem &system, std::string body_name) + : SolidBody(system, body_name) + { + /** Geometry definition. */ + std::vector beam_base_shape = creatBeamBaseShape(); + body_shape_ = new ComplexShape(body_name); + body_shape_->addAPolygon(beam_base_shape, ShapeBooleanOps::add); + std::vector beam_shape = creatBeamShape(); + body_shape_->addAPolygon(beam_shape, ShapeBooleanOps::add); + } +}; +//---------------------------------------------------------------------- +// Body parts usually for impose constraints. +//---------------------------------------------------------------------- +class BeamBase : public BodyPartByParticle +{ +public: + BeamBase(SolidBody *solid_body, std::string constrained_region_name) + : BodyPartByParticle(solid_body, constrained_region_name) + { + /* Geometry definition */ + std::vector beam_base_shape = creatBeamBaseShape(); + body_part_shape_ = new ComplexShape(constrained_region_name); + body_part_shape_->addAPolygon(beam_base_shape, ShapeBooleanOps::add); + std::vector beam_shape = creatBeamShape(); + body_part_shape_->addAPolygon(beam_shape, ShapeBooleanOps::sub); + + //tag the particles within the body part + tagBodyPart(); + } +}; +// an observer body +class BeamObserver : public FictitiousBody +{ +public: + BeamObserver(SPHSystem &system, std::string body_name) + : FictitiousBody(system, body_name, new ParticleAdaptation(1.15, 2.0)) + { + body_input_points_volumes_.push_back(std::make_pair(Vecd(PL, 0.0), 0.0)); + } +}; +//---------------------------------------------------------------------- +// Materials used in the case. +//---------------------------------------------------------------------- +class BeamMaterial : public LinearElasticSolid +{ +public: + BeamMaterial() : LinearElasticSolid() + { + rho0_ = rho0_s; + youngs_modulus_ = Youngs_modulus; + poisson_ratio_ = poisson; + + assignDerivedMaterialParameters(); + } +}; +//---------------------------------------------------------------------- +// Application dependent initial condition. +//---------------------------------------------------------------------- +class BeamInitialCondition + : public solid_dynamics::ElasticDynamicsInitialCondition +{ +public: + explicit BeamInitialCondition(SolidBody *beam) + : solid_dynamics::ElasticDynamicsInitialCondition(beam){}; + +protected: + void Update(size_t index_i, Real dt) override + { + /** initial velocity profile */ + Real x = pos_n_[index_i][0] / PL; + if (x > 0.0) + { + vel_n_[index_i][1] = vf * material_->ReferenceSoundSpeed() / Q * + (M * (cos(kl * x) - cosh(kl * x)) - N * (sin(kl * x) - sinh(kl * x))); + } + }; +}; +//------------------------------------------------------------------------------ +//the main program +//------------------------------------------------------------------------------ +int main() +{ + //---------------------------------------------------------------------- + // Build up -- a SPHSystem + //---------------------------------------------------------------------- + SPHSystem system(system_domain_bounds, resolution_ref); + //---------------------------------------------------------------------- + // Creating body, materials and particles. + //---------------------------------------------------------------------- + Beam *beam_body = new Beam(system, "BeamBody"); + BeamMaterial *beam_material = new BeamMaterial(); + ElasticSolidParticles beam_particles(beam_body, beam_material); + beam_particles.addAVariableToWrite("ContactDensity"); + + BeamObserver *beam_observer = new BeamObserver(system, "BeamObserver"); + BaseParticles observer_particles(beam_observer); + //---------------------------------------------------------------------- + // Define body relation map. + // The contact map gives the topological connections between the bodies. + // Basically the the range of bodies to build neighbor particle lists. + //---------------------------------------------------------------------- + BodyRelationInner *beam_body_inner = new BodyRelationInner(beam_body); + BodyRelationContact *beam_observer_contact = new BodyRelationContact(beam_observer, {beam_body}); + SolidBodyRelationSelfContact *beam_self_contact = new SolidBodyRelationSelfContact(beam_body); + //----------------------------------------------------------------------------- + //this section define all numerical methods will be used in this case + //----------------------------------------------------------------------------- + // initial condition + BeamInitialCondition beam_initial_velocity(beam_body); + //corrected strong configuration + solid_dynamics::CorrectConfiguration + beam_corrected_configuration_in_strong_form(beam_body_inner); + //time step size calculation + solid_dynamics::AcousticTimeStepSize computing_time_step_size(beam_body); + //stress relaxation for the beam + solid_dynamics::KirchhoffStressRelaxationFirstHalf stress_relaxation_first_half(beam_body_inner); + solid_dynamics::StressRelaxationSecondHalf stress_relaxation_second_half(beam_body_inner); + // algorithms for solid self contact + solid_dynamics::DynamicSelfContactForce beam_self_contact_forces(beam_self_contact); + // clamping a solid body part. This is softer than a driect constraint + solid_dynamics::ClampConstrainSolidBodyRegion + clamp_constrain_beam_base(beam_body_inner, new BeamBase(beam_body, "BeamBase")); + //----------------------------------------------------------------------------- + // outputs + //----------------------------------------------------------------------------- + In_Output in_output(system); + BodyStatesRecordingToVtu write_beam_states(in_output, system.real_bodies_); + ObservedQuantityRecording + write_beam_tip_displacement("Position", in_output, beam_observer_contact); + //----------------------------------------------------------------------------- + // Setup particle configuration and initial conditions + //----------------------------------------------------------------------------- + system.initializeSystemCellLinkedLists(); + system.initializeSystemConfigurations(); + beam_initial_velocity.exec(); + beam_corrected_configuration_in_strong_form.parallel_exec(); + //----------------------------------------------------------------------------- + //from here the time stepping begines + //----------------------------------------------------------------------------- + //starting time zero + GlobalStaticVariables::physical_time_ = 0.0; + write_beam_states.writeToFile(0); + write_beam_tip_displacement.writeToFile(0); + + int ite = 0; + Real T0 = 1.0; + Real End_Time = T0; + //time step size for ouput file + Real D_Time = 0.01 * T0; + Real Dt = 0.1 * D_Time; /**< Time period for data observing */ + Real dt = 0.0; //default acoustic time step sizes + + //statistics for computing time + tick_count t1 = tick_count::now(); + tick_count::interval_t interval; + + //computation loop starts + while (GlobalStaticVariables::physical_time_ < End_Time) + { + Real integration_time = 0.0; + //integrate time (loop) until the next output time + while (integration_time < D_Time) + { + + Real relaxation_time = 0.0; + while (relaxation_time < Dt) + { + + if (ite % 100 == 0) + { + std::cout << "N=" << ite << " Time: " + << GlobalStaticVariables::physical_time_ << " dt: " + << dt << "\n"; + } + + beam_self_contact_forces.parallel_exec(); + beam_body->updateCellLinkedList(); + beam_self_contact->updateConfiguration(); + + stress_relaxation_first_half.parallel_exec(dt); + clamp_constrain_beam_base.parallel_exec(); + stress_relaxation_second_half.parallel_exec(dt); + + ite++; + dt = computing_time_step_size.parallel_exec(); + relaxation_time += dt; + integration_time += dt; + GlobalStaticVariables::physical_time_ += dt; + } + } + + write_beam_tip_displacement.writeToFile(ite); + + tick_count t2 = tick_count::now(); + write_beam_states.writeToFile(); + tick_count t3 = tick_count::now(); + interval += t3 - t2; + } + tick_count t4 = tick_count::now(); + + tick_count::interval_t tt; + tt = t4 - t1 - interval; + std::cout << "Total wall time for computation: " << tt.seconds() << " seconds." << std::endl; + + return 0; +} diff --git a/cases_test/test_2d_sliding/CMakeLists.txt b/tests/2d_examples/test_2d_shell/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_sliding/CMakeLists.txt rename to tests/2d_examples/test_2d_shell/CMakeLists.txt diff --git a/cases_test/test_2d_shell/src/2d_shell.cpp b/tests/2d_examples/test_2d_shell/src/2d_shell.cpp similarity index 100% rename from cases_test/test_2d_shell/src/2d_shell.cpp rename to tests/2d_examples/test_2d_shell/src/2d_shell.cpp diff --git a/cases_test/test_2d_throat/src/CMakeLists.txt b/tests/2d_examples/test_2d_shell/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_throat/src/CMakeLists.txt rename to tests/2d_examples/test_2d_shell/src/CMakeLists.txt diff --git a/cases_test/test_2d_square_droplet/CMakeLists.txt b/tests/2d_examples/test_2d_sliding/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_square_droplet/CMakeLists.txt rename to tests/2d_examples/test_2d_sliding/CMakeLists.txt diff --git a/cases_test/test_2d_sliding/src/CMakeLists.txt b/tests/2d_examples/test_2d_sliding/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_sliding/src/CMakeLists.txt rename to tests/2d_examples/test_2d_sliding/src/CMakeLists.txt diff --git a/cases_test/test_2d_sliding/src/sliding.cpp b/tests/2d_examples/test_2d_sliding/src/sliding.cpp similarity index 100% rename from cases_test/test_2d_sliding/src/sliding.cpp rename to tests/2d_examples/test_2d_sliding/src/sliding.cpp diff --git a/cases_test/test_2d_static_confinement/CMakeLists.txt b/tests/2d_examples/test_2d_square_droplet/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_static_confinement/CMakeLists.txt rename to tests/2d_examples/test_2d_square_droplet/CMakeLists.txt diff --git a/cases_test/test_2d_square_droplet/src/CMakeLists.txt b/tests/2d_examples/test_2d_square_droplet/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_square_droplet/src/CMakeLists.txt rename to tests/2d_examples/test_2d_square_droplet/src/CMakeLists.txt diff --git a/cases_test/test_2d_square_droplet/src/case.h b/tests/2d_examples/test_2d_square_droplet/src/case.h similarity index 100% rename from cases_test/test_2d_square_droplet/src/case.h rename to tests/2d_examples/test_2d_square_droplet/src/case.h diff --git a/cases_test/test_2d_square_droplet/src/droplet.cpp b/tests/2d_examples/test_2d_square_droplet/src/droplet.cpp similarity index 100% rename from cases_test/test_2d_square_droplet/src/droplet.cpp rename to tests/2d_examples/test_2d_square_droplet/src/droplet.cpp diff --git a/cases_test/test_2d_taylor_green/CMakeLists.txt b/tests/2d_examples/test_2d_static_confinement/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_taylor_green/CMakeLists.txt rename to tests/2d_examples/test_2d_static_confinement/CMakeLists.txt diff --git a/cases_test/test_2d_static_confinement/src/CMakeLists.txt b/tests/2d_examples/test_2d_static_confinement/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_static_confinement/src/CMakeLists.txt rename to tests/2d_examples/test_2d_static_confinement/src/CMakeLists.txt diff --git a/cases_test/test_2d_static_confinement/src/static_confinement.cpp b/tests/2d_examples/test_2d_static_confinement/src/static_confinement.cpp similarity index 100% rename from cases_test/test_2d_static_confinement/src/static_confinement.cpp rename to tests/2d_examples/test_2d_static_confinement/src/static_confinement.cpp diff --git a/cases_test/test_2d_tethered_dead_fish_in_flow/CMakeLists.txt b/tests/2d_examples/test_2d_taylor_green/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_tethered_dead_fish_in_flow/CMakeLists.txt rename to tests/2d_examples/test_2d_taylor_green/CMakeLists.txt diff --git a/cases_test/test_2d_taylor_green/src/CMakeLists.txt b/tests/2d_examples/test_2d_taylor_green/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_taylor_green/src/CMakeLists.txt rename to tests/2d_examples/test_2d_taylor_green/src/CMakeLists.txt diff --git a/cases_test/test_2d_taylor_green/src/taylor_green.cpp b/tests/2d_examples/test_2d_taylor_green/src/taylor_green.cpp similarity index 100% rename from cases_test/test_2d_taylor_green/src/taylor_green.cpp rename to tests/2d_examples/test_2d_taylor_green/src/taylor_green.cpp diff --git a/cases_test/test_2d_throat/CMakeLists.txt b/tests/2d_examples/test_2d_tethered_dead_fish_in_flow/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_throat/CMakeLists.txt rename to tests/2d_examples/test_2d_tethered_dead_fish_in_flow/CMakeLists.txt diff --git a/cases_test/test_2d_tethered_dead_fish_in_flow/src/CMakeLists.txt b/tests/2d_examples/test_2d_tethered_dead_fish_in_flow/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_tethered_dead_fish_in_flow/src/CMakeLists.txt rename to tests/2d_examples/test_2d_tethered_dead_fish_in_flow/src/CMakeLists.txt diff --git a/cases_test/test_2d_tethered_dead_fish_in_flow/src/fish_and_bones.h b/tests/2d_examples/test_2d_tethered_dead_fish_in_flow/src/fish_and_bones.h similarity index 100% rename from cases_test/test_2d_tethered_dead_fish_in_flow/src/fish_and_bones.h rename to tests/2d_examples/test_2d_tethered_dead_fish_in_flow/src/fish_and_bones.h diff --git a/cases_test/test_2d_tethered_dead_fish_in_flow/src/tethered_dead_fish_in_flow.cpp b/tests/2d_examples/test_2d_tethered_dead_fish_in_flow/src/tethered_dead_fish_in_flow.cpp similarity index 100% rename from cases_test/test_2d_tethered_dead_fish_in_flow/src/tethered_dead_fish_in_flow.cpp rename to tests/2d_examples/test_2d_tethered_dead_fish_in_flow/src/tethered_dead_fish_in_flow.cpp diff --git a/cases_test/test_2d_two_phase_dambreak/CMakeLists.txt b/tests/2d_examples/test_2d_throat/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_two_phase_dambreak/CMakeLists.txt rename to tests/2d_examples/test_2d_throat/CMakeLists.txt diff --git a/tests/2d_examples/test_2d_throat/src/CMakeLists.txt b/tests/2d_examples/test_2d_throat/src/CMakeLists.txt new file mode 100644 index 0000000000..6d1d678de7 --- /dev/null +++ b/tests/2d_examples/test_2d_throat/src/CMakeLists.txt @@ -0,0 +1,24 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir + +set(CMAKE_VERBOSE_MAKEFILE on) + +include(ImportSPHINXsysFromSource_for_2D_build) + +SET(EXECUTABLE_OUTPUT_PATH "${PROJECT_BINARY_DIR}/bin/") +SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) + +aux_source_directory(. DIR_SRCS) +ADD_EXECUTABLE(${PROJECT_NAME} ${DIR_SRCS}) + +add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME}) + +if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + target_link_libraries(${PROJECT_NAME} sphinxsys_2d ${TBB_LIBRARYS} ${Simbody_LIBRARIES}) + add_dependencies(${PROJECT_NAME} sphinxsys_2d sphinxsys_static_2d) +else(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + target_link_libraries(${PROJECT_NAME} sphinxsys_2d ${TBB_LIBRARYS} ${Simbody_LIBRARIES} ${Boost_LIBRARIES} stdc++) + else(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + target_link_libraries(${PROJECT_NAME} sphinxsys_2d ${TBB_LIBRARYS} ${Simbody_LIBRARIES} ${Boost_LIBRARIES} stdc++ stdc++fs) + endif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") +endif(${CMAKE_SYSTEM_NAME} MATCHES "Windows") diff --git a/cases_test/test_2d_throat/src/throat.cpp b/tests/2d_examples/test_2d_throat/src/throat.cpp similarity index 100% rename from cases_test/test_2d_throat/src/throat.cpp rename to tests/2d_examples/test_2d_throat/src/throat.cpp diff --git a/cases_test/test_2d_wetting_effects/CMakeLists.txt b/tests/2d_examples/test_2d_two_phase_dambreak/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_wetting_effects/CMakeLists.txt rename to tests/2d_examples/test_2d_two_phase_dambreak/CMakeLists.txt diff --git a/cases_test/test_2d_two_phase_dambreak/src/CMakeLists.txt b/tests/2d_examples/test_2d_two_phase_dambreak/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_two_phase_dambreak/src/CMakeLists.txt rename to tests/2d_examples/test_2d_two_phase_dambreak/src/CMakeLists.txt diff --git a/cases_test/test_2d_two_phase_dambreak/src/case.h b/tests/2d_examples/test_2d_two_phase_dambreak/src/case.h similarity index 100% rename from cases_test/test_2d_two_phase_dambreak/src/case.h rename to tests/2d_examples/test_2d_two_phase_dambreak/src/case.h diff --git a/cases_test/test_2d_two_phase_dambreak/src/two_phase_dambreak.cpp b/tests/2d_examples/test_2d_two_phase_dambreak/src/two_phase_dambreak.cpp similarity index 100% rename from cases_test/test_2d_two_phase_dambreak/src/two_phase_dambreak.cpp rename to tests/2d_examples/test_2d_two_phase_dambreak/src/two_phase_dambreak.cpp diff --git a/cases_test/test_3d_arch/CMakeLists.txt b/tests/2d_examples/test_2d_wetting_effects/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_arch/CMakeLists.txt rename to tests/2d_examples/test_2d_wetting_effects/CMakeLists.txt diff --git a/cases_test/test_2d_wetting_effects/src/CMakeLists.txt b/tests/2d_examples/test_2d_wetting_effects/src/CMakeLists.txt similarity index 100% rename from cases_test/test_2d_wetting_effects/src/CMakeLists.txt rename to tests/2d_examples/test_2d_wetting_effects/src/CMakeLists.txt diff --git a/cases_test/test_2d_wetting_effects/src/case.h b/tests/2d_examples/test_2d_wetting_effects/src/case.h similarity index 100% rename from cases_test/test_2d_wetting_effects/src/case.h rename to tests/2d_examples/test_2d_wetting_effects/src/case.h diff --git a/cases_test/test_2d_wetting_effects/src/wetting.cpp b/tests/2d_examples/test_2d_wetting_effects/src/wetting.cpp similarity index 100% rename from cases_test/test_2d_wetting_effects/src/wetting.cpp rename to tests/2d_examples/test_2d_wetting_effects/src/wetting.cpp diff --git a/tests/3d_examples/CMakeLists.txt b/tests/3d_examples/CMakeLists.txt new file mode 100644 index 0000000000..25d1ef3df4 --- /dev/null +++ b/tests/3d_examples/CMakeLists.txt @@ -0,0 +1,5 @@ +SUBDIRLIST(SUBDIRS ${CMAKE_CURRENT_SOURCE_DIR}) + +FOREACH(subdir ${SUBDIRS}) + ADD_SUBDIRECTORY(${subdir}) +ENDFOREACH() diff --git a/cases_test/test_3d_dambreak/CMakeLists.txt b/tests/3d_examples/test_3d_arch/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_dambreak/CMakeLists.txt rename to tests/3d_examples/test_3d_arch/CMakeLists.txt diff --git a/cases_test/test_3d_arch/src/3d_arch.cpp b/tests/3d_examples/test_3d_arch/src/3d_arch.cpp similarity index 100% rename from cases_test/test_3d_arch/src/3d_arch.cpp rename to tests/3d_examples/test_3d_arch/src/3d_arch.cpp diff --git a/cases_test/test_3d_arch/src/CMakeLists.txt b/tests/3d_examples/test_3d_arch/src/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_arch/src/CMakeLists.txt rename to tests/3d_examples/test_3d_arch/src/CMakeLists.txt diff --git a/cases_test/test_3d_muscle_compression/CMakeLists.txt b/tests/3d_examples/test_3d_dambreak/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_muscle_compression/CMakeLists.txt rename to tests/3d_examples/test_3d_dambreak/CMakeLists.txt diff --git a/cases_test/test_3d_dambreak/src/CMakeLists.txt b/tests/3d_examples/test_3d_dambreak/src/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_dambreak/src/CMakeLists.txt rename to tests/3d_examples/test_3d_dambreak/src/CMakeLists.txt diff --git a/cases_test/test_3d_dambreak/src/Dambreak.cpp b/tests/3d_examples/test_3d_dambreak/src/Dambreak.cpp similarity index 100% rename from cases_test/test_3d_dambreak/src/Dambreak.cpp rename to tests/3d_examples/test_3d_dambreak/src/Dambreak.cpp diff --git a/cases_test/test_3d_heart_electromechanics/CMakeLists.txt b/tests/3d_examples/test_3d_heart_electromechanics/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_heart_electromechanics/CMakeLists.txt rename to tests/3d_examples/test_3d_heart_electromechanics/CMakeLists.txt diff --git a/cases_test/test_3d_heart_electromechanics/data/heart-new.stl b/tests/3d_examples/test_3d_heart_electromechanics/data/heart-new.stl similarity index 100% rename from cases_test/test_3d_heart_electromechanics/data/heart-new.stl rename to tests/3d_examples/test_3d_heart_electromechanics/data/heart-new.stl diff --git a/cases_test/test_3d_heart_electromechanics/excitation-contraction.cpp b/tests/3d_examples/test_3d_heart_electromechanics/excitation-contraction.cpp similarity index 100% rename from cases_test/test_3d_heart_electromechanics/excitation-contraction.cpp rename to tests/3d_examples/test_3d_heart_electromechanics/excitation-contraction.cpp diff --git a/cases_test/test_3d_muscle_compression_soft_body_contact/CMakeLists.txt b/tests/3d_examples/test_3d_muscle_compression/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_muscle_compression_soft_body_contact/CMakeLists.txt rename to tests/3d_examples/test_3d_muscle_compression/CMakeLists.txt diff --git a/cases_test/test_3d_muscle_compression/src/CMakeLists.txt b/tests/3d_examples/test_3d_muscle_compression/src/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_muscle_compression/src/CMakeLists.txt rename to tests/3d_examples/test_3d_muscle_compression/src/CMakeLists.txt diff --git a/cases_test/test_3d_muscle_compression/src/muscle_contact.cpp b/tests/3d_examples/test_3d_muscle_compression/src/muscle_contact.cpp similarity index 99% rename from cases_test/test_3d_muscle_compression/src/muscle_contact.cpp rename to tests/3d_examples/test_3d_muscle_compression/src/muscle_contact.cpp index bd525475b2..695310a59b 100644 --- a/cases_test/test_3d_muscle_compression/src/muscle_contact.cpp +++ b/tests/3d_examples/test_3d_muscle_compression/src/muscle_contact.cpp @@ -155,6 +155,7 @@ int main() MovingPlate *moving_plate = new MovingPlate(system, "MovingPlate"); MovingPlateMaterial* moving_plate_material = new MovingPlateMaterial(); SolidParticles moving_plate_particles(moving_plate, moving_plate_material); + /** topology */ BodyRelationInner* myocardium_body_inner = new BodyRelationInner(myocardium_body); SolidBodyRelationContact* myocardium_plate_contact = new SolidBodyRelationContact(myocardium_body, {moving_plate}); diff --git a/cases_test/test_3d_myocaridum/CMakeLists.txt b/tests/3d_examples/test_3d_muscle_compression_soft_body_contact/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_myocaridum/CMakeLists.txt rename to tests/3d_examples/test_3d_muscle_compression_soft_body_contact/CMakeLists.txt diff --git a/cases_test/test_3d_muscle_compression_soft_body_contact/src/CMakeLists.txt b/tests/3d_examples/test_3d_muscle_compression_soft_body_contact/src/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_muscle_compression_soft_body_contact/src/CMakeLists.txt rename to tests/3d_examples/test_3d_muscle_compression_soft_body_contact/src/CMakeLists.txt diff --git a/cases_test/test_3d_muscle_compression_soft_body_contact/src/muscle_contact_soft_body_contact.cpp b/tests/3d_examples/test_3d_muscle_compression_soft_body_contact/src/muscle_contact_soft_body_contact.cpp similarity index 100% rename from cases_test/test_3d_muscle_compression_soft_body_contact/src/muscle_contact_soft_body_contact.cpp rename to tests/3d_examples/test_3d_muscle_compression_soft_body_contact/src/muscle_contact_soft_body_contact.cpp diff --git a/cases_test/test_3d_passive_cantilever/CMakeLists.txt b/tests/3d_examples/test_3d_myocaridum/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_passive_cantilever/CMakeLists.txt rename to tests/3d_examples/test_3d_myocaridum/CMakeLists.txt diff --git a/cases_test/test_3d_myocaridum/src/CMakeLists.txt b/tests/3d_examples/test_3d_myocaridum/src/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_myocaridum/src/CMakeLists.txt rename to tests/3d_examples/test_3d_myocaridum/src/CMakeLists.txt diff --git a/cases_test/test_3d_myocaridum/src/muscle_activation.cpp b/tests/3d_examples/test_3d_myocaridum/src/muscle_activation.cpp similarity index 100% rename from cases_test/test_3d_myocaridum/src/muscle_activation.cpp rename to tests/3d_examples/test_3d_myocaridum/src/muscle_activation.cpp diff --git a/cases_test/test_3d_network/CMakeLists.txt b/tests/3d_examples/test_3d_network/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_network/CMakeLists.txt rename to tests/3d_examples/test_3d_network/CMakeLists.txt diff --git a/cases_test/test_3d_network/data/sphere.stl b/tests/3d_examples/test_3d_network/data/sphere.stl similarity index 100% rename from cases_test/test_3d_network/data/sphere.stl rename to tests/3d_examples/test_3d_network/data/sphere.stl diff --git a/cases_test/test_3d_network/net_work.cpp b/tests/3d_examples/test_3d_network/net_work.cpp similarity index 100% rename from cases_test/test_3d_network/net_work.cpp rename to tests/3d_examples/test_3d_network/net_work.cpp diff --git a/cases_test/test_3d_network/sphere.h b/tests/3d_examples/test_3d_network/sphere.h similarity index 100% rename from cases_test/test_3d_network/sphere.h rename to tests/3d_examples/test_3d_network/sphere.h diff --git a/cases_test/test_3d_particle_generation/CMakeLists.txt b/tests/3d_examples/test_3d_particle_generation/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_particle_generation/CMakeLists.txt rename to tests/3d_examples/test_3d_particle_generation/CMakeLists.txt diff --git a/cases_test/test_3d_particle_generation/case.h b/tests/3d_examples/test_3d_particle_generation/case.h similarity index 100% rename from cases_test/test_3d_particle_generation/case.h rename to tests/3d_examples/test_3d_particle_generation/case.h diff --git a/cases_test/test_3d_particle_generation/data/teapot.stl b/tests/3d_examples/test_3d_particle_generation/data/teapot.stl similarity index 100% rename from cases_test/test_3d_particle_generation/data/teapot.stl rename to tests/3d_examples/test_3d_particle_generation/data/teapot.stl diff --git a/cases_test/test_3d_particle_generation/input/teapot.stl b/tests/3d_examples/test_3d_particle_generation/input/teapot.stl similarity index 100% rename from cases_test/test_3d_particle_generation/input/teapot.stl rename to tests/3d_examples/test_3d_particle_generation/input/teapot.stl diff --git a/cases_test/test_3d_particle_generation/particle_generation.cpp b/tests/3d_examples/test_3d_particle_generation/particle_generation.cpp similarity index 100% rename from cases_test/test_3d_particle_generation/particle_generation.cpp rename to tests/3d_examples/test_3d_particle_generation/particle_generation.cpp diff --git a/cases_test/test_3d_particle_generator_single_resolution/CMakeLists.txt b/tests/3d_examples/test_3d_particle_generator_single_resolution/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_particle_generator_single_resolution/CMakeLists.txt rename to tests/3d_examples/test_3d_particle_generator_single_resolution/CMakeLists.txt diff --git a/cases_test/test_3d_particle_generator_single_resolution/data/SPHinXsys.stl b/tests/3d_examples/test_3d_particle_generator_single_resolution/data/SPHinXsys.stl similarity index 100% rename from cases_test/test_3d_particle_generator_single_resolution/data/SPHinXsys.stl rename to tests/3d_examples/test_3d_particle_generator_single_resolution/data/SPHinXsys.stl diff --git a/cases_test/test_3d_particle_generator_single_resolution/input/SPHinXsys.stl b/tests/3d_examples/test_3d_particle_generator_single_resolution/input/SPHinXsys.stl similarity index 100% rename from cases_test/test_3d_particle_generator_single_resolution/input/SPHinXsys.stl rename to tests/3d_examples/test_3d_particle_generator_single_resolution/input/SPHinXsys.stl diff --git a/cases_test/test_3d_particle_generator_single_resolution/particle_generator_single_resolution_3D.cpp b/tests/3d_examples/test_3d_particle_generator_single_resolution/particle_generator_single_resolution_3D.cpp similarity index 100% rename from cases_test/test_3d_particle_generator_single_resolution/particle_generator_single_resolution_3D.cpp rename to tests/3d_examples/test_3d_particle_generator_single_resolution/particle_generator_single_resolution_3D.cpp diff --git a/cases_test/test_3d_particle_generator_single_resolution/particle_generator_single_resolution_3D.h b/tests/3d_examples/test_3d_particle_generator_single_resolution/particle_generator_single_resolution_3D.h similarity index 100% rename from cases_test/test_3d_particle_generator_single_resolution/particle_generator_single_resolution_3D.h rename to tests/3d_examples/test_3d_particle_generator_single_resolution/particle_generator_single_resolution_3D.h diff --git a/cases_test/test_3d_passive_cantilever_neohookean/CMakeLists.txt b/tests/3d_examples/test_3d_passive_cantilever/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_passive_cantilever_neohookean/CMakeLists.txt rename to tests/3d_examples/test_3d_passive_cantilever/CMakeLists.txt diff --git a/cases_test/test_3d_passive_cantilever/src/CMakeLists.txt b/tests/3d_examples/test_3d_passive_cantilever/src/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_passive_cantilever/src/CMakeLists.txt rename to tests/3d_examples/test_3d_passive_cantilever/src/CMakeLists.txt diff --git a/cases_test/test_3d_passive_cantilever/src/passive_cantilever.cpp b/tests/3d_examples/test_3d_passive_cantilever/src/passive_cantilever.cpp similarity index 100% rename from cases_test/test_3d_passive_cantilever/src/passive_cantilever.cpp rename to tests/3d_examples/test_3d_passive_cantilever/src/passive_cantilever.cpp diff --git a/cases_test/test_3d_play_simbody/CMakeLists.txt b/tests/3d_examples/test_3d_passive_cantilever_neohookean/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_play_simbody/CMakeLists.txt rename to tests/3d_examples/test_3d_passive_cantilever_neohookean/CMakeLists.txt diff --git a/cases_test/test_3d_passive_cantilever_neohookean/src/CMakeLists.txt b/tests/3d_examples/test_3d_passive_cantilever_neohookean/src/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_passive_cantilever_neohookean/src/CMakeLists.txt rename to tests/3d_examples/test_3d_passive_cantilever_neohookean/src/CMakeLists.txt diff --git a/cases_test/test_3d_passive_cantilever_neohookean/src/passive_cantilever_neohookean.cpp b/tests/3d_examples/test_3d_passive_cantilever_neohookean/src/passive_cantilever_neohookean.cpp similarity index 100% rename from cases_test/test_3d_passive_cantilever_neohookean/src/passive_cantilever_neohookean.cpp rename to tests/3d_examples/test_3d_passive_cantilever_neohookean/src/passive_cantilever_neohookean.cpp diff --git a/cases_test/test_3d_pkj_lv_electrocontraction/CMakeLists.txt b/tests/3d_examples/test_3d_pkj_lv_electrocontraction/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_pkj_lv_electrocontraction/CMakeLists.txt rename to tests/3d_examples/test_3d_pkj_lv_electrocontraction/CMakeLists.txt diff --git a/cases_test/test_3d_pkj_lv_electrocontraction/case.h b/tests/3d_examples/test_3d_pkj_lv_electrocontraction/case.h similarity index 100% rename from cases_test/test_3d_pkj_lv_electrocontraction/case.h rename to tests/3d_examples/test_3d_pkj_lv_electrocontraction/case.h diff --git a/cases_test/test_3d_pkj_lv_electrocontraction/data/leftventricle.stl b/tests/3d_examples/test_3d_pkj_lv_electrocontraction/data/leftventricle.stl similarity index 100% rename from cases_test/test_3d_pkj_lv_electrocontraction/data/leftventricle.stl rename to tests/3d_examples/test_3d_pkj_lv_electrocontraction/data/leftventricle.stl diff --git a/cases_test/test_3d_pkj_lv_electrocontraction/pkj-lv-electrocontraction.cpp b/tests/3d_examples/test_3d_pkj_lv_electrocontraction/pkj-lv-electrocontraction.cpp similarity index 100% rename from cases_test/test_3d_pkj_lv_electrocontraction/pkj-lv-electrocontraction.cpp rename to tests/3d_examples/test_3d_pkj_lv_electrocontraction/pkj-lv-electrocontraction.cpp diff --git a/cases_test/test_3d_roof/CMakeLists.txt b/tests/3d_examples/test_3d_play_simbody/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_roof/CMakeLists.txt rename to tests/3d_examples/test_3d_play_simbody/CMakeLists.txt diff --git a/cases_test/test_3d_play_simbody/src/CMakeLists.txt b/tests/3d_examples/test_3d_play_simbody/src/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_play_simbody/src/CMakeLists.txt rename to tests/3d_examples/test_3d_play_simbody/src/CMakeLists.txt diff --git a/cases_test/test_3d_play_simbody/src/UdfMotion.h b/tests/3d_examples/test_3d_play_simbody/src/UdfMotion.h similarity index 100% rename from cases_test/test_3d_play_simbody/src/UdfMotion.h rename to tests/3d_examples/test_3d_play_simbody/src/UdfMotion.h diff --git a/cases_test/test_3d_play_simbody/src/play_simbody.cpp b/tests/3d_examples/test_3d_play_simbody/src/play_simbody.cpp similarity index 100% rename from cases_test/test_3d_play_simbody/src/play_simbody.cpp rename to tests/3d_examples/test_3d_play_simbody/src/play_simbody.cpp diff --git a/cases_test/test_3d_taylor_bar/CMakeLists.txt b/tests/3d_examples/test_3d_roof/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_taylor_bar/CMakeLists.txt rename to tests/3d_examples/test_3d_roof/CMakeLists.txt diff --git a/cases_test/test_3d_roof/src/3d_roof.cpp b/tests/3d_examples/test_3d_roof/src/3d_roof.cpp similarity index 100% rename from cases_test/test_3d_roof/src/3d_roof.cpp rename to tests/3d_examples/test_3d_roof/src/3d_roof.cpp diff --git a/cases_test/test_3d_roof/src/CMakeLists.txt b/tests/3d_examples/test_3d_roof/src/CMakeLists.txt similarity index 100% rename from cases_test/test_3d_roof/src/CMakeLists.txt rename to tests/3d_examples/test_3d_roof/src/CMakeLists.txt diff --git a/tests/3d_examples/test_3d_self_contact/3d_self_contact.cpp b/tests/3d_examples/test_3d_self_contact/3d_self_contact.cpp new file mode 100644 index 0000000000..15269b9650 --- /dev/null +++ b/tests/3d_examples/test_3d_self_contact/3d_self_contact.cpp @@ -0,0 +1,174 @@ + /** + * @file 3d_self_contact.cpp + * @brief This is the test to check self contact for solid dynamics + * @author Xiangyu Hu + */ + +#include "sphinxsys.h" +// case file to setup the test case +#include "3d_self_contact.h" + +using namespace SPH; + +int main(int ac, char* av[]) +{ + //---------------------------------------------------------------------- + // Build up -- a SPHSystem + //---------------------------------------------------------------------- + SPHSystem system(system_domain_bounds, resolution_ref); + // Tag for run particle relaxation for the initial body fitted distribution. + system.run_particle_relaxation_ = false; + // Tag for reload initially repaxed particles. + system.reload_particles_ = true; + //handle command line arguments + #ifdef BOOST_AVAILABLE + system.handleCommandlineOptions(ac, av); + #endif + // output environment + In_Output in_output(system); + //---------------------------------------------------------------------- + // Creating body, materials and particles. + //---------------------------------------------------------------------- + Coil* coil = new Coil(system, "Coil"); + if (!system.run_particle_relaxation_ && system.reload_particles_) coil->useParticleGeneratorReload(); + CoilMaterial* coil_material = new CoilMaterial(); + ElasticSolidParticles coil_particles(coil, coil_material); + + StationaryPlate* stationary_plate = new StationaryPlate(system, "StationaryPlate"); + StationaryPlateMaterial* stationary_plate_material = new StationaryPlateMaterial(); + SolidParticles moving_plate_particles(stationary_plate, stationary_plate_material); + //---------------------------------------------------------------------- + // Define simple file input and outputs functions. + //---------------------------------------------------------------------- + BodyStatesRecordingToVtu write_states(in_output, system.real_bodies_); + //---------------------------------------------------------------------- + // Define body relation map. + // The contact map gives the topological connections between the bodies. + // Basically the the range of bodies to build neighbor particle lists. + //---------------------------------------------------------------------- + BaseBodyRelationInner* coil_inner = new BodyRelationInner(coil); + SolidBodyRelationSelfContact* coil_self_contact = new SolidBodyRelationSelfContact(coil); + SolidBodyRelationContact* coil_contact = new SolidBodyRelationContact(coil_self_contact, { stationary_plate }); + //---------------------------------------------------------------------- + // check whether run particle relaxation for body fitted particle distribution. + //---------------------------------------------------------------------- + if (system.run_particle_relaxation_) + { + //---------------------------------------------------------------------- + // Methods used for particle relaxation. + //---------------------------------------------------------------------- + // Random reset the insert body particle position. + RandomizePartilePosition random_inserted_body_particles(coil); + // Write the particle reload files. + ReloadParticleIO write_particle_reload_files(in_output, { coil }); + // A Physics relaxation step. + relax_dynamics::RelaxationStepInner relaxation_step_inner(coil_inner); + //---------------------------------------------------------------------- + // Particle relaxation starts here. + //---------------------------------------------------------------------- + random_inserted_body_particles.parallel_exec(0.25); + relaxation_step_inner.surface_bounding_.parallel_exec(); + write_states.writeToFile(0); + //---------------------------------------------------------------------- + // Particle relaxation loop. + //---------------------------------------------------------------------- + int ite_p = 0; + while (ite_p < 1000) + { + relaxation_step_inner.parallel_exec(); + ite_p += 1; + if (ite_p % 200 == 0) + { + std::cout << std::fixed << std::setprecision(9) << "Relaxation steps for the inserted body N = " << ite_p << "\n"; + write_states.writeToFile(ite_p); + } + } + std::cout << "The physics relaxation process of inserted body finish !" << std::endl; + // Output particles position for reload. + write_particle_reload_files.writeToFile(0); + return 0; + } + //---------------------------------------------------------------------- + // This section define all numerical methods will be used in this case. + //---------------------------------------------------------------------- + Gravity* gravity = new Gravity(Vecd(0.0, -1.0, 0.0)); + // initialize a time step + TimeStepInitialization initialization_with_gravity(coil, gravity); + // Corrected configuration for reproducing rigid rotation. + solid_dynamics::CorrectConfiguration corrected_configuration_in_strong_form(coil_inner); + // Time step size + solid_dynamics::AcousticTimeStepSize computing_time_step_size(coil); + //stress relaxation. + solid_dynamics::StressRelaxationFirstHalf stress_relaxation_first_half(coil_inner); + solid_dynamics::StressRelaxationSecondHalf stress_relaxation_second_half(coil_inner); + // Algorithms for solid-solid contacts. + solid_dynamics::ContactDensitySummation coil_update_contact_density(coil_contact); + solid_dynamics::ContactForce coil_compute_solid_contact_forces(coil_contact); + solid_dynamics::SelfContactDensitySummation coil_self_contact_density(coil_self_contact); + solid_dynamics::SelfContactForce coil_self_contact_forces(coil_self_contact); + // Damping the velocity field for quasi-static solution + DampingWithRandomChoice> + coil_damping(coil_inner, 0.2, "Velocity", physical_viscosity); + //---------------------------------------------------------------------- + // From here the time stepping begines. + //---------------------------------------------------------------------- + system.initializeSystemCellLinkedLists(); + system.initializeSystemConfigurations(); + // apply initial condition + corrected_configuration_in_strong_form.parallel_exec(); + write_states.writeToFile(0); + // Setup time stepping control parameters. + int ite = 0; + Real end_time = 10.0; + Real output_period = end_time / 100.0; + Real dt = 0.0; + // Statistics for computing time. + tick_count t1 = tick_count::now(); + tick_count::interval_t interval; + //---------------------------------------------------------------------- + // Main loop + //---------------------------------------------------------------------- + while (GlobalStaticVariables::physical_time_ < end_time) + { + Real integration_time = 0.0; + while (integration_time < output_period) + { + if (ite % 100 == 0) { + std::cout << "N=" << ite << " Time: " + << GlobalStaticVariables::physical_time_ << " dt: " + << dt << "\n"; + } + initialization_with_gravity.parallel_exec(); + // contact dynamics. + coil_self_contact_density.parallel_exec(); + coil_self_contact_forces.parallel_exec(); + coil_update_contact_density.parallel_exec(); + coil_compute_solid_contact_forces.parallel_exec(); + // Stress relaxation and damping. + stress_relaxation_first_half.parallel_exec(dt); + coil_damping.parallel_exec(dt); + stress_relaxation_second_half.parallel_exec(dt); + + ite++; + dt = computing_time_step_size.parallel_exec(); + integration_time += dt; + GlobalStaticVariables::physical_time_ += dt; + + //update particle neighbor realtions for conact dynamics + coil->updateCellLinkedList(); + coil_self_contact->updateConfiguration(); + coil_contact->updateConfiguration(); + } + tick_count t2 = tick_count::now(); + write_states.writeToFile(); + tick_count t3 = tick_count::now(); + interval += t3 - t2; + } + tick_count t4 = tick_count::now(); + + tick_count::interval_t tt; + tt = t4 - t1 - interval; + std::cout << "Total wall time for computation: " << tt.seconds() << " seconds." << std::endl; + + return 0; +} diff --git a/tests/3d_examples/test_3d_self_contact/3d_self_contact.h b/tests/3d_examples/test_3d_self_contact/3d_self_contact.h new file mode 100644 index 0000000000..0b37247641 --- /dev/null +++ b/tests/3d_examples/test_3d_self_contact/3d_self_contact.h @@ -0,0 +1,103 @@ +/** +* @file 3d_self_contact.h +* @brief This is the test to check self contact for solid dynamics +* @author Xiangyu Hu +*/ + +#include "sphinxsys.h" + +using namespace SPH; + +//---------------------------------------------------------------------- +// Set the file path to the data file. +//---------------------------------------------------------------------- +std::string full_path_to_airfoil = "./input/coil.stl"; +//---------------------------------------------------------------------- +// Basic geometry parameters and numerical setup. +//---------------------------------------------------------------------- +Real half_width = 55.0; +Real resolution_ref = half_width / 30.0; +Real BW = resolution_ref * 4; +Vec3d domain_lower_bound(-half_width - BW, -half_width - 1.5 * BW, -BW); +Vec3d domain_upper_bound(half_width + BW, half_width + BW, 2.0 * half_width + BW); +// Domain bounds of the system. +BoundingBox system_domain_bounds(domain_lower_bound, domain_upper_bound); +//---------------------------------------------------------------------- +// Global parameters for material properties. +//---------------------------------------------------------------------- +Real rho_0 = 1.265; +Real poisson = 0.45; +Real Youngs_modulus = 5e4; +Real physical_viscosity = 200.0; +//---------------------------------------------------------------------- +// Geometries used in the case. +//---------------------------------------------------------------------- +TriangleMeshShape *createImportedModelSurface() +{ + Vecd translation(0.0, 0.0, 0.0); + TriangleMeshShape *geometry_imported_model = new TriangleMeshShape(full_path_to_airfoil, translation, 1.0); + + return geometry_imported_model; +} +int resolution(20); //SimTK geometric modeling resolution. +TriangleMeshShape* createStationaryPlate() +{ + Vecd halfsize_plate(half_width + BW, 0.5 * BW, half_width + BW); + Vecd translation_plate(0.0, -half_width - 0.75 * BW, half_width); + TriangleMeshShape* geometry_plate = new TriangleMeshShape(halfsize_plate, + resolution, translation_plate); + + return geometry_plate; +} +//---------------------------------------------------------------------- +// Bodies used in the case. +//---------------------------------------------------------------------- +class Coil : public SolidBody +{ +public: + Coil(SPHSystem &system, std::string body_name) + : SolidBody(system, body_name) + { + /** Geometry definition. */ + ComplexShape original_body_shape; + original_body_shape.addTriangleMeshShape(createImportedModelSurface(), ShapeBooleanOps::add); + body_shape_ = new LevelSetComplexShape(this, original_body_shape, true); + } +}; +class StationaryPlate : public SolidBody +{ +public: + StationaryPlate(SPHSystem& system, std::string body_name) + : SolidBody(system, body_name) + { + body_shape_ = new ComplexShape(body_name); + body_shape_->addTriangleMeshShape(createStationaryPlate(), ShapeBooleanOps::add); + } +}; +//---------------------------------------------------------------------- +// Materials used in the case. +//---------------------------------------------------------------------- +class CoilMaterial : public NeoHookeanSolid +{ +public: + CoilMaterial() : NeoHookeanSolid() + { + rho0_ = rho_0; + youngs_modulus_ = Youngs_modulus; + poisson_ratio_ = poisson; + + assignDerivedMaterialParameters(); + } +}; +class StationaryPlateMaterial : public LinearElasticSolid +{ +public: + StationaryPlateMaterial() : LinearElasticSolid() + { + rho0_ = rho_0; + youngs_modulus_ = Youngs_modulus; + poisson_ratio_ = poisson; + + assignDerivedMaterialParameters(); + } +}; diff --git a/cases_high_level_simulation/test_3d_ball_position_solid_body/CMakeLists.txt b/tests/3d_examples/test_3d_self_contact/CMakeLists.txt similarity index 74% rename from cases_high_level_simulation/test_3d_ball_position_solid_body/CMakeLists.txt rename to tests/3d_examples/test_3d_self_contact/CMakeLists.txt index b22fde6d8b..3ee5bf7852 100644 --- a/cases_high_level_simulation/test_3d_ball_position_solid_body/CMakeLists.txt +++ b/tests/3d_examples/test_3d_self_contact/CMakeLists.txt @@ -1,3 +1,4 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 3.10) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${SPHINXSYS_PROJECT_DIR}/cmake) # main (top) cmake dir set(CMAKE_VERBOSE_MAKEFILE on) @@ -6,36 +7,36 @@ PROJECT("${CURRENT_FOLDER}") include(ImportSPHINXsysFromSource_for_3D_build) -set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) -set(EXECUTABLE_OUTPUT_PATH "${PROJECT_BINARY_DIR}/bin/") -set(BUILD_INPUT_PATH "${EXECUTABLE_OUTPUT_PATH}/input") -set(BUILD_RELOAD_PATH "${EXECUTABLE_OUTPUT_PATH}/reload") +SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) +SET(EXECUTABLE_OUTPUT_PATH "${PROJECT_BINARY_DIR}/bin/") +SET(BUILD_INPUT_PATH "${EXECUTABLE_OUTPUT_PATH}/input") +SET(BUILD_RELOAD_PATH "${EXECUTABLE_OUTPUT_PATH}/reload") file(MAKE_DIRECTORY ${BUILD_INPUT_PATH}) execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${BUILD_INPUT_PATH}) - -set(FILES_STL "ball_mass.stl" - ) -foreach(STL_FILE ${FILES_STL}) - file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/input/${STL_FILE} DESTINATION ${BUILD_INPUT_PATH}) -endforeach() +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/coil.stl + DESTINATION ${BUILD_INPUT_PATH}) aux_source_directory(. DIR_SRCS) -ADD_EXECUTABLE(${PROJECT_NAME} ${EXECUTABLE_OUTPUT_PATH} ${DIR_SRCS}) +ADD_EXECUTABLE(${PROJECT_NAME} ${DIR_SRCS}) add_test(NAME ${PROJECT_NAME}_particle_relaxation COMMAND ${PROJECT_NAME} --r=true WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) +add_test(NAME ${PROJECT_NAME} + COMMAND ${PROJECT_NAME} --r=false --i=true + WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) if(NOT SPH_ONLY_STATIC_BUILD) # usual dynamic build if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${EXECUTABLE_OUTPUT_PATH}") target_link_libraries(${PROJECT_NAME} sphinxsys_3d) add_dependencies(${PROJECT_NAME} sphinxsys_3d sphinxsys_static_3d) else(${CMAKE_SYSTEM_NAME} MATCHES "Windows") if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") target_link_libraries(${PROJECT_NAME} sphinxsys_3d stdc++) else(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - target_link_libraries(${PROJECT_NAME} sphinxsys_3d stdc++ stdc++fs) + target_link_libraries(${PROJECT_NAME} sphinxsys_3d stdc++ stdc++fs dl) endif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") if(DEFINED BOOST_AVAILABLE) # link Boost if available (not for Windows) @@ -44,12 +45,13 @@ if(NOT SPH_ONLY_STATIC_BUILD) # usual dynamic build endif(${CMAKE_SYSTEM_NAME} MATCHES "Windows") else() # static build only if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${EXECUTABLE_OUTPUT_PATH}") target_link_libraries(${PROJECT_NAME} sphinxsys_static_3d) else(${CMAKE_SYSTEM_NAME} MATCHES "Windows") if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") target_link_libraries(${PROJECT_NAME} sphinxsys_static_3d stdc++) else(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - target_link_libraries(${PROJECT_NAME} sphinxsys_static_3d stdc++ stdc++fs) + target_link_libraries(${PROJECT_NAME} sphinxsys_static_3d stdc++ stdc++fs dl) endif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") if(DEFINED BOOST_AVAILABLE) # link Boost if available (not for Windows) diff --git a/tests/3d_examples/test_3d_self_contact/data/coil.stl b/tests/3d_examples/test_3d_self_contact/data/coil.stl new file mode 100644 index 0000000000000000000000000000000000000000..6621c51e2fa3454b02364689a57946fb6e58dff8 GIT binary patch literal 2565284 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 KXW3de4l9_*4>Q-R)9t`+ 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

P&piA9xAU3;ei1Esa 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

S^V^3ZC%aI8Je?cS2?U;BKxwZxE%5b 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 z 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�cb|-y_ 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∈UWl{P|;|A7CV?4 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
vr~5(tS!JGWR!}6Eh1> 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 |CcnI2z7t%icYu32Y@ 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%0AV38Y1u;CI*T!CTpp0f9bLz~cTt?2wbut3FqM0~; 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�gRiH$GZaCyOWbT;PzAv%QA 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�jSi6w`k(9BVu*lg`pLaZ0k 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%0A+7N-@!*kU{|Mtc6{&a-wdt`Ap`j|C{fAWrR?>T zv4V*=`qun_yI%|w?m`A~&ZD_X=C02sHGy4AjA6!ZMSl#TW^f#bx6W7gCWaTe$ymX} z3TvIQ_op8QyLiYzE@_pf>`mNkuO_godwI4oXhuchT2IJ8+HX!*GIy1hIm=kV#OIJR z#sTL43>A|h13AHazp}4pGu}x>U{}ZQwMa-x6`}EL$Ur8UnfgH9-d#_|3MM|^vn0CZ zm4tm`AOq<+OqO78Vz#-Rnn134?|BlGT1{9tG>Z|7Zp%vMu4i~187uhjPH@nZkPTIZ zezhS3>1UI#ykBj2?b<2=yKaY$Ac5Pe3jvmpfef=S^?`iFS;|b}0!uR8!~^TgHeQqn|20Ifk6CDq{r`XLXB-_E-(UA_X## zT}nSm@C?@Gj7lm3yQWv&K|-=>3*szGMpXFsQG#c%dhRKgu!4!+tF{uIv6gUqOFc$B zt!3&1S*iaw6@gu2J+esf#kxYj6n93H9RI8Ifed{7LBa|qTJ$_yg`}`TvzN{xKAKQx&#)>vdo?-W) zb1DM6H2yz{_N}vU;nzq;6mPGiWbSIFND@{s(R$KXqWk11^xiUw5vSLiGLX)@kEjUj z%C<3M?_8=coEZxl$dBfxK9GYB?~|~Ci4*mzQ0*TVp_wIQAU_^*Ro)>z?sJBUz^-rh zRy44(htSXrGLZk3I4gZ1!voVKtYG56PfHqX?k=3_0~yFlp{72Nmb(_K2<-ZsR*wc* zH5B&l*u#h^jZJ+Z7dg+9u!4y}-EFDX##3mPeSi`598G;7pA?Q(5!f|w1+K_6z0|fqJ9wWY4 zn)*Q6kKrV&V50pLPpS>@6UyQt1NqLy)CV%Xi<^qTu399JYC?mACk-J3>9^MOKH-#S zHWF4a5fkW7wT%M>|2{DD+8J)@139ez`)o{LSHJh6R1*~rCOl0mLrm1`=xI45%833GBL)EYhF>kwS;|kb%rfG4+A$wq`*jRxqL25=FI% z5kj}-kb$I8raq8%XIknpfnB32Hm91Ayx?yI8OW7?Ono48*A3NU1ry2EO{sRAURZk@ zhzg5dV25hI3P4UnOylk>StdaPigPhK+`GBZkuTnib< zR%~PpYh;6Mt8tjXuJ`xjsAhh&P`{SQ2>owA8Ty{LA61jX3MN+GjiK7bqR?|P5I$}J zO73-FO0bH+u7r?y8oVk-2yOz2#-7VeeISq53gfVXiBi8<8nUjL5bz%mH8j4;d)(V4 zcT*AAHRyUg)nvp8f#V_3cy*zd@*ejFzP&lDU}DR;SgPILLO47Jh_Rc!l=q)k$(^Yp zuI2#7;A{>nm`K^dY8-AUd|M5~&?)XR^mN+aVYQ0Du1+^w z(%_SAg`RK6Frw8NQy<75XV-98!9>jU7Bn=wweW5u5VNSWk_&IvZI_C`u1QgGRC~6A zP@^_vAnkvca{BI_cXL?5#PFyX8hWXnFkv-hAXooaPlldOgYTYJ5!hAba}3qwbruGt zK%#Nr?z%GUJJ0BQhQkUbvVTTX?aht?2?Jv2c2gh7xjU|^2<&S0HkxW4bQAhchFyR+ z1*Sfb(@$OHu!4!1{{$NRw2KgI1;mf|8cM!>|L%t>0=xEU8ByF*NN)qX8EI3hDf#y6 z%^z}D!Gu{yo`$^ZE(q6`Ga`SysSl*f%XcaQyJlb1Q*CJ<;ZRlBm9g7n>H{g)e8*u0 z6We}8P~DGS!j=(`fxO@JmjurntnBhfMPQeBIGk$!^%wN1u)B2Q%ohoIIyuz*&0z%- z+23?jTcw{+_3e5_q@OTlAeW4;%3}h%W`EMsz#4;v#{VGE_$>N`1kd)}URj053MOvF z2hrf#1BHn^5bizhOVHElua3QQ68$aga#`w6gX<*;4;#a7U)yRoB-ryh@4_;WNWsLB zO1?D2IYBTC+{uWejV~*GAP-Dr8AwE6SG8rHG{j?=&?O3XEoWFzWsdX9$C}3qCPG{r zP@T^ZVQZItjM&xjqy&9f9yGC65!e;^)0qYZjT9c#g#FrQZ4XP3)0dCi@mRq`;~kDv zs~ax7s0;hGj^|7nNVD-SDgwLu+_0g6kz<5k8L*2S{l%1l%sb=6V+9lKH`Sp*hEc-y z)yEjIbk7P2a{9;WHBb@QHNcCFEMvwC7e2#o_&O`rk%AqEi=P{h6->0Vu1-VZ#|mb* zAp`lJ<5Xo0V6taJ6@gu&zgMCm9sU#A{exY7L;sP=8bIf&o;+4CA(&O9x~>xhznj^N z2rllRtYysG=&K^IYs9uMB&g36;qW;~C*+=pkszmkw6!;n6-@N0{*ij~8IY(9Ui4sZ5Ia_s zpr=zciM=Tg{VnX8lzf@2%bF=@lP)o0;GTEc(9p zgV~VN_vsd_BCt!kb(EwDvxS}FRYq*GOV5U!{(=R8JXSE#!9SBYM$Zx)UqJ@4q`58| zdO9`ftW^=%Rgk=qth<;jJiG`Q$hLcGW<&q>uIw$3SiwZNW<7DZGF!;%o6CrcCtc3K z_{xK2ATfbmBeu*VYg^3~xNmuku+6#N1jbhz(=w&)yXm2) z(~x0W6@guS=l3S5H|7aexdn{4U1(7HK)P!+JXSEVC$a}|xII_c-zgtHRmOHbTuV02 z3|0}?^`=4)N$a*ixE%vIvTps4>fu_l);o~L3MQ^31rW!c^M(AY@Tv0l|4=fJzGWH} zfnAeNlpEJQStvLp-eknb;!4U|`1qOrJXSE_{_UsH;rRmLYH=YW(rY*5;9BzUQhPH zZBFXj#lr9vP-EcV4oZIp&pr)#tYG5)s^T1n4=KV4OE~gt?I&{3(`ooRHGy62A8m+A z8@*I`ng%sKwVT30Pp5mu4S1|zqW9$VD93S21an)c@vEaL19>G)O<>m`i-m^N@5=-^ z8frXwzFgTW+WNqi#|kD&|I9Pk|5_^e68K-eUA9%(V@a#3Ca}xc^Nk^O+6tk31$?TO zd$%d?^p55nd8}Y!>f<7V{mkV;ZEL8}DD=3pS2X{ry^6rDfy2Cn)XJ-bnAt$s+&ixH zft+q_%VPx-U%oaF9L!b<{j0+F>bL7f<=y19v~^Vkc0H}rNm#dVweZ>w2(Pvmm3ORP z=xxPg1rxoC+X;?~R|!cO@Eh#h@vgF0WcAxpMPS#HU$cZ%tF=Poc0lZ{ZR!JA)7OH> z3MMQpXA1Up)(GRC!nNe`jS}VEn>FFd~{h%VS%O>c&ux?Ab5cCD^QIoD!QhGWSwlC(ef{AAaDmZLU z6Gm)=d(`(C)s=TncH8+_MPOIAnEOJi_Xgq8Ex6Yc!Ry*ePp5VVuc-*^>X7-e<>J6@gunRb4SPYOBz5C}bda*RHSh%Dt5OkkIB ze3-cI(H`OPO2|Oo3^Qf!f)Wntv4V;3IiaG%)7`?m=P*m{>22x*xhkQLiomYH`y$2E z0sDj==Ia>oJ3_<5n&q!LjrCZ;M8UJhqW$2#!i3>4+b-B|>I3=nWT!|>VAngHK}>zU zUl`SOB_rJa*9WrxrTI;;f(gg&oM``UpWt(21tUH$)++BNUz}c&g$eB1Un^Q%H}aq` za=~IoTSDySuZM)pjgV+e zoAkeTlbdhK#tJ4@L`92^KMo4Nqk(vn6r}V|?AG;#iomXR+hfJlDVf5`jgV-}`D5w> zxuED%HdZjPeMfWAo@HeA^Z=qmf++*}dy12U3G7uMBsW zu!4#1omz5H(K!8{DYG5Gjh3*23A?4SqGRpj z!ry~HR5<9t!+K)-9*a~2c1>}K7ypl{GmodDegAkUkuBLJNu-kfP;zFNktGxo^}FWp&*$wjXPay8`!m<% zZ>r=bOp79f*2lkdAmg;NR8WCLoEbTeykneh870d7|IUFduPTuc(3Si=jL)+=!QJ+w ziALqZzjGj$eNd>N0*N1wLwHM@<6MK164L{$#h$@y-a!cgT?f8~@&#o2SFi(3G@_*c zn*-VRkP0f0*z_}qx7vDwb2~%4tmzM!&(+q02oAgdyOFm#=v=kkv#s6e9axZb=~ zm6}_9jb~k`_7g77?d8hnqA5 zd3r+)L+`JBJlae`K-c7hc6>qoSuXJq%|LdTdqSK8IpL#;02N5Y46x;`n$B>+b50TB zeQbp|hyKttYY72eC08u@Eivc0A+Kl#a{atr3|(i)_OTM60*RUH&3WtL=eU)hXa>@5 zO{nEw4PQ%$Zo8K=v}ZWq*$p3k=MrK_`_bY$L$^vt0Vu!T332nXxtK}4w!=k2 zKv&59D_s7^dQRsU%|Mnf))Utm3{0H`s7Q$84V>kd%iOk(p@i_edrMwUKD+G-$#apx z-wFY)omm2xzp#-gfj zfP#|ST;f8SfgJnRTt#O`SEck6paO{>vn~W!mEPhM`7{HWrqfrPmtmLcEFqvP;eAX% ze%HI4kNHMIup|7%*$Eq7ISEjKL>pCffMvHkoY8Q4i7e$rZ3GvKHR%kMdvLw=XI12&^4>d0Ok8V;`(=> z8OWRK_KEvA_b*xqP=SQHOb=PI|8kRC(@%TSSC#mlSGt%>2Tsa-TBmON(kukO4^R{2fyIzTl5HVX3IPAd3Vk_ ze^gL`1h2UjSq^>9McLB~r99?dTqsQRH4>$2?1TcdJQOlTr<~qLmxu)?W`@%bm~#?Km`>@ zC_dLA%h*?3oDR)Eu0LiV&VgK+bX`I~m+I6Ll>hWKSFtae5F1KyB5(2sw?)`=eroH7NdeaQ#tEC+TI^);3MWuoYB$oaB zgsf)1;c6c)AjC#8^_#By?6Es6A)xEijW)R8{X1@RJDP!Xcy1v+XY?RRFhd0r*Y0Rx z>rZdF1KTqRF+ayvptGZ0rk6?x=oqj$?9e+3p^!rhl!m6MGiNpa8*joQH_o0^F zwROYLMW`U}$M4uK5(2u$=egkg4PUup#bZKvW%Lw3S7tBGR8WD0T+10-ZT!N$xk)GR z>-74YfeZ^SFZ2+q z$ZOTy(Lh2#*Bfs#C9TUZ?#ikTgqWK0cMfF6fXgSL0tv-LKWy#rldITiLI}-+9%8Pq zPvVQdkbtht1Rm$_`ps!*(+uSCb*|#G$u+vA`auN}T!ufk+VhL+@skpF`u&{)SwAI2 z4hiTw`Z@^bxwLQ#P!B@P(R39v+qEO6%Ao=Yi{Cu9bpOM(TT3&L8M436CO_2oxP*YN zrzb*j-H2BF$o<}g(7V=4{N43boRvcb5@qTjY;~-KD-ER?$T2Hig-Y^%ENP|9KmxjI z>ceoIjRv11aD;fN<1Em586U^%Gf;sdf-UPV&YJA3>&-v~5_t~&vHk=N-n$3QKqdtC5c7#)ONL7b=-N{kfor#F z@@E`qqOnZrDA4Ceo$Nk_feIvEV^YIGlTY!cgmA)9plcaRLsKOLbbUQUj$^VGzjq={ zG*-2-7wEmOYL^uZR3M=&CpDgH@;8GhkyX$|p!dG&T-hWcpey=rIIip7nx9gXM2I6d zI*Fg%=-|x^R3OpqdMGy7qs4cNqy+QWM*L0OdVi;cfG*z%@}4)h=C{?*L}NQEEAcmR z{{3AHR3PyyJOt~{XwA!mDbY}DA<%nYODB?*7Vx*AD^4>M*H*OQ?I+PhqpHqCoE^RA z?Fj}dkhs?_2=}l5)=CKI8lw?}>t<{7)q`oGvE{pgK<|Bx46S9L z0tv>9$NC@J@SR_-CB(7?dg5<;x$j*G0bOGwI9w-d%OB}McQf>MwH4@$tt+?hFi?TS zkeMttIHt|ND$gcFzwLiBkk1#rmJrZ&{FxlreQnDtAJRnQq#Lcoo*|>WnSlx>cX~z#SJJ{#zPYD5CXRh?dwN*NN_hh=elw|W!%nt58@PmO0B#v8oV|}vM zx#%3tKyK{ZtfJSo{R7*ukbo}E(i7J$(d8rVmJz~X|6>)skEQLD)+|&Y@hjO4>$m9e zMmCiAe&M!?K5t^CuD*nTF0CqOTo=%e_p+e7ef{TNQPFuByFa#Lp#q8bENN9!HY8tapfy9&n_E?`x)49}} zW+0m{R;uWXts)Cc2?1TVdUe9JXZ83t@96I9*E0uIv=_a#$()4>Bs$!*#`>Xp{EE*9 z2(jINhl)OL;`oqG5(2u`v@^qXYxH@vg6<;EoURZvsn?1+vQUAtqQ=H2gWoXAj1rqw8`%fxGJm4ylmMtu86Gy_={rzzh1 z+H!!*frNhxxDt#mqPpSj`S_Uggb*>!Bw<6|CEfl_-G7YJUzH+&1EWY0`Yj{Z9w0 zY0nT6&%+uBVV1a6PJ4!B z8SWASx6aypmO$K8d63M7^)U6B566Mobw`d=3@A*%48rz?|p4;LQ=)x zZ$X!_!Dn?{qB;L<1Fg~UHCmjP@glh!3l&IQUhrP6|G|t8)}u$>5H?Somr;IDNGpUKae#LRzCPGKHZ%!@^cwS*Spwb#Au5!BKPmmzaT!xwc;1o49|}UP3?@&PWfa z`(nY{gwh&4r~J)8wtTf^p#q63t5yaWEU@4gou;qVIJ2$dyo@o9QUbaHA3hJLJ7LLJ z=F=mu@Z81(lH=gVShG-p#O!HL0`&bX`6K4EMtRi{F>}|cwUvZ`t_dCvTsZI^%gB4$!AzWTyI z1rqyu@8k5_+3?Ivnt`;Nsm1Of-$6aEcM<}+x^F$r)rEKBGuzO!L7q~RrJt*hC!RA< zfrS1s#2H+&;ZqONbJY96?ZoFI4>NfnA)sp}Sp%&zwB=>Z^jv!|LYJk_CJ#7vgMkVp zEYhxX2HBnXPh~U%c^#AWp@&rjco~xr7NX@a<;#|%<9@`kGK;pE94zGWU z{8#&F*Ei&foj8}%*dbp+K-c{n9r!xyE_~mNV}$7b$(E%(!>WbZ3{)U7eRq4_pxBO| zz3?a@MjJYcGq(QQnkFHj%iFpmUw5wy|3mX1LKytEXKBx{IzEMg3M9tYS@Qbhy6|J$ z93n*9%I++^r(v7lI0*q=|6Q^t)86dK^Gs<5a@gE%;#|(KC(#U4AaNtsj@P%h=N}EB zR}<-KXEA5_W0{YHfUfngd+>D=yYkvW<%B4U?7`BWVaV~G3{)U7b&33}oI% zH*qfKn!K}eNI+N2ARoT&r2}7ijAkH1U0uZQpy8@Ra;QMUyx5C3nC8ICwJBj@?;+-Q z4^&Q+5YW{>(~qy+=g9YQp&7{iq_3j0ye$XGfH_r0Wb@`|Rlp6-Yc|8D9UrBR}jLT{S4&;2}Peu2GK{paO{j3+;I3+Hl;p=_nzp7R0lu)05R(6NgF&=o+#mA)u?$$ePdX6N4@1(mfWzt&UCH8>v40<+cD7 zNUTaVpuSr#6N-`E;$ANAE>O*JH-Yk-b9f3-0hhqb5k$i@$R%dT$rR_`}% ztA+|B_IkJH8*`%Z1;53Fc)UQ@zq7Zc+SkiULO|EzL}PxmMHC)aH;)h%nOgp9Z7tRI zQ*G5yfyA#ddc570NW8j`&I-HLZ0wJfZ-p*>T_gl_C4}koU91M+UNSn3B;H@o-z4>| z5WU}14HZau{A$DRZ-~HE7imsL|BspfBD7Pu^@WiT(DizYHsAGmfBaJ!LOxGF(X~q z-u>#HB=xGQ~s3mxz6K%-_u>>?)!E9zkO)ShS|qR2Hx9pgd99Fe$s2VDe*!|}mr?ipdmu>zf#L<(F*^5=T*_xI8B?NR`xp0+h><+k3ZJ*M$SrP=Q2#*m17nLmxcv!zDskSDs~;%`x)t8tEq?piBG8F>cRf zA6z_-W{LuRPO#P4M*eGev1+J5;);7YSE}KIRk1aMm^^n6Yq6@Q|B8D(B?NTcZnKMf z5F^9UlWPevlvl8+dwTlMD|1mp1rjg$0dk2x@xFE!Y?q9dmQ18Kb7nt#D9PKv17K5^Y?!GM1TZz zeQQ0Fd+g(eSM5GRh{w*}Y)<_+|LJp|3s8YXZnr2-dCnDI*Vs;ojdB}yWVh-5^?64n z1a#?}4&-{icEKl%XpMQVjM#Alr~5laRSHmngsZPRw>QNFN1mp~k*GI})rv`0ubG`7 zz_ScIi`H=Fe0aie{P-}PPFZ6|Pf%?o#JIB&?8B&J zwbO}Y2?1UEueIk>U5Dd)CpHmc1`cN*PD)nK8k{UZ1rnJT4S93>5%}vXdR80NiD$DS zlGQou7fT4}+UcXmFL^r*FX~RuQ961Y`+P#Oda=b~0V9mnRIvPKpFV>|NB8bsv?iPVAg6Km`&Pe13A1V#qsqCz%id z_dBy6nPm0e?rS9kbh*s`z;!VjhSztZnbhW;wrtd}Wc7~vH3C#1!Ro!^Ds_hA_6mBg zRaTm?1NtPZ8+NXf5YYAg{8P^F^iXWKjy`ETa9#)Ydvvn;0l!Xw3M4MAeZ)0Z4#V05 zq6neUr!CvrGg-aqS+<0Lt{vxZa-C-l#WOy66XL%&ZCJ~J$!g#E*#cA`G5N$b?r7{V z+~+@kLQK;8%+xxQ_x$cA2?1T#Q);<)WYuFsF@BQ(6-X2oo#$5R z4#RJ==-K(;vOCOS$7HobYMz9Eu6<8b+_U2`_^XPhsUBUq$yoJER*(6fXJMnAe??)x}o}Bm{JY=^fs{`*8N(kt>`mv1j{xk$H_54YGeMU^)!5I1_tL>OV0V>UN`Q~B?0bSF#ZQ@$58-mBWJRvp83Nsn4-pT5)*F^$U zAfaBD!xg5+;3h+w_-$;CW8St;RtIS-Bm{Kz?!1b7WyNBS!uQZ?dV66u8Kqpx(7?w&1=(^K6lDir_7-2S zrxc(9i36SyTteR=cy%ew@1CwNk`HQ=tbVk+R6;-(XY0YmT#Lq@c_oCfxsW4&+Jk&{ zuPFtnKw^A}E7yE}FrGG#p4Dc075aJoOj0+wlt~Eay3nT`w=*CbyC%?VZT;18e%Hvi zHea_?fC?mn`{{6U*ELYUfl zt7h7ha ztSZ_fS*_7lAwUHZNpY=IE*67uO$x1Xv3ZTE_C=B!y(p0o(A9rOXw9zik@#QDIfQ6U z_G@j(=W2Uku>ciFthpIf!wiYS9b$6`VXc2qRrowfZMUvSLO|Eue&=cqmJGzbzSH-7 zLC7K1Tc>1o(xWW`R3I^J;pv*-oJc&&m%it_EU&3FNR9aI1rh?f>}yQXk=q0CxO?=! zGJJDQHImfW|2a>93M8C%cR*p62jWN1%L#!^zpK7KOHyljZI%$w6*(*j{bMlzzt5#f za{CqERl`Y*wNqK&WYv zI?&(Uescw=K;pBk3Hivw@C!3PLUarpBY2S-%POWx21y z-VevbH~}h^ht7>5gqh|vp@n>|{%Ji{LO@q%_75t>A^2-c93g&Oi5G^F z8d^6-3Q&Q>g55um&#_RvJ}`+8MmOgRe@Kmj{74A_U8gH`aB)j8&I(;gh(`WtZ~7@Aned}7a_W>PZuIdjk~2T z0#qRJ+RqZZ&Ira{hI`0A)JFZIz==%N92XCLjlYA}BI-7>)3n`?=s@g^sR3PCx zUWWTl2*fU*eh|Vap-8YIH4a`nBq5-yb($QP?cs2vXIrusp4+`pm_ur)+5IZ0K*Etc zd%3rQ$8BcPtaHMJ5}_}t(L6CrLO|E`$>bT*&jauoMF&Dy&My`=lNwE3GgMH4M9e-G z%kFR(9W)_?cA-)TBsEN~Mo0+gD##DQB@O|&O%zQo8}(5LC8WlZ@BLIzfyAfFJod8W zaD8t&?^SubR4^trUfpb|h6HqdHX%l2@N`-t<<3(mGIaDC={AU>Us%NqLZ%Wv9 zE)xcm<4AU>mk`i3!aWizEg8&z7)Xe%nM&aRsd1vmEjd&mVG9UZ3uEX;~ zup+$=b{|G}`xb1;7N(LK%W`#Ds6ZmTydRdO_~MYaG#!2P%{sw})Oc29Eg_(5{?3(hRv8#pMq(*lGXBH}u7!)tZvQNG7zBzQiR<9yW@Fg|2FY}WS&^7b1FIF-> zxO@-YMV_y*Lj2v;|J;{_3M6!bdSh>o-nfqzB_hig2?nIb-Vb3C0=kZC$gncS8@Jfg z{d4UR3xqgw9A9K%EL0$oxxfQ^&-B5KQ|bQsQ}bEkdGJWl2nhjQiHlsYqEd!mPop`2 znOCQYef!D1!&#_6qF&b-%l3NXeI+ypaCP4Vp+7l}t0_|@1azUH-LT@57tT+mc?PGH zvBC~=97{(`VW9$v=YP6l*$WwNRZ8;=7Sjg{!K6k|&KwB=U8*g%SmEJ?eMZwPdrCGo|-BlpsRbhIabc}#2!CrzQk%cC!Rmb z2QFiw0tp`%Q!I=1!tq~ezU0zDnP5qd<7B&Z2?1TE$p%=l*8^`HdXf;DY7enz;JsF{ zP=SR0TsdW{yRn{y3M8~_ z-y@lo2fnL^31RwLLwv0oTjxp$=pC`nODJNC5hf6$CC?TM$rQ!%u+;zd#=j#YjcSKR!W zrlY&~k5}1~8iAfl2?1TTLON0m?S;)#8woMTcerW^snO$V2@4fSOu4lZ$->B;jh$$s z@$(K7l_9BNlwT?#pbNJdi@ozUp1|+ z?@Qm0ylrI?0=mAP{$8Un=!qY_rrFw5dFNN69WISg1fE>fbuG_o5zn+Gbj#)z}$KcT(fq zQz-#mUR5f8#c3z(Xh3V|=+0(Vk{Z7QOIWBtVpL0&zwCH-T#`p?)J!R629g@VH;N?$ zbm7vC0SXN#yyyUZt#;oiVfK(3+HOTGR3OnlD?32;*$D^rr8PPpL=63&NA)R`5YV+d z>~nyE?S{X6pv0*v%;b?8(U}D-R3P!Z@qK{I(+S6?()av&B?NR$S>wzp z7dc|*o0M?*_)L61hK$z7pc>3Ax`>QqEnbhb!bd!XDuJsM0IK^=X zoIIWqU98)%i^y^KZO>t$0*MtzhjX%hj^xQb^gEa~*qjX|HRfAykPy&iziSnz_}mq< z>nM@On6ukSjoESQSg1ha)u}X2_R;}w=t+M|TwR^mAX1}p#99dfU2U6-IfZ9e{Bs=r zO&tHE8(T_hIPA_~p#q89hYLAbHwWz0hNh!47WQV1NeykwRT2WawkQsB%31b!)@}OR z-tyCjrPrKu6Vh0yKw_!cAtw&R{ij5epSaw7+(ila<=z+Ddwk`sYy$OM8aegd_<8UDH}W;1nGEU&gYu zXXy2F0t*#LWZM46$*jBJgh1k-D^+QQ8&U1!)b>EV$!q_UiQTT_w7!JMO({Q+B4vzgZ&@@ zU5y7Kc%{ENUNDKSEje^96~E`PF^~H~1rmqq`t#mi7Pz7tCDtaDv9xEXTc27D3Fx{s zW+1OvY=$SK%ppYgCra`CFdmj!4HZa~%n9dZiRRebnGzfKm$G!dJtEjZ1qtYC9vI0h zPMG39nbde?ijN(mMANhqwjZgXS-Vj}K-Yn< zL0iP0!FA>d6;vSccT*l&!pcKHyZYu`Mv zXV`!Io(d|Ecyb|>mz8zEOM3Sv#I<3$EKgpm9to`kNI;kBWhk$B)gI@I(@1=GZe$hY z{aCP>JarQ&knl4N=4JPdv45~LsiDtgvvi%Ive{TdK$lTc5U+4*j{{5S-o(qb>&2eI z=CQE=6-XHG=6P91WBetJ?oAXIWwI8eM)VhZ2?1RX`f%ne!UeVD2TlS`NifUt%#B-Ex#2^7Gkoecx zm6sVA;$i*ioTB;L;@B>v#_=WNB?NTMzt)3S4A;jA^Dh%(T-;>ww=}k>&Xf?)=}Z#rbr0rnmOK@S2VT5)3?w);Z>dbi#-JLVyY+j;oD$nU)^jcb@KT zYt;8*UC8^tG}H3{jcuBm{JA;r?*SH9EL!Z9E|!kFpZaQC^#}1gJp5Veb#ldxHhiM4>=M7y7sud;uL4w;=LIWgfJVZ!_pkt%ct1_R3yZMXPm4`2e)_*CB%aK58^t5 z>9x%gVh?aF47|oEwAy0(6EypACH;-K&JcJcSAYs6J_R&!vafA%@6$B<@uJ^lhF%-Y z@!TRIpsSEfE>i?(YD= z3JrWCB8?E1H$$rDk{ZJ`N(HDu!bbBfq-fd+n8L{oaX-GWuLXIOPoNbe<_g=(6u2gu!gPugXY{# zCdB$XYgI|4#<4-g0#qPja3P?^dqgX28%dAjzv>DV?HMkX6-fx_nxr~i!vy_C-44+V zmYV+5JL$3upqnD&0Wb=bRLiBO##cv+f+k9{z@|cc%9)4azhS z=$d8FwhaPQAfa89h!P5apt!W_gjllMR?IWZy^$p$pljmV9K^VMMI)^q5~5Rztw7fq zzBy(JP=SQ*(hVry^gCLu`-KqmT-^ju@>)Hm%enk^&U4`|e zMvim302N5=cixGnuK$X(Rv8dN%fesGGek{TAt9h^-FpGayL>{w!)OL_Pbw?s8IB)K z6`%r%5nMHj)A@pK9isO%oE|?=a3;qw_Qd9a8;^m9cVxGZBf4YQ#uELU+h%tMQMp@AegxHikUx*|%;&r1W1az?}I+)RYi=6MK5u)CFokkG4uNsvfD-Psm`62F1{ZHx~>9LAn~J(C60gc z8ok}LhY$hLSpvN_uvua!A)t$6?Xi6ROEh^Cy_~tqv2qQHj<`_u`=n6j812gZRqvO*r5n}F@4Z==RBMIvZP=Q21G}*(s`4Sag zY9Pe6$GHN%c7A>QrwS6#b!DSFW~Mzu$GgxBWaGI_0=;&=l=npi6-elsyW;rh7wAE6 znt}W?xj@Xr?cR7pLO@s4B_GT@eS+>NzY?NV?|d9-7}OFM>CL4 z!;1uKavbl^{v#ovYwvhDmXCXkro7Z9SE;rZg~B{?98bI}R8WCL=m}pO7yJ}$bEMa` zAF4_O+FxCbT_+)+>q`iSnOpy&XVLWfTra*@pgqHEyEQ7PK;qGQ7RT2-Mjt2B>+`$~ zN+F!oxZW^OLO|D$r9qe({1AC+(shR4E(-BIU;jQ>1rALDUc-$%%-nG$YSO9eAh z=q0*M(tgK_+khiJzhnt?P*D-(12 z4d#O-1a!?iAC8%jdnmUpUH5t3rc_8IHQa1`tDypkMK42foa+O0b_*qZOGpn&j^lyn zDL+U+*PI&zFoW)(Ng;IoD#o-_C?GY`CcO893MBO2hvE40dnk54CEOj#1llvC^{$me z0=l~OjKp%k+sL?#W*|RjD}}wJ#<<{Xa;QM!D;t61?C+u_w<+OdSt{6)8eEkp0}1FN zvmP*W^d<_|rK^M5ixpx<_|^_ZwAsLU^xR7NVu1i8s@i9*$_%}>|ZR%$Z>SF+#w;LD|P=sEZ=<%`P@t< zgi={3WRurw&g`8GR3Kq;IvmGk-#~*iDPg+yZwB&&-9-rjU2eJqu)OP4q-#o7ojvdV z%|Ko@sb!!7i3-Os9M|qTLKQRv`B9!H=G*({zLgNrb)q~BGbK%EX)n5Z-X|+ppx5V5 zI=^F}0*N{@M>u}vRdoH0=k-2fmohXkBqJ8{>O}wSz_96jLO@q)Uw`bUcNv8}qPsFp%T|lukJzm}S*SqbYK9z7 z{ZNmNM^a)^eIy6d9 zQNIUtLhQG%e>0FZ8^R<6bdBpN!)(h%w6}%sl1(lDn}Hm0r9TT5NGMA^aQw46^t&G= zs;AEqx{~A2VMa&@=n5%!!SaO{(4+ozfAC3<>0;J-_`u;TR3OpX#~H_suSGVCcM(Ep z8!MhazIB-@A)ss1>TcNY<2m$o9Np(EygF8(*PM?urm#?fgsz(do_hNNy1tC=bL!U) z76Qm|Oz@v0A)sr*Wn0Y5IExIKe+aRCPn1whYBan}VxavKqa{EMQw{0D#DW1=lHE)s-(3PJ1 z5V7?Fvfpu*5S_AG#5I7*s~cIUK;m7(J(O^)1~tUfTt>v}hbr2a=&#F{5YW{eQj6sM zRcQVVnlEwqb4Nw{l6$dvEL0%jG3`8xb62CH?DK?pGqPI6kQ(R93MB+|o#Kunzf&iX zc4ZwQ?2lEdwvZZOlee%?fyBxq|DdURRH$|`&B?Sf%vaItC=KTl2?1SQ^At$VRH4=! z&HMa3woyfUh9UQgSg1ha=*VIe=TME5&u9knmP@Rv6R9!JNhu+q%XGwQ#8w_hSCbkE z@xJRw)dEtZ=0XVz6-cDbUWF1ARcK{)N$wCDZMmD2SyyXd``j`G!Gw0`2)8BS>BFPSdzXe^LHQkWk-lNF*9LUgrCtoT>2a* zKe{(@lVl*F0*T)0Z#8jbpyGr%^uJQJ-Y%#28=N0hCLy3}v6wU-@+GEUtz3pH?a%pv(5ZTWZGY5Zc!61|eK?x-#*k zMp&bQg$g84^L2H6tHa14m)3}yJdL4yEV=Kc1azHVd)VJ^(*ZQdkk-gBo5j#v#<|E6 z7Alaa_;k>JYRVyWCZE>$cUBQ2uERYmmJraj!aO^GF{nURhv{oI^>HynzaR71A{Hu; z_}OlKK>Viz=sH7dTt+nvy)V@%yih_wm*bfa0nF-s=<_#97}O(%u3rTz3RtK>VoA)q zfcTjeC_0_K=K--#8JaKYYM(D5perV=t(0b&=#lP5x z9?qtp-FB;6vyP<3_{2>T0=jJSMso5+yAeB$68(3!W>d*=tPpZos6b-bvSD0Y>|WHQ z(L{*JkIclq?bdxZNC@aE(_h6gpLd~2h4fq7aLG*E+y1?39Sapm$e86P=Q2zO*=l$Q;Et`Xb*R4(Mr~z)cDZbS3*G7 zo+2}zA$KX5c~=smYEK$lL~1m%J}guq5xLBSkKb2<7I@I^wf@{1G2?YWzlVf?uEp;= z@pAt{G@_99)Ked3h#9ZnF5OtDKtjK=BOlkT7>(LRd+JYx>sh)+cEQ(DLO_?{F-M*` zQGm`OdTsEjWF1S_$Ycx5Sg1fEZ(LVCzO)eadq}Sh{(ZJl%oJ^WrY#|$tL9NJp7F{@ zZw}CF9KUNh>MY$j8 zHD~PJzZuB8oe={SNbrIWA7_|{Y{DtAFt=FzZ6BUrDj}e2brH)mWgAgtG`)YYLwJ$+ z+wRk=n1Ko;e473E_>5e%L7>;p?ne~jZ{oY1b0q|H<@XBYnT|QgHH)qR%pF<6(w-qX zX$AuoNYqXX;Nvwnp$GbuXdh6@x{>$8Zf*|=0bM=xLU{S+4QTw1EJ6g{P_UV##^{$_ z7^pyEio72mw>Srl_M^lt-!d`pbJXvI91_sA$t#SP8?8rG+vu81zf(#v?^BqzR}K|O zB#a2*r+(RhCPq?1n`9u}$#J~ENq&%kE`DnS&#cKp`?BcT(l6Um@i=N`wD*GwB%;;* z`S`^3XwX1P?3r68=1ate_o@2a2ziLhItd|bpT6r0|g5S?e|isy6u zqD4YLm(!n6-tWP3wCEOHp(0ujo#L^RD&e@n__oxVgpxR3M>7Jee-O0xfNz8OX6GGsW}A{WbOy z0=nkJa{S_*OHiec2_eV>=EU>Int}ELR3Oo*oaGa$QqhKBQ$oxuO&2q%W8Qm82+7ja5_bbIr>ev;Y0#qPz<$*8X7`GIqw9+NS3%%vyeJtB5cnJYr^E7+& z^VZKt+Xj9oJ6LD&Qt>{Px%#{S6-Z>yknxvCFG5bnzX$Z$I#2fdCaqY>2buFZY^-CjFop$o2gg@f_9CE=@u}*La-{{KDusWM-U42=}1A z;yLQN{|W&rka+XWh;Ou;js)LALKyXN5$|JZyEsEaK-bofI{f0`$!MPKDniVQ?kV2K z;$OI0fC?l&wQkE_F^xz4SJDjRs9YQIH!*tpItc+?1MwejVc%H9ZKCr!_MJ zSprlbq0{px*QhfE^=zaWNW1BJVkY&$^&ANSU44$c;^w=KL)8HT2(cUKh?&&ni`fEH zATfB-bFThZESmpI9GrQ zB;H+Y;u`)Nhl=Cng!sp;UR*EvV7o;^K$lmy8g7yK2$W}NO^7p-E;2NeTG^a0Km`&H zXQ{X=PevotSq_8{RpfrM4xWUi@Z zFuGQKlMoLw9?JWW<4DvglMv9QeK(j}^eh6kDyJF9hjW|cG?N;*St&pT5{3HxxTYgf z=)pvR5Dm-q; z8R9zi$ktKa4|Km`)~8NZsQ1stmFNsq(&^nMk+ zcGfvuBq5;dus5z*sOXFEB$|O#wK<@o*UnRQ3I(V@V$5Q7O~Z5+b=*ZWkVZvK;%l}4 zN`Zucu3KCOl(Mlmdh|G(5QFDkQPF%!i$lHu6-d;2wMR|k{m`rT^wU26+!qzimlRIj zEFqw)|6?94OqZe8a+-l8ExedXO*pnmfC?l|ItHM|A$`y)4Vr;$F*gwCnoNCSj)Z_N z-KHdzlH!5ROi&Zzv5kS4FF7b|5TF8yWm9LNrciIx=KM86So;2*1NrJpmV|(=&F{0( z!kMn#k?;AcH+B*Nx(4Xk;{}=~$lR1>AW!;dinBi}Y-|OnK;k3rj2jxv z(PW{T5XC~a`2E=V%t%5&SJ3Jnc>Wh7l>NMp5Q{1|h~JOzUIqeGAn~%R6RtmPf}+D{ z2J&-luK2mSS@cT<3FtaL)g7n2(noCmzl7L!aFduXF<$Uh1rO5T~R>=5{FD=_{u&5WPOKTLADAm z66ihdht!891a$eba=frf8!c68ldII}Z3=~@uENts2WJI{IhLvl5zt^dmhF-P>Ls@x2-Qi1Lj9i8~EA5sc5szVM~y{Fwhyb>@LkeQy{~l0rm; zN~NTfv4$*jXU-KOl1j9SwC{V`Q+ARiOBf_dw3kxm&dfop7HO6CP5ZunTYl$UfBfG2 z_w#(bvzU9|d(U&XwPW98`+6A7UgBje8>oqITmp#q6( zSN!pw+*iujdP?LGg3d^(*Iq9mpo_@~#2=rwD8u^CB81E1e17(4wf815R3Ndr!XFoJ zeyR-ZK#AEC@;SQBdA8SX0RdgNkB`Ey8*VBG#VsR*&Y_(gUFWP{T`GnOBmx>n;+=Eu zE9bgX;%PxHH=fiuCps@6plfz_GROPaRb|#KnrQ6tA)7lwYQ*)wAchJgPCEGE;-K5g zSz(mWA4evOlN!e>-wFum+Iz|m|IELjJluiaJ)cpM$>-bOy1f%a1rlo>_~5;+*Oa+O zXa+K9b2`_F)JTqV>sSz-k@w( zOf!&4gSYX|O(Zm%3JB;La>oFQFYU)vnWxXZ#eh%d7^D_km zbd5b}iNAa)R7xatowL{`jL*ZB?VG_s1rnxV7PusBk1{iiu5%6PP`0s^|; zx9@|$wB#xun$h)IMk|P)ZMY$SDFYQq#MSr0B~iu75^K6%n>56W@2M3%HVFvm$~oN~ zf2qq*nkncivVC8c&p_5zZD61RiLUvExFkGJIYfS(5c{_{@y|_6tBny5&=vWr3;tM{ zqWsyLuAirFci`x=mR;X&WuO9y1Fy7kk?#(rxC>oBAMZARe{Le>eWHMXuD#hE@R!Uu zgL>e`OKu3dFHoq-A@hJ3oGDpA{_G#*ED8U61(;-9rNSd}dxpv&^+Mb-1%xynto zGy^Gm*}~^6rv+p&P=Um}@8?urO;O4>>&_A4tiPOp)^b|vP5}X38P|`iTIWnvI+ivN zLci=J|Ey)`*jxrGkYK%zs(eZoC|$?VoXkm$9R69$F1-o_1avi77pfk*j!`bLr+J^^ z$_)Nl%ZAH&3{)WD^*&!ET^6qNx10e>%i)uaLj zDv;>YS|&mLb(FVqXpLP_JNbLz7ry2T2S zSfUkd$`yb0 zGxj<&G_(CUEJi>;SJ>(!=oHxzch?_!Y~LVqX6U_e^CMdrs6b-w=Yz;4V3J}^1wFQx zd3rN+K11Z>jRFF?wroF#dIa9(l$JCDnYY7>p*_Q?&g&ScK;qizdZd*ySaIe)Jx4Y5 z59Vh&)^xD8+Qwo19k+m8zP=SQVwm*B}$Y9B|c9h6(Naxqj4JK#`2{E3fUduvz1jX9)uk&N=)H{SD>8njQ_q|I z#ZZAn>VV;FeC`X$vv5j`bSh@(b4As*at}yAR}XVP*2Gy)y5b_eC!?!qn*+IS;Xx0m zK*Gt#hh6&fpCol9CDeb5zMw>_8CKmxjg{712-Sp%hUy;cz7%B!{vYUiN=IExqQ#CEBS;RDv;=T&yTI{Q^n7?%4i!iww2ojE{=w35Hz;A6n8|-u`_=vl z2D)wRumv{l#VpaO~DW|oy#E|6-N^(DldyKQqI|Mci5AfT(`CdBF_ zZIvF3?nVgd;6%P>`1{bDg9;?P>KV4WB}%$H&5#fdwQX}C^%EQf1auwP=fQRRk>YUy#J$#?8 zU=7#Z$oCA#%Mk|^NQ7q$WvBE`kctXv2J-jlm3-z-H#|r{K-aK;c5K(CENPbjx)OJ@ z<1)TySer7Mg9;=xHrcS#oKvOYlU@)aZ0sEV`s3Xhl4t~f3%b5K3}$s+=S%lTUm?V5 z$ zaU4`2F&1gEQx5ExPJ2f)kZJAB7@G0A5|APwpi9g1AJU$FT6%2KJW34f!RPcZj8EpE z0*Nns|3j0`9F<8qfUa?iGLYWgJJRCAPe?+jE^M=yX71)T<#SMh#I%WND2%x# zy_xxw5T6H0#58la>t3ONfG+2^t5CPk52TAMY6-D^zKfX7bo#NHWFUb8iK_X_QRMjB zQnU4U2=PASsR!*DHnt}jNI*bWoXH5JtNmPBFq}@t%U^ZVgJ$k3$@C$pK%#WG7n-{8 zzSQS0%|M2&=;lH1GhEIr77)<2Dajah?*B%*P?SN4ntosSIgtIx`ZH7@aebx{nh^U` z>g2eO5C?ag*U-$JU0Sh#fUc9z8l<}JpQMRzXa-W%XBa;RQm11P2Ng)j|DKnI$zDmP zSmhDo`IA@t9LU)}g#>gdKa7VSqt(+s5Nov-}b#J`O@ z1O#+hmmyW$3~h90_ijSm3jD^;XSjPggM$hrjNdXU+iXpgGw(DZ*6-5c=-xGT$!P)t zx_;&^QEgqKi^L`L7bP#(;jTsbY6`dMMwS+@tHfL#jzYzAhAKCRON8GD`Ge65JKIL;WKx(mC*tMy0%}DtKxPT zqtSh72GXxe%&(tMZnv3(3M7{GKdrJm+YN;$nG<4sK>(iv_z@K)AfRjF_9j(KaS!y? zjAkGUZu;|?JB9U14l0m1z2mA%)YKi#b)XqYJKM?p`uVu(`2qsEwxqVIV)vV&!U|tP zxHwPZXFA11&EcQ|iQc}?RJIRHk?*vzgwT35hkqX@2Zad;=$d%yw<@8!FKRq6lMr_m zv-v!O>(5CXR3Kq9-^ z=FaC}00$LF)EIUodoJ}sr2mT2LP3_?^?Y~twL zkDmn$2Ng(sGtt8$_5R4RBh5f&wQtKn?mllXAfRhaohgpJXN8s@FC~Qjz_vM%+gfbM zjNkt&kocQxjBWKS(Z14hLi}ft%;#icCYTEd=o)m}9LK-3MmjrA5@MZs5}&#I7T24D z3MAsT_rdl(tq_TU5h8upc7CRlJfy3DfUfT$Ryg*nEy|Ce8OWb`ZF3;KcX#2S0tv6L zme^*XHDb=vE0#V-GWoq4uO$7ELjt;rJnV3+nj^ZZLo<+p*%^GE!DZ$TIaDB_s}f-w zM_cs3l3saja?jz9SNcot2ngs(+USg9yEr4u&EE*Is!Q7($m40(K#{>j)754PN@use*EVv`NO5Oh@m!Ik6+~bfODv)qq;fn1>I-wsK zGz0ln);0%n!4oF$C5W9xGmwEj+U7tGy5=W`3M6iAVX^IWcQkek%|LpdZJPrb zcUwb1K-ak&BXF#kMO_1F2J&r4A)leQaQ{;S6-X>r^Tsxd7&JtWW+2Pg7xS|wQ@%zB z29(G27kt0Ioh7Fl-5Mhpq)+I2b* z$1WI!+-K56WK@0s^}J%17b&m~rT<(=tNz zRqo`E?JpB|i=hIE;Oir?-HFj?^6RC9NI%>*2hx){Cm^6Jw0{7OPYXfDaWv8RP(6q5 z848`xi=hIED2X4ouN{lDyHX;3UKU69c6w0rMnFK<(gr^qTQCv5YoUq8&!;l^J*3YB zycI(Q5*wmKtNZCnhlPBHXl`J(AB;pP4hWA)5$k8gn?V)k^KjaW zX9@`D`q*lT6Fx3NNxkW+WtW67J`Wc)W(ETlNNA>7V2A$mQHZ3H5Wb1yI64<+VD2&j z0bSuPeQ?~LrD*S8y83!|b`YO+)*4CXKmr94?mv5BTiZqGZWl^)+uoLe?A*FhKtR{w z&)soM=aneBn64sQY;DUxZl1e=feIwTnhdeXeJT1?c$^S^Z=Lw#?ySNX0Rdef2I%5A zqbSr{ovxop|8(GwyOrm+GEjlU^Z`29&SwQW^Rb!`<$DJ3IsNyS69oixMc(Xy6V2D5 zjeTei;MnDUd``d9lLQ7TkXZ7)J$4wo8U_866XLhhkfVECK`KI$CZ3&P=KhJb*snzBbK+-o!XwfYPp zLMz*G3(5cP;$7(sR3PCUbzdc#w*h%GG?#Jh+++S;M*g&H0Rdf3kqxT4UD0U$L7IV7 zyLFG>(_)5876TPXL~S{*$~nCW+0Qvgh~9{k)BoMrbvp$FboH54rK%sa6-~>aIhn;d z)pGib$!BRU0~JWfZyZ(SqG(jcUL?eiUpaERZ=Z9Q0s#SC8*2+ybq8Y56?2;RxmTDe zr}Ok9m3a(QAkny`K$UZL3u-xgi4YgM&6Lv_rk47J0s^{1Q{q*4XdJQ*Z6rjn+Eh7x zF4g^50Rt6CEG&*yWd)FlZ{O&v!WpJ=`rHJ!sYpOTm&LJgRqd&GR5g_5uUdx~$mvX{ zKZb=2R3I@eZmKG~DHiE9&{tJEGq;A$bh1a$4Fktwl%3W_SCxxTzzWghg{zUF)( z0~JVkZpl|>%}7E~L+GpeYSmM$OKO~0Lv~;Ue+#;91-?_%UfYHqYu+NnpYlFp`fT5z zM=}N~kocnZT9N%a87)YoHHNO6C8jf-Hg+l$5YTncs!&oNx*aX=N^2Z;o+qX=ox08{ zV4wntG57K%xl6X8hq<(dGCG%^1No?fkbth-_DRy(`)R1hDf(Hh`EMsbpFw|09s?Cf z%;=mT&HkQ>xZ$+MviqEv&UC6>yi-6xm-DH&QXH9qE_R?bK7LT}@58Vzhk*(tl#^dc zv)8AgKWX$k{~r5<&!lz;%oY&PRdsP7s(qD-96nNlb$H6JO|&cA!9WEPk@E+jZ1oJ} z6HWhjIhi^?(`oeX3;_XMvc93HZgCdcwTTktN_Bpw(}quJ3{)WT&>$G)BxItO-LDbC zP|b*+CsVyNRX{-3wp&|J?YC^S{22Y!TD>>qXF8QDQy8c~;=!-YC|fTJ4GW?f$T6=5 z@N2?BrxOJPbQKTCL$&L2(Z3XW^jW=o06)_yQ!9ai3M6iS$wApWvQdl!%|JFwoca9K z!7VWY0=oJz11WnL&d+pO+hIBb6-bQDeuuL67a;Aqw9B{wqqfBdzfY*r`=e`&nX(U##cZMr^I+ZggqU z2|`?s*uu|rYVG2}Km`(Vy>4u_WEYw|k9MzPV-on^;Hjrp0s^|?p&*Nv_C)yW_k)g z|H^25cLpku$ot!m%^q2bX7r_369*2b^D~`-E@=t~=$a63&DNgWi!Oeq*Ej~nZF3+q ztuz>@K*G~>Fq_?2hKl-A;`y2^KC2ef>x~!^(B;<0k*#I+A-zL11DQ8|2cK12Ve&!@ z6-Z3Zv17AC_M)es=+&KDO^XI**~xZ98<6iJ=0CnHBDA z_VfxgsS_n0<+aU${E-wTAfW607nZHPdH`L&olb}^Y}*{j_JK>pP=Umop;9*cWhGi1 zN{OMiMg084U6CRI0bOTXyxF?(hfvCAnrO5*+cpQX{nY_ts6e7pcQ~7~=m1(2Pl+VE zVm@CYwp4jQ0=kNH{n)ye!^rY;EFnrxwatN4O*-lU6-fLY>ci%KJ%|n$P-0RMc@Bqs zA6JZX-5~*8_hbFp+K8j5>phxid_(4h)86Me8|V%dNCfTnWwX~FM(G8V$XVMq2eNAH zoEk_#SE%PGwzl;c%3iR75bEy=nOUU9?cN(}paO~bi6hx;wWH{74@x{yk?G~+eQX-5 zDTf4f^&@*n*Db6A*+C)`~0ZCN)-^$Wz;#4 ztzA=%4!h7q~a8$+nq+K zcWI(=>)N(Ckkj+4C4vZaA@PIA%xif zv26~dV)&mT4i%moB=O)o$!Hc_GTm%9<7$+v9|@mY;*(=bx7DGbBK9A*}&3IzwARwSCy}^U6lVMagsS_d2xWw>1L;PWT4l0m1 z*Uyd3IiW=R(rE@#6uOzAd!;l!K>`B0dIh?$^%iv~Y2h!jf^}`;MuzT{f`%gwDv;<` zJ(SHAW7JOchY*RoR`Ro>`>O>B2XGy|U5V=+x{U7`T5LvhP=UnPavL_M zwiXpUr5Q-4eRKHy?y31<0s^{X!w0k2?kpZPTYb7Rns+jkpZytgejW!ENX$6Ym(96y1}#2J zGm!d|0vP&C+`)rU0s^|$sP|;+JuV=_j&z0hu%{p2Glcl9=AZ(Jf+iC-H~1VH_ljm9 z0}UAd9JNdtEg+yvXPp6ChcBWI-)RQ&u)aH=NnQPIGY1t&e4pEu&AEFXE$)>^i0!&I ze9v&$C0;;4*Y5?LSv=wrGXF+1keLIA@I8Z0NE`7-?PFmqF14bgfL!e#$QKq zHYoxEx|Y}eL$#MLqg;#mgc!4@2Y(&qH7uEf3M94-{Ef27q@KHzR}kX(XdV7st6!8R zAfW4s@-?a-cNOhBGKvs8cX#H`wTCmeb5McA&;_k1cfn<36C6qiuL)oIIgq_mb_fXQ zI{y47s%>sW`bTI6GSu?}f1ly57j=u zjzZ>X5F#OI4}YH_C1ocE6-Zo5+KaOPT|OUb`yoDUECZEaYpaO{K7p@&5<~O)aZJ>P(VP}L5nC<`{@=s z{eos7Z%lI)my;S77RflMK;mNI3Y4vV6YUi@6Jo&1Ru4Kmy6>MN0RdeG!M><=^&Rx} zG0i|e9^LGbMQZF$Eaad9iRyLUC_C*In!b}xtrJb{$X4l0nS+tnRq8{a{=oMs>$3ruThj!c8pfCO}1n}0=Gx3z_AJh_z+ zFNWCF&}ZDOIu&tHfyBvm4bq%~X0$kpW*`$@Jg=ea23`IM3FtbhIYC;hbsvpiOf!%V z4}GZFO=|R>TgX8L5_3NWOSAj8Am{FM0_IeIshrM^_PH%2pliW*|E* z7%r!?qmQ}CIH*A4hFKbuU49SkPo^2jgQH{l`_wOc3km4@AsMQy?fwYOZ%;FjEx+UV z`_#5;@;RtLBB{n+nQi+3mGBc6U*D~en~?YM_IjRxfUfiba%ElqW7O`-W>Vv9zy0#% zx){D#RcrGC-Fr@VmtX9r!!08< z{(Md4paKcKb_-P5KF`qdG1m#vezF;7Kx&*Bnj#>eYmXvTReP)zX)d`(h!Q(9em%-& zOA-eaNYq!SsIo6TM-6j76T9f zKw@&{DOJv$m*~ikzJv%62k=?7E$5>I1a!5iH>qm9-=bNoZ3xl4%AcRT^Kb1+4l0mv z)xN6A4ttH z+NY}QXK&Df$}xm^wS5j}Pikz72@?>|b#2&hRo$2mD7ke8A^y&q&83hUIl+@Ts6ZmB z;)g0{-aF*)LNky@*01EhkLlJy0s^`gsWfrj?T_eaUNj-HEZXKk=ID*$paO|6w={6h z$Mldmw++ZPZBv?o+}%~i?@hkzl)oG*kVu-#;_N+tkXlEYfxMt>n>*-xSyMnjmr6MT zhbOjUQ9GJ}yb{zl2lBzTc5^ePypI{`Y{hg+OpD6r==Fz-<1PUKU8ZM7;fP)uZ1dBlgcx~sCr9_LnUz^0 zh6*Icw2Z_JgUGAOql8&~TL$t-z*zwST|Rc?JAa_TCOgwaW2|uw-!mK>aZU^sNVxiw z@BDQKwnVvs5Gm1F9G$Us^ZIK60bNG-{c!lMj%?54G|~8{F_WX$9}{Q45kmzMv)hlr z7Y}H#{URyRQh$%0bS0^-EjDU&TN7SU0u4J9?kFRZ5QUsKm`)@O)mK2sZQ*mWi$h6^?V&a ztJrX+uYiEA6D31bub?GCz1R=uMw1DUaZ1OpXFAQK1Nu)Z_v-$FBxf95addxo2R zLj?qM4Yskt5ks}vxF&1$?qM2MMRy!f6WbLU0@0bQj-jB&*J zF6{j+bQSsG3zqL0+@;mG$4`ej=gs6gU(m=136@4|ZBq!~!Y$dZ4i!SFz$fPgLwBMls(uE);NpgDjv z_x^m(z~KZ2Dv$_tQpXK?y6nzpGy|DrXT+Ze%Vwqu2ib&|nUX3_Ih+n7E5Q!) zb20}KD3F+|cR|$<+LfIXe2x%{oD}?i6KQkE97sSwSN+c_RfL`ayLxj2AsQ1;@%v4z zu*qeh0*Ns*kEt4Z>a+jsXilc}eJ=m(>)5~f0s^|aOe|7GTr*(%7|^^=UHT6G+1EFR z^BAZ=LSiCQHQd){BTv%wj;s1?J};`?kxWAbe+#;N1}3N?c64LOR>*{ic@xGz`?{{Y zfPo4m99-j64aEj*yO;D;S*7;mpMC8TRU{yw>)+c5RYa&ETVzG^S55s4`Db7Ebtz<^ z0*Na%VXB76Zfsu_eN{te6x7h3p*5sfKtNX}=b(z{X~eqhr#Uj+cd0eBXZU@;kbw#$ zM!4Il8U`A&Qhqw+TJ4k7^gaX4Ktck#I=@jWS{~GZ}mcD z!+tVhY#@DA$+o6qIxk~Hbdi97u9F*oC?X<_+1VXwKC$qanV3HN`lVIIKm`)|UA`(B z7I$ZVq|zF0TW9clNMG$%C?KFK=|`p{VxS4TNT1et>^VnF_nX+hw19yMB+g$=mozvU zv$uEB8ji_1{My8mZbAaOF0GE2M!Yg%x60{f6{opVO!p>to}b4+1rj(uR@(5*nC&%! z)_C?#E*?xis|j0n3JBz^X+yOMdm@{D z=TSM2`S}c!XJiWq=(10?MB$5ju=9RWV!rnier;mz@f{3QAQ5hFfiA8yWg`;lFDlzg zof$&j2g_v$23M3}Bj71HS9_%#pYlJY_ZpZ|Z z8sAb=1q5`diMODLZ#~&r{I528qajm4KC4IfQW&T}VpGIs)X=^s`+Ew_K;GFffT7R6 zCf`jI5YRPt+fEcw-HUZDphut4ZUY$l>}&1d1O_UQs0_p;+96u!=kP47#OqIah{F?5frRX?{dP=Ullk`B5U--~U&M33#WS9>vZM!dz( zjRFF?t}Q)_A|$=pZlh=hvfZWO{2JuD(sc|}ATelH9cmb9#wKadvzpQLaSYwBDC5*J z0RdeR_gYXy`#x;=Aew=+jTy`AA@9R**yDWM z0=hKUe?$@Web@m?cKAfTRGLk1xXP^R!GQ)SMp|Ll+ax?8R^g1l&=lfV?hYASj z3N38UhR641C%vP+&)nLD481Svzc`qI3M4k4ZO2~B?8ACqq#4M4G3yvQFQdbKUjYGK zyE^Hx5hKmnlMiVIGR$Z#KV$3UMQ;Wwkhrd^#WsZWWl;m|`rdWf%Foyuv)D~QK-cw; zhHQjxKh`ki1R=_pE&M#PDJxtUs6gWWEd#cphdJvOPrFz9$OQg7?>c(0fPk)9W6aoy z#(wOG??(u+W#{ln+gc%8ryFG8=l#p z_3ckHkPp35m?fk}QC)WiDvs2|zSbUz_*Ryt!yYFMI90s^|!CRwu)Ar@?f zQ8^*@#-=g!+1I3e4F)QZ*!ISXZJ6Gl?dU}_kSnKW@!6$H8s;5Z#ZZC7*+q8jB})r-n-L`{_O@jpGb+vr2}O-X}bu0*U;7 zK5WC+f$Z(NSVF`X7W1>IHlN??4hiV`wA!Byue4&N8k-5RIk%X3J0Vi>_h2d4y1FNO zuQ(J}8tg+#)SVqUHxxfPDnn5c6ZVcq2)2Dm=Xjm$md8wBA{D*ED>RlL* zzno7cM7w(znU*twiqm(S1q5`(Y%*jo{2qsYI3^RqEvS}>-5aP-eSXYA1rkPbUG{9v zINX2gW`K6Qbv#8n4$ko4I2j_dWvMFAB^ zc>MW`+HLj6)xl1L__kkHG8>n1>RUYp1azG+Y{#zL>5EMa=?<$uEi@%92g|rVS4SwI z0*TIbpU`$+U;H+PObap4G1ZlQOtfPk)GvJ&omlf{od()HQ_fohW7NedwJlO8jlFkPkNtW zqSBba=7|cZK;r%EYV_l@JC2@JM+m!b*O?P1nwh>a0RjTLvi(k?1D}TBm7gvVV$+{` zCiHGI<7^+KfC?nS63fx*Go1B9eC%6f9;{KN_I1i z=~zBkQW|WhfC?lM{Sr{xR!2O)hl~(!2FEfpsu@YU&SnAvx@Nk>BkPV1_}E#R_Ze%t zl5u&?NbVc;Q9uO}D<&>QQ*!O`;GAqie0nvNDV#T2^8IpW0Rdg^o{P~Fe>?o8fbQs- zy?rDzDsQx8*K}@J%(E z7HNKK!319llbA)n;-CVF&j0LD-|;s1c-js^7^w7^tbX$)5w0f%1a#TB+M$XyA{HO3=ozGU9FY7Q!pcoEYX{eEVRA5~-#;_RNCOpnk=MX75g2hRZTjI;knS9X(4 zD85%sSKPgyWiTafkqWClM+5|PebDW~>LiZG3d0yepk*11)%Zxo@zqB-s6e93S({z| zE(EU~lt75H%afV2&XI~?yG{xS=yD&T$u@e8$KQ6+)MV#BNsRuuNQLkGlN?kaQ94tD zW%q?(2h9zHxPrGbhaDmn89|(YfUc*;YV4Fh!MN`Sdd87^ZDr&^k&23L90wIhcwYI7 zE{BKUfik)aI4+E0yllv;I#w$npzH4bujtUBV0_gskPwY|s~Lm9NX3T*wH#C+AzJkb znHz=RZDusf`F+?NCd?{QG5GvB0RdeV*IUtruwZ;Ak|r|;S=Kv$UPGTLx>91g4PMTnBl-i+b!NX5(TjT}@U zadXW@^xkMZz7lFlh+hp(OrA-kV$_El0s^`Q%~7Hi)O3NW~@n zI~-IX;ir8FNkW5h>Od_*^pYDehjb$qolmt02-Hqx}$KuG(&&luHC0C#6<{GK^ z;YN1)0SY9pe=bH&U&i4L4*wCtR7Z`Or4^~zkoZ7AK-b}%9VmU!SS&8OLWm)a|HR$M zulBRr0}d*Xm|v2CI;4!lQ$vEbJu0Rdejp2VUR+!$=Uj3(&phPQ~9 zI7TWmXFuYg0tuHsG3a@}aahlXzN*d*r^G|mNe%6%0s^{*{8@zd1dYLym(e7k$M%zA zOZ!O0ro~S)vDWpf44Ku)Z=wyynkzg?9HB0s^{5t#(9D7YE@6(|AJs zj13o$5=AO*&3Vp21rn&YJqo@t20LUG5F&1MH?j7o=?eS6RsjKBe|2?`Q_E<4a`_TM zNI&X|-NxZSt`%kHq$h`u#(hRj zB!r3j2#-(XD0)z%m4gZ-o~fUaT9pUkoqy<*O_M*pYM#BCu6SVbQb0i0xcN(@ul5Dv zVGsNWG5Lgb%>e62g-!QX4l0l^W)?~(>jmNWij{=;xmI1?^kTZAq(DeO*VR|Wk{G=} zd_v?&2roTN`AKr5UY7oXg9;?ND+?vxmW{@P2Q4DRg1KJu-TzHjyvY<2&=s?)o!n9} z3b!|B389J^AusL~sj%quf`bYq8t1f=XZIhCfAprQVrg}Pe8&Ch3cd5s1q5{6^&Y8o z{vCiHnT#Mrgl&?%f_&$#i=S~&frM}A2<4t>f%sAT<%C$5sFJ^KnXXvx_f$YYSLbKv zl@iwgZ0<$B^Hr5tZf6&%2zmRMg9;>ChM!d)Dj0>Wf70*#qQ(!oI;rvH_d@{zT}P|B zt2|fw<26fH6JkNr54kV-4I1Y^;GhDDeo8~tvE~3Ac8|_`?UUV|Yq&dI;r_fuKtNae zCm)sc=t!*TLWyHd-8myt<7xC=4l0lc4DnJOF($i{W|k4ccdH%eM&5^5>$ZS^uF-2& ztA>5>!)5I&39%!~j*BBT_Lbh`paO{#9xGJk;eZBNNns=t}49jhi`|flk;HNoHblAsiFP6PC!7{ zS1qg(?HPgho9Gcj<4+X#n$%byQ_DdG66Mhfl}y_Y-?(c+hy%^>97}3k`&%O*pes`I zw#xRY5AK&ekPyRCxVz;02sAj#K?M>EEnca1$Be+}+(<&?CdxPoso~*XAt0bD{!%+^ zJJlPn@SjGAYeq6Ih19TmRn9>L5`CDzsv_)zzyF&}h}moRa*m`%$Cbqb0=nvAweXNU zFFev>6CsX%+QV%jHKMJHIH(AS6P`-INrR2g9;?noAhy>hd175vx5-(6Hal1NR7SD$pQkp4le732O15> zomP|(;z)Bfx17|t@h_2s3M5X35Pnb3bLU8nP>s0)0=iTd)>xFv;(2E; z5F$_iJlB={?_RB+#X$uUBcEAe*=JAu=i?SY|+oJ zdq|BDR@ri>K;mmRUz{H`1k?xL7&QQWw^BK2})TnxTL_k1S_i*xA&2z=cOP3QuyW$CVmelyt>zEiSkmy=E z3d^pzVNWYccz=4t?ISfldA<}7(B*d|5R3K>!{_+jxop!Pa$iY}xIwSPP=Um=|NOB` z#|`^<&{-A%I`_F~QsdVqJq8lc^?7&z7Cm#pQM>5s(&F+Ku7T8ecutRj3M4AC{IJZ| z6$g%^#E*%0`0sYi3VBEb8ZitE=fM+0Jjb`0qpgs}%zkNE}_|gJrS9uw4ix zzCLT>kGnx@Jp}}GS-W~;>o8}mTSZshZkE0s^|86eBFkcf#+|=&EJ?$Oi5`si84+G6NMzB=(SCSvwb8uStmuC(dwb zq(;HsMQW_I zO%o8%rFYgAi^e+Q-6Fbvjzf-fhe?gq*S9lJfyCmqA}l*W-uXPbeja#qKPMwKUR=u) z5YRO!aS#@5cfj#uX%1lZv`Vg()L1h;kAVs#RO2nN?29AL-b8Z%UG;Z!MWjZ<`%(b` zU9XPx#iC30SSC42h)wmoxL2e`!2D7MDv*%p_Qo=MN9;WL2qD@vy?C%7DsdUNR8lY3I-~Wcxu`K%UbR5+yyjQlV`q|+e2#XG(IOFpzHRB8@@!)%g|GHggpaO})>I*7ap$(q$n&z+8U$fxi$@_RSx>-O#SH8gsm1wm!KCW?@ z5V8mTxF+&Gz6RZ4paO}12ac&^Eh1cVj;4ysmuPX7q(;l_`vL;G{xd34iH;A!Mp-mL zXD;i+eJ3@RUcARZ1ro#m6{uvUA{;-OCg{>W-Ip&THPUh)3kc{s92>6^eHx5)E?pzU z&6K-x+B1}Hc*H;j5*N8xm2A8E7$3YsU)BA`&ueJUu>3`RW<%96;5j6Mu;y_Cc z?HNYzd%-{j5; zh0$Z;m8^x#BvRwawsQgkx;)!eAscOTTxv{@?H-ba4DA_STc2g10*PGNQB=67A70c< zkL|zpwlK73IA@>|5YXi^u>pxj^u=-qdXDOIC7Pi z9lDL7J;UDClL7*|Y=1sN)-io>(@%P?-PnE`bDz|hz3l`86-dnM@BqnP_Qi&c^jy2a zBA3}qYAn`2Bp{%x{@gbt!oBgyMYLxaaVm$QJ;VE&0}NCk5wYzvl3Dh}SI5wv;rN0Q zhV~2})5-({bRC(f!P@*Y!&95^FCd`n-c4Or8ry-sq5p*=&Q?MeXw zT@OqLu{NK{ozCU-+Mr-XJySz!ta!4FfeIwdUJPIhLwez1#q`=BjJ?Fro}pvMsR9DJ zu1~aOMRq-~*=Tx=<2kE=p*@3T(8cq`Vm8WhK^_7h#>)8`S-n8k%cjS z){;(YM8*NU2*lktSG!Y4tPZG zOw=8J!v9~X`y3TR1rq&8epgm+j3pZ=ao*|$Q$}j+*4!u{pzGFWe^w+j!iO)=J1m!f zK4WOlaK|A^3>8RR_VQz8?TxY8I!a{iZDr_vhNPQ%0s^`^&kSTmcMWm0jNU2wZ@~+O z_6*utT4Ja`qVb?VTPW#{S4UF9Z_G=+XDA!g&I1zARb?8)icAc#yly!m>~&lDo?)x; zU3aKJ;?k5+tZbbT4qrow{T46zo}vFMSq&thtE281RutR~cN;dF5a{*`zGpc3x2y&# zka#^Rkd;*%;v>eCxM|+X(EAKGvKToepi9h>8tDeObQiq?>3jJ(-!trP$I77siDl8G z#q-uHu%E*Q&8dMSui!by{f48(M0MPW_DuF)Ooz zqqT{{V^syHK*B!SiVwb}Bl<>BBCO&icZQ7dX?|}B0bL^ktaw9@PU8GheL_^Ym9ja* zIPcy9R3M>fw%~(bX^UsQ>6-qE++sFoC~`2C5YUz0$$~e`)Di=;Xl){G^)ohS*zRI1 zKm`(=woc=NhiQxEmuYR{+@~kpa&jG}5A7rbbS*0~=M7tFiHl#+8q2(s`E1V6HOfwa z3M4A0B0hL|Co%3-XF?b)e#GVs7gwy75YTm^p5qN=9Yyo`>VydV^?=P8nsiqSP=SQ^ zN>e^KOiP?gdQOr~Y@IW?iUX9McTVK!$7y>?K53MAgW zH0FaVI*OVZwB~ica~k`N8okd)LO@q9m2tdbQhPCEF759lPfB5PhLVv!0#qPT89ka0 z?$uG$ok;upOj~-Fqjk>T+kzwnboq8O;0-^w6EE4)K1CDS-Qno-;8(360VkPiuL zFU~$qYpKS=L)e^QXVi5G0bPC9_vQ^ZYKm2FXiYe*>N59$jNzzvU4RNCHZIlUgA3Y; z>(A4gaD2=e_I^aZh?5Y|^(VaxZ+N4v*ceZ1+x<2NusOrz$#DWyAmNm!!v}Y2C$0#l zwQY~9$GOAg{aC#@T|z+DD35l$VUvc~aFW*OhivlV9+B%9t4I@|0*S?mZTVnpO)=vx ztg79}p{u%hYr8oiRC~K}bp5Jt%p(CRkhpI63I*TO5N~){6XJcC z6`M2oe0wS(plei*5@Z9`k1?SWQD5m_oB3zxs~l z{K@-)VxLP0=ql_dBEz!QqFo}*u%z@J&AlLFXr~klP=UmSzzh^DR~J|8>Pd*PpL?)5 zgKl`4gn+KkJ8qy+WL3Cp2<`B9Si3v>etf+AQh*91d?!Yuko|4M`WI?sjDnj#Wvj_` z$R1Ql2U+o$#wLKuN0sH32W{&3Qlb;p01^tq9 zHb@BQQgRl^@QI4p=QhoFwTZl@aHrYdnS*b&1bk1;maH9YfNc`38hC;Teir#YCUv$^NZKib2z?(En z2B@zwzwv(`W)PzHU~k1OGRCI^^#W8Naq_6Sl6(9YZ!gWD0{UrR>fcSE&$WHpmrDrf z@=cnKoi=~Nk9W{stS9oi33Q#|Vbn_jDv(&$e=0Ve{1clL(|s(x^NfVeWDNO;5(xoa z`^|S@=T~3w5nY;&4yZN~Qpgxd*~J1>An`?WJLclP;F`M3M7_!pTaW3Z`i446(OGVb`=hi>(C#RBO##cQ1*4~ zSn(12xql!;=a(+RLvkH|Gad<0fkZo>YuF^}3yyfAO76RE-Q3vwVbUgBLO|DtT^Kt# ze86wqwFt2-dJoGpM2BYzP=SQs2LYQ7_>BL|q!~!3P%m~Jw+$2$0=gP^KgZ4m|KZi& z=uV4S^SlH)_xYQjCO}0(Tr0xd#g90C?Px*-Bm}TL!-Q+`5~2~fuEf8=PS)@6A<}w~ zRQ2Zt2y~qxd{&$Q6-ZpT+>B-2K49mbGy{2kal9?(eaL z5I)(L1%ZrlYut4KDv)Tc_=8PP{)Z=-(hTHY!)vUT8j%$yA)srcMLW@1{u+N-e~=KH zBccR)zk1^qDnJDioLgH_ruhz!EIUewNdG&+3345SR+H|=;BP_K5BDyjb80iz%JnBi z?=P`J2^nMfupj{{kf=PTBg&4x!Dmv=5Tf7gRQCOtf6!M#K$n?mZ_#;t6JGM|3L&ms zO<~`UsTMv0R3M=;R!@}ud4>B&MH1q_C8Dr~T*s+?hb07b-Cs6Xbc$)f4+hZ;q+tja z;>mTqYB(rB1rmrGD4OnS#w!9+2$7TekYx?BgVst2=<4%jr06`n9xs#A45VAr1C}+o zJb$$S6-Z1^)E8wR8gcS^nt{|`_C&ZyuH&@&YzYBfubak-&R1&jX%m`({CO~6s3+GE zTxBOf1rmj)M~kv`4LErK%|Mpg6$|wDqwV!c5(2v1CruWed)HupRhogkwD6fge?R7i z8VgW?#H?InQC44vUxs`m#Q7~R1s5_#d|F=#0bTbhIMFGn3NKZ*Ay28}HkArD$QZx# zdkav3#Eqqm?`O)Uns9$LVtl zs6fK`pM@yPt-zV2rv<4fwrUdSJkc=LLP9{-BR5;o=|CwqKR`2(#~wGZ_k8&va|Ki& zvGakID4S7^&uGwfAJ=Xz8OSy!ap{nNF42FM=={9|pKnBjXkFZrfsC<_k(O zQmhk3i7(0~VIz61I+eDOK?1r~9+)jUzj=n&{h?h?Uu|v_lIZs%NK*zCNJI#BqAZ~V zTWQku#BK8%1zN9N^Jlw+fUYic$rvk(utoq)G`{>-FVK4J;2yhWP=Q3%88U{^bDX!G z5+S^c1Vw&{gNo(N$~?r}v;q1{FvcY1)XUgNm?bS4x-+uV7hD z^CB${63``EU?Vyg<>LWvboG2=cA0RGTnE>u69*MYq+FXR$}Sh+0|t~>y00YzxyV3Y zLO|F4u`@+y+dSOrG_3-()h%IjhP0Xb98@I4i5a4-$5Z^vh!XxCideRm9GEL1_5;_S zxzk0b2RXRkNm|YLFRDOLlI!SrpXZ7|3be0g%HBCgK zBmWqGUPtRV>br7S{;E}gGY1t&9GYS#nzYZuGi%NgVnO3Q!H0}7Tx+|8fUej3O-1MQ zhxo@pT95kYo-NR4gZi7>IH*7(TVskSbI-wJJZL>iUDlF;?3n5hYRP)7)G%X5(2tVQ-9GZJQH{9PwTa@ zZLSKnWQ?(f5#-d(|0|Gi$?Gf1Hr&Hnvb%&x?Qub%we1DmZ3zKgV-D+z&V5BZC!bc4 zHx``}Xl*+oDu#m!BsLuFE^>`o_*z*aAwD{uWWR$Q2PR7h=n6T}Npw1=#8XXa{d{8o z6T(e0#^T*c98@6jD5j%m>XM0brqKGicJE_sPs4R5rG$X4AG+$IW9JN9;i@D=tG7qk zYvmuD!9fKQj~rTyCND8QI)~-}HYzp?;pDX{Y`QNcfNQ1wckJY=z(S*4md#V*l7TeO5IoO8SW3ol&(4z&(Dlx@7B5s!$2Pe%mr*)=KHFn* zq9K=q3M4$vRADY(zyl-h5u&fDnLslvkIIXr1aS5Lo{OChrQ%)fXujn42pP-0o_SZu zK?VNZ*E1etQ%eQj_52|rIvpIso&a{N<7kb9fUY)!j^l;z67Z#r0z!0tQ>LKz64y1=98@6D z)zK4kNy)fqE6puwrbjF0lQDYls+SPZ)pndKc3K^e(=XHf6}}LupmT;+v+6jgK;paE z5-b~&gyUz@{FS@o3dK<}#*4s42?1R%GP~9XUpzF`&e@dr?ckor7=HVIH*A4bo6<0 z+qjE+UZYpO@pb{rK>qhjNJZuA{pcT)&>qLkjV0mGcyUejqSDQ z81{3o%A&~_LzMLr0=lNCo|ZdLy@6wN=omx8!)2ey82eV#aZrK8k?#I-S;rXMGmgGi z(|eT4Xl^O5u0}#Ym-D|ba%UkLcb!N7t1Vq#%IJPI!M&P;3M7VPf0WBSZ{jcC>3ja5 zE$IbF#uz!dQbIu21V01hJn0(#Hi`aMXBM_)d7tmamGwBvZM8Pgc>-uKwnM z+y*kn#mrI(0bQ~C79q#GkvKu1#If@OIr?nix#R^06-e|BazdtiqcOLReg_-mrrcFB z#ww3z5(2umJn=xz1`#-}k={#U%}lwsWQ^niMI2Nh5!S&S$-YG4_uZXR zJdhC3Ww9;+IrqPU-SqPa;c#^WNB7-%I^XA@0tx^2aY)u2jy2!Vd;7S|BdkWBFda(> z=*oHc7&%=C#W~aIZ&dY>BdkVW-I&2a1rlcc9wO6aSFvgr+F7+g>jZa!Tt`Fy6bS)c z=^Lt%bJq~uLHhwAZm0NhbRVS0y(A7Qka*-)fn?=jShbP<)=sH9$6X;~ys(ay5YSb{ ze??9wF5{1$bk5+p^BnhvjA8UWhJy+uxOShAY(XeaSxe^(Zw;=pyr}=vNC^R5_eh^R z=eC#du{(6`^DpKK7e&S>KN`V71rj|LsqwO>!FXU8o%^Vi-sI?hPP^<&5(2s=T+rg3 zj$FW={&Wtv(BdW+NybP$c#(q&B$VkLcvGv(_=_K%!(FILV0F$c!_yK1x<;Sv!8`vx zhhGm*Cd5Ot1di^(+Va7lg9;?BU+l)qvM=Ip6OssV)<)pyJu$Yur-XnmbIty|)6TQ_ z%mF&5)(=u}bk1=9g*yioNbLL2hc`95fE_Q;IrXk#d~OYWy0fV*;b^U>*JuL?0bTQ+nD9=EPvQJi z^qKQq({q+Dxjudb2Ng(!IZx(Ihn>M^KGA2+W6|Z@PBKQfDjf*{UFri7?_6>cSCpS4 zgidG~mqD&0Oii1E3M4!)a=a|;G)|gLiG;9fmM@7I`A!B2=<*k*@y@ePU{NiA5PQ9= z*l(0d{3{t$AaT~foR{@Fg-7|GCdBRgb(|Zyjs^8e5(2v9F_yejjxYY$){hW(uhw$e zcxeP(%jnyuk$pc7|9412K-c1GYu<6X4}SlVuCT0oThG29 zJJt8epaO}2aWi?-PA72cRZ8TBHL=gtny}6i0=ldn?09G8am?MPD@8k2HFA06I)*Ol zD1!5vzpnQLGMa?Q%FEprM5lqJjDw)cHcpWybew5{RnyW!2~Lh z=sM4qm#O*SU5~dDV)O83j@BT1>77r91aysQH=B2g_rRCDX`<1%tdXPr8Gadur9%Z0 zgRJa$)BVTsur-wEHnNGM&z#N4CJIPEmy7vq-f@&Wp7@2XK;C`cz|p<0uZEf_paO|A zN9}l%Z=SgH4<)o68`xfe1y+F)0=h1y+4D}SI3o;F)U+)6i|W0nLp&U z+Twv#11Yf}u9lukJ)F}SnHqw6-ewrjm(c=LAW;{j$M0Oc4kvcLMu^X!TQZQlLgFL@bUpaig^yRd z;$Hfv2{AA+fXx|>|GO(d1rkf!cINlYUX8oy1QDY0^>LOR#3tzy0=oFDc6?I&65Rd| z%|K2`@?!f9RF9_#P=UmiPi^^q(^ue~eQDRzYlZvRoMGzFEC~T!^K?}Bglh|Nk9wMc zEIZ`JGIxtzG6kqW!dCo?_DpfbC}1ribiOZVnbg(`9!m)5y71&Jx_ikHk20D{h&+WW z%cLe9dL%#v5|iRzpQ4CI_)Gg&5e zW787>Dv;Qa{|xOJxDeMbr9H5_WsPN-RL2v~B?NTMzbm4oV{`B!J9;X64?LP>Qhm-A z3s8Z?xS>k4x4R<_($XV@#uZ&QXXxiqCLy5fT8En`VfQS2{MKiZ#qm&dXLE)Hhh7R$ zfrLd)G;(VPz^uNR;K3H7q+ zXm5ixZgq`jAoqVCC8M>}0dt!q1a#f8)I|vvQ}M=;ZiLW1p)aGE?d8221*kyc!p!c- zt<(~KOQiqRs>cUR>9bm^NzD=hx>k0|mB*PPtaH+t5atKhnZ}VZc7AOXpaO|Oiyz5% z=S|1IFVYO;u{T^g-Iuy?db5OpE}sy4dBSK8U#y}T$c~Tf(rG5u?02I86-X#v+REKT zbFA`!W*~>m>ZhRdk}ijuBm{JYHMQXq2Akl@$ut9bvT>lIfsCjkJlV$B9l>2_U) zvrf|t@WrHt<|0Wa328OVu_848+7jnu6bpaO}w38HdO@DwaA zry0l*i$5#q8kv`IwS<7K_eNcDLaVX3VK?pMxq8eO1zjV1@T^jR3M5{j&e-krL|ntp zyq{j*mE}vi3@?`u&}F=H8cz5;3hyXANyhLR+>PZ+9%sE2paKcYS|0E57?0nW(hOwH zFe6r18*g1AA)srM#!j5jG!nmAKs%+cGc{s$wcCxw0#qQOS+EtmxsAcuE*XTVyKBMn zCHA)pBm{JIEIW-8%7)|IvyTXob;&}YIpn7nPX(wzBJhJh-m`HO&YDn32;Ds{tjCto ztQ-jeUCyJf$-nt{B%cMr=me4UUj zA)u>Iy%NV~4aDQ$(Z0^9+ASGKn`fB;3}pZG zSe7rjx;99F3M2v->xgc8J+apent=?|Yso;?J@Azf(3RP`x0tw38^7I0Gmru6Qv_No z@;l`tKm`(Jzj}&pTHW!CQ8WYjY@{gA^{c}h4oe8=8ajHg7{9qAet0E;5I*a$fXNuT z=#T&vNZ7O=DDGD6g1dL68A!SBLzXYmHC`tnple_DNb&CSc6e9HLqaT0eIU?Uk;RwQ z0#qO|%~N08^|cfBZ_FXYT9YRN&7KdBm@Ofo%SH6etN#VjM7V?S9!K$o7*WHHIQHMZIEnGp4pp0T`-^NfiCR3NcB z(pcPEp@|P@(+uQk`Uw^wk8YKtg8(5_jEG z!(BA!6a6B-r4Qtf`a%UHpvzCiLR@42Rr#T$HzD!|*9bM_{pfV^i2^E+s98Qu+>_c0 z$H&vuWROM!`~CQ!aalq@*YE%I%A>D_6C$I$j^z_OeLJs!3M71AS%}{j{!%tv zrWwe6bS6gL56cTy5(2s+*4m1z2fR}9hBO1Y6*mZU-RFYlbOlr(;gxJ9ewg}28J0we z3);;rzx%jjN;)K33vMfka7yt@t{pSUKF25;^^9gg7!r^xg&u z0bRp$?ZnlmvXv%->8f)>MwRe~T*ukvjWVb}Vs&@Y$2FRmc>N3`sQ*)aZ2Ng(MOPwkHxb#SQr!^&G*LoZ95(2u; zZ=No$u#8iVSxu`Mqw)&azKPWXcn&I%82Q3ne79auMt(d+|10%8miKu!)LBA6mv;>k z*NwTQY?wx?GEP@=Sl(y&2PY0HkZ^P|6F<*QR;Hz&C4_g6`vTo-xqCA?n;iTt=!(B$ zDz5JtrTqDaR+suFw)BCt(%!~F1rnVNrifom?9-^7g&e+dCy`2$9aE1NGU zFE`O@-@=Cp0)4Jcp61U%1rjRH48`{yB9%GAD6!NqMxf8N%V%Dc5YW};?Qn73(=*E1 zI<(F?zVxO*pKDdiE^ttR#GK@z;^)sHN;5NB=e%}0T%a}jWt$=-1auwhGeBIIazeSI zRU9GiPQS|bO*Fj;=b!?K|5W>lpUciGtNPp}#DRq^8OR4Jw80w5>%zR0%kR)C z@>kvSECU&_E{1~&B+jLE7e8kODEnk265?mrNtWw-+ngjJpiAv>CvnyBBg(G*Y5n}q zsuKczu1%FEaZrK8@>d$dkKl;-8`QcT^gtO$edOTDqA)xD)gSz;vX1DUFg_02Z zla8`|6T{bKa8QB7wX?0o&u2W92Pe}UfV1-!ws&br%6$m|T?NJ8alzg7N?|C?GZ=N< z#B#_9ukUeCfyB78U+~9$hn0`oWfEfjcL#y)wamYeCn2EgRgXIS?8s8(&qSJm{Pt)* zd#;_9p36Z65_kJlt(q#4Mo`a{`#_i3k62?1SQOq21mffma6YMOx@KVdM-Q-|uj z;GhDDfT9H4v}Un#Tm;R@^vrM1o}+B9R7eQu@|qEb^FEp=19#HA&+8wWET?}du$+Sm zBnk~eaMk45$}MAP-p3)iT0!fau^Vb61aytdIF6r*MoRs=1%wzjt3pBdT5gjUYwvR*< zZcA~~i^)m_JIDRG!DX?C?TNh>6x)O&$Wm0Xm^?;Gv4W{pnEM{9U3^OK%yfW zjjN*#l_yv#%yjn0bed- z3{ak^qIo#Ahh5X?{*O4vW(fgZ-BGaeMw`DGsj4M}hz&WHO0Of2^nrv5B)*Kjq-?V6 zrd$wCuiW={4$DBQt29dp==!YNm~khfK0{*-9b;;K5$gllKCY323M78^ugiGdQ&VZ9 zNw0jO&pfskeyl)BKv#PB4YS~Bxfv5S(J>qf9oSlU`2GeCDv;RV7;TnW_#2?1S$H=mTBs*lf*b)#b_WLIT$ujSbtbsSV6(ZDQ;DC|Qy2Ng*CZl{k*<6<-3HqmcXY4!k)_Gd7u zEtL=wmuw+2^I4GL|BwAmC*A?TdJb9xa66-Z3ldJL^>JeaXQirz~uKAX#WO>VrIFCm~S zH1{$()BTN5n?>)5`Pb*N8vSyMJPs<5XimR~0*2XTq#U64#CYco99;{ydh^cHc#*tNLbK`9tsRD!Y%co=#`?U**A&rzD-y|F1yesQ&|`XZKQ24yC_p!T0<)+Mi*LLyCleF0+~{ zG$yKAHhAa*`d@AJ<0{E@Jo%HvK?M^1Ka?ZC%iDyfzv*x7s0-&f+Mi+B@mL7~UCrrV z(2nNwT)$8{XHXk+j`c`6IsP^W6-a1!d_dS>~-#3G@ZkVHxgJ+C%^5dB?NReF6zP8jbCCG zIX#&W>ogKLx<>YM;wcU)km$dx8-Gz(-E95rBtrDkZRrD9KHpP9Kv!sCKYqziPqS9R zbWT0orKJz#&A}cVR3PD--iO~_Vr;fBh0du{DzaI>KKaUB5(2u`Bo5;(oI=gcB+_RC z+%21BqyE#~!9fKQpN|dUXPjGQwxEVS8`QkYVfREQizN~Qy0-iq#XF@Yn|(e*Gmzoh zIqaS|u@HMM>N}q8)PAKFKkk9VbjngFrbZxvgf#0n6)T|9hGmvIh z1+0F)ZQfK4Dv+3MF^;$L4L5t@OP}r>a7zZVywpHKKv&NjCcKMVvsr8+%|L40f6nUX zQ57RNs6e9Hm}DT|q?`ThK?y_Oa*oa!zW8^R5YRPW1@S}Qwvrd9ohL-kqh+jqek)F! zg9;?3?dAAk*3ZmF?mkC|)!xyrz>4=1YuBbd~e&qfk^0B=<3DLf~p53p$K0hFX3MBLf z%;a^AJIQxErfV{jFEnvtjm^no0s-NO_T(DhHlo;N6$$u-yPAjFjRq~jvFmmHnb+7v2~u(!75 zdj<`Yd-kTpXoF@pX9(XBkPZpxGEkq*kLx&7KCpZ>Ax2a+vN?nAg^TG>fyBrecKm>Q z2DNc7LL;dSRb$ZvX6B62}X zAIMePTM3YWF2B9D{ODXa`6_yFO=ghV>c~ zBm{IV;w|{ILC57aE;P}o75j{R2W6cn2vC89M))+ocf&sU(MkOX(J=KXn=|}9WGf+{ zYmW9*J}~B_e9^Jagb1q2XLAO17h3@;kWfE__zphDA%r~T5l4G+pkb>d1azGr zVaA`m7bsuxMx78|^B=OzU3%F{0Vq>kQYH$4Ln2av9r|zc4>le(W;MK*s%U z=>ut;eOG`ABqs2kd7W{$<=>Ce4CLgA-W**o8Bfv)kbo{*)%JYgMy0&V+QWpfQa#SD zF02N47j@INmSS83id(#Z$?)Cdw=I-0)ObG#9ezR5hGamQkV`5zi(XHoR)(6sK zbfy3mNVGHmhuSYolgB%+BgB-z6|5(D<%q`;0=gbKyhG=MbL1Ca(G28lr)3=N>C|f8 zBLOOq2%GX6b=r&Ni9cut^2BRv)(6ry>Zyc)u2o}Spi7Ab^8bSM31RejCc9tVQ$7)( z0*QZ4#i(<@eYun7IC>ph#&I-rH)!p12?1TnQ!`OeUWwehiDn>oY#77Qo=)lHbVR5? z;zptpb-a-)4~?K1$aA(mSszIK)nyU_x^`c^i2@obM?Y$Y^b1>d{I8Dv+4F zDFAhP`9dCcr9_lBko2k!8b(VB3|qIv-;knq&C zKw9k^&jBZPI)7}f`Ju@&iE1O66t6`$ydPEY?PKhSG0A-b$IkkNI9#xA4>4^SX+ z$fpO=9P~!M(J+7zU)#8w(#+kz@y!wfx=x1V%g-(TBexy9kPsHJJ6RvdJD(Z_s6ZmQ zK1Z%)@=@;nhGrns&yP!|nY#juW(fgZ53}dXZPQdx!vsr0*mzD&rA9moZ@3%(&9L+$wtM*sWo=y(NjRI64F>vZU zPHWv?`7l$Ofn56Bo%MkX_|PCBpsOj-<0V z%X`ucq^R;q;X_`lskYS;0=h^iYi#?vBeDyiukL{7pA^O9I)dJkv#@~ziLzEY*xRu! zI=hv2b?iH#E2~YMoK`L&psR8GRBZiC8!eI34CLM&U0KF!Zp}*pDv&UUMA&mhd-Nha zoDetMMhTn9b^O{`A|aql_INwCRO^Z=f}#mA+kX^0_tmZ4a{(%l_;G#*$*u~7 zxZTNu)oaHV6-Wr^(nY7SRi_^4$(@IUxcYO3K%b*xHa!)f0*Ocmf9$zm2R)o!L5RyQ zm$J-V*4`Wm0bTlMqp@u-J#=-`e}vGwv{ZOR#yF_^Sbz#7B9}(t%XNI0eb#>Z3oqo*+|2{A4viq%s6EUrih z=(_q?Q?y#3kFKTdC&b8MQLL7__HU>F6-adZry+V|4?>RWGy{33=N*=1 zkJv&`KQpF;vvAThjTfauk1fI0-ynZE1dhpeZQTj4qh0bM5oM~YUS6OeK& z%|L$ieZVqzny1$YP=Um^1^S}L7bA4Zl|HeY)PBP944lmz2?1SaPK_0<0w$qWgJ=eF z@~C`v_Hx4zdjTqt2sIrodZ>&;Pdd;iuU;B0eIOVAoGc-rE2VjoXcc0DI#qol#PzPv zSf0V~zlj1=AmMY+SoG*_V9gPvvApu?LZnH$IMjQ0ToF*Dq_jgYE41e? zC5m#J1lrSS#gJAqNI;jv-r1tnzd2~E15GpzJJ2X3lIy5%R+B*m5?9jfM2`)&$YdoY zOg6Ukfm~+0NkTx^DK%%NUF16Ux+4hzU88-ci`Fw% zA`a1NhS}!=meb$57jaO5#LCa+qF2IFG$Ds(AX7)@v7CM{4<`u$U9qaXXgg;OD(^?D zGAg+_ET@0e-id<>B)YhniO2CWH0JDCLfoHzUpP*#qj&LE2?1R;_f18sCF{|L5}IhV ztj`t-$@`(YYbys8NQ{{^MfAvBi3Z3hQ5wWY>Z_oC$iw2HiZ(Rp?*@)4sL4l0ni z^QpV&uDKH(2}>lzg;ys9dhSw(J4q4(x*RmLMeC>os8o&C&z)jVu=AbgwoT%o0*UuS zv_#Ktdr)6BT0id-cZ|)i{61z#2T(Hwx=(W7jBwRBte_M>VK{P^7ZO&|ywA(Xb}Sbjp*i$xBYIiK9F1I z)k_HIa=*PCM;`S-+dXOi>i+6z1=}|9Z@6$2^B~LrUxrUYd^FnlwSE*)B7v~*-E`xLO|F2wI4Df zFP=hYSJCXCevf=py0>paaw7*7NIbWDmyua;0@Y}+E5ANlM&~~7NoOEPKv(4KFtexu z0chH0I>z>|^JKKA(-f};4l0l+*AFqvcJN2rwdoi$kA%vulQG^_)=LQJ>Q(M5kBB;h z?(5PqmK2A{XwNeb_c{(LkT|dEBhRcjh4fPCYvnuZxr~0Urgy595YRQZ_Psp9FbL_m z(Em!+zC=cQI<1Z+owI-f3FFkaa&dV8dfb|hQ5>z#YDJD4D=BqPk><9YU_3bf$)gWbRFC_$YmHl->QIpRjmm*5^Fdx8b zkds4Ra8Q9naDNAswK)h~IYhsMY9FSso=$pCo=FJknts+DMPyt+y;Ujkmk_j%@Va$H z98@52;`vdO`SmQ?9#8Kj!4u}PzBnGw@+AaxIgG!IBBotJ3ybJIvA+FW))!~{mOKtB zkO&)c5oPW>kN)}5d!nhZp7nHktoKktK-UL(Jc@XH8EM$i3}n-q^{fY7X!3myDv;<` ze+Ol%UPS#m(O-k7BaX0hM%UcJ5(2usu02AL_8~~Qk!BzRULR)XjJnz?IjBHFRC$21 zye^?FQ|Pan+I~Nl{aEUsA|as5dwvy)cpi$(O&<{A!*D;A{ZLFu=AZ%z@^7O|tzZ<{ znf^NK%sR5(2v7jlZCXMOP4)Oy>-1-kxO{mW7*cb5MaqfXYXdc_suczmQ3Y zU(r|CIis`fqa*}$#a(F4N7PFK8>1ax@}?!ia=7m3y`PbS3H+<5lcZ8~_0g9;=Lj_Af`4h=`So015zyjH>b+?9uV zN(ks`y4;VC+;I)9P{a|U(Llk`{m#obdT>yIgqLR@KI>)#awws*@b#QsTIf!}>s8d$mMDK-Zb4 zqxi_fH_)sknt@EKd(3*VW+g1*paKa;F9SX^`5Josi9X|)Y87&x2lBG#NDeBH=-zoUpOtk3Ei|P>m(ArIJ=^JwNf!wLU0bT;d_Ae8*cER3P!+XP(b|5`&6L=&Av_TF31r*ReA`RYE`)I%vs9_KQQq9sCH< zaIluUN8XRk`;%l)fyDi?>3r7wSmgJKu6`U=YvkyhAvXJ{gn+L9(yaNYhNyborRt@ev~v(OuocgqZEv z(g*UnW)~S$AW`9A&1bsCA?vH($}b%f(A8CSHXk`L1w|XJA;gQ?mOhZ_MS4eIQq~F;+kVx`t1i%||Ix(e?{8(P;Flf%Sn*+dEkS6-dl= zx8t)vC!zmBDWSKsft~L>W7la30bShELAN7`ke_%0I(alKO|>jQahU@HMCkjQ#&#bqvW%cm^G!0 z^?|%;t0y6#EB%QTAN5R$TprO3jSCg zGG0PJ*8yh>KGIo4mttuKvR~mdj%HYz*N+#V0*RvZX?*7C3^c)`A0hf|Xz2qvqQXW( zKv$PhQ~B`fOtj}|XF@z^`-Js%o{?cAKm`)ZE+byl#pq#P7eZ(>KH?UU@424GN(lj7 z@2$=F$W__MW1&Y&0SEg_&Q=Eyic za@zxB^NUvEkW(s~Gc2_C7N7zNtUrd&x}J^XB{Ty$Q9F*!cduUwln~IhCcT?`L&#tjqpa2y}c({z<#nJasSlcIr=#>-A<_xM`LL~%ry&5uvk39GoWnHJ0 z(ZTz!u{i^GF+_k0BsBjFlQ5+9@1-)IK%!9i~}XZW3$CLy59MBbi{(0PJRJ*OGSpqa1*kxx{S1u6rG+R| z){79We)nYW#}~^o2?1TdC*ML5x1Xc)IbTT@r=goJdq3VxeJMZ%5;wnGN0|*pXu-NR zWQ+|(e`Rz}wf+7o2?1Tx{|2I{2`^9&ttVs*GmBp`dglF=-$SwcY96r%!pq)jDiD5e?6+r18((jG!P-Zu(RfkeUje0kQfGIH?bF+#j= zKRlhD8(u!MSwcXU$~$LyL}3*g;X^Z!MVgb-Y47NsDop}ZAd#%$D9>zHf#z?fDZQgX zJry+fy6L2pfG(4OKrX_u231X?8AzFN9|hfC+n;L`paO|CYXi7U|4OvjoMs?{HXT#Y zxt;iN_Ku!s@LYfjBp$nL#+joUQF()c5PMh8VCTf8{wk0V z(A7Ee6pq~g8gv{ayH3E5xEkr2@J z;NLYIq4pN}uXslY{^t@_cUGD9Sbz#7`>%5uM(n zBYy`I!hVmZkWStY{Y~itR3K5NR){mFzeTHdk0L}tpwq1LO@q-&(}C2@B?yq zW=4pR!>3s$wcp=30Vn50fuLKHSe zvP`O)JWPNJBsO6UF{|VQio1A(5c4Wyg~R0i*fjB+gn+JVMV-ZnVPDa;tuzB^J}s7I zYw?#L0V^g8Zg_PJWt=7@xVu00P1iIL-epw(gVg!rb8S+B5VC5Hs4 zK;l>U05NOrSESRO&h%4^9TrEA`~5fcpqzHD|J9h!YQlTo&XEw%mAqoC7-{w!eGMungtmIVKzm2;$hH?G3i!M8 zbw-PsJAa@HM`#EB$eL#?vpwL-6bS)cy~RmlMD`z~Vn;KO7vC4L@5i{HNdiufHZLdy<*Ly03U^2$9x#|)Ex~A?m7mHT6;wP@vCd9(E6|BeBCXdzvR3MQ~%4*qd z|DscybO>QVIw#Y8EUI^l6_9`~MeYo-aHI;~*0eVvmi?+`y$ypF6e^$siHtGR#LN@_ zP)Sdkf!x`tr6Cdmy7E_Bi3RUe`1>=56GEO*$MT5>vM(v10tu|N5M8`m@u-zP zAzW)3S&uE}Y4#EVx@s(K#lkzPyrs{0Le$-AVCQWQxMih)3M9s!vl3lxRQP*6Xa;hD zMl(B`eCV#MbVxv#_r_Ub(RMXn|D&7`@edjWx{t+ZYic@FAaOgvMs(Fy<@M*$4CE|_ zX7(K%006414*$X+a**qT?}ZbOJ0+D+^`xN+|Z6R1GqWp_K#wNRBexJ8NHMNO=C zv?BVaDI}ol)SlU5!Ozybe;b-;3^~@wUaR$eRAf+rL{*BN=n|yH-|9+L@`K%#xY4AFIk2EQqlW+2BLED~tHjKgJe2?1SUQPagjUrpXh zk5)5|>lF%g?`uGzoP!D^_I8>ox(;v4XGNSMM1XT1+s9H{>nI_hD|;X>7TUGr@BKJS zh^OCj*qmW`tRn{%NGQ&miLS5P@)x#HB6Y`of$n{^G2JR5peyIKsaVjtJzpM26OALg z-ecD>tzruY6-XFwpCY=%X!6H(DPdfi!R8F(N{&eg=sKq{Q7nAco?l~5t7Lln1h$Vw z_slU4Dv(IJI$m_$(vII7NHdV-XOdW7&f!}A5(2u0?i(!@p6kGGxk{^jn@kb~y7zT@ z!buJ)kQgy)l;}FXJ?~i?MTmf?7`BfkMR`F&K$nM|zF6qok^h-@ix8dW-V$h!tz~mA za8QASch6y>>$moN)}Nb%(C!i;(7mt6{tK57&=u=BKrHC1#Sg@H36Xg3D(m;!VPiN4 z6-cD5>nFOTcHmtq?+_xq{sQ}cczDG~2^|xH9paO{#Mm5;=xHkX9 z^&TO9jYVwEpuN6GLO@rmYx%e^wlg1=OVc8e{mj_jj4lTXIjBHl=*JxFYOTX>xcHC| z8;=ZQbB4&=7ZL)xhOJ1!|D)cp= zru`1?NO;0P1rla)*(mXrHScUq`##mlmD+b-bGeri0=fo%TZ1Zo*z!7=w+Inx_Cova ztCRZ+1}c!~(|$Ed+-$>(p|rQ;{r#Bs-Pe%7DhUBytKN@76$LWB$9&qqO1^nm`|fK? zr%DDYkZ63Lh!O|c^4T3K2r<59lJ?zK`SfZD0bSP|yP=9j&H0bo=2^FnW7T)aXLla= znt=)=G6Wx#*h0qp9;5#&hx325`#_#7tC0}Uh4(f=6}%mv)#)A~Y)5|5b|BYns%D@9 z3FCLVDDkO`|64&jkUqXs^69&;mj~5K2A$^PqETwIAkg4ei>UoR7nWvIvcu1SrP8YD_YWwHB+(`Ur0v8=1K-CkSIxBrA&Nd z&o5KcztzBxcNBE&y|2k!bRSW*}X!^f;-*8t^L^38`e!)Nm61%&;RVE&D z;J2I5jH^HNwQCbMRy~st&@~*pa}~E+@a>~0aWCRu2l7U{rwmje(K)ppH$2IapYVfz zc5jSxVP=x!Flh5gLO|CAF_x>yY{|EIMu}-#T(rNJY9c;hpaO}UssUVLmlnKoE&UGe z3ht%d-{$h`>7h3Y&chPHN zipxZXzWXW0*Zd-bDFxBq5;dj9oETvCo>M9{RJx^ucl8pY7@))QX{!AMQ@?&K_1< znH}Uf&NQBs5YRQ|&#blbK`}_X9zLndjUiD%jlH1f`JMo#zeK^6K}TWgTK*H_{N2o8M>Eu z&bDL;0bO&W+Vi)*yYa)n(?(;;xk85Sm*LuX76TPXM7M6s56@`BhqR$Y*Nr!{=R7ub zn1q0?z5BfQiu|_xz6Xa0aX0U}b}!cA0Ye$6Ktea(lTRGz#vhSU;>PP+4BaoIzq*fv zfG%18Zv5?q?fCfRv;+C!_f3Y58BXo(%|HbbmS27NM8~$gZv}n2o4(+_cKy6T?j<3h zYs4@=zJhDdkJ8B^#G9r6_JQn9emxs1kZAk92cP()EuS}xb|7a*KVc@5tCm<9zTpzHdi z-h4%oJKsHd6Ct(-KG*)%ukv1<0xFPrf1l?QN3`c#HPTsweH$vZzX#dPsX#(N*N!nE ze8rj$eEyA2d9W#u*FH=AT5)H}y_~A!8@JfA3REGZRKw27l`9lJ_E_{jLD=IvA)@&9b z@_vy0;V!GhvlTA|ePqpB6c50kucdJx&CPhzH3P6{yPT7$&H18&7~EzkZN2<)xyY>g zPbH>pc_Tmt5|fII__fI~xW$J_glLF*#rPO>7vF4dCPD(b!n{rREn!jksxSS`uiem3D<^`r2HmeGXhx%ek@wwpb91GJ5VfUaSV`ux!~Dm){a?&G!j z_IsvHOM5Zb!Ciz3Bxb7qaE%HT?%vpw5c{~M?A1MWLfTVb2?1UHfBteh%lhF9{ksuj z4gZr_x}Z)lzw0kT1rjTA>bVwO`r+UwtqHN$*^sTo2ZX}1UshL8~aez`Lt zG^hVEImZtOi|>btP=Q3HVHG#yb1hL` zl~K0j7t>^dkFaiJoCp<2Tur&hE!@}}^ZYlmUhAyVWse0MC>Zr(u!MlFMH}vM1`Bwc za*H-=u6(FxVz~nalh+Ryp#lkg&uiRoXC8Ae?hzumrh!TMXvlaJ4V4hk)p-3X7iGm_ z!|M+Tu{!c4^Wm!@6W~bpzyb;+BHCZ%4xeOjkcB{qm3~i|t7Foc;ZNfv1ay5Edx86& z?}wL+DI`RlTPfq2oX$+aLq(`S;&%FBu5O?oj{QRSDO&LPGE;E&A(Nztln~IBu;viA ze|-;ZcjycuK55P|^$#90;+_~0Dv(Gequ1%}d*GlAI|y-O6rlo%q2x`#Nbhd=iA3-&2oLx|f=W-+VIF|2*0vxI=I znQ_y&A=^6Rj$1Pcv1()jQ(eih2A-}WR3PD8AI&**?2L8ytsuneX?>aWu@S8I>1GlF zy56^p;>Ga>f9uwtTbCa|fS-wRNI#E=>jZYq-FrBO8FqmwQ(!EPcuq;$LLNsMhGV?B} z#BZnT1*kxx(oUBj@~1y``8b>qyKWp{KJ8YCZHm832JDxuDlw-WUW$(*Y}!82 zSo1K(uL%J z;j8P*v^l8~jh2{;P=Um$t(&=x?$KD0O?TNW&2eR77plahk(Lqyx{MaB;yTqw;L5o< zgb2Rm!Yn+l5?wo4ico<>@T(QvOd$%}O}|KpugYf3t9dFh^^ui?fUb`Zrg6iUMc_Uh zy^8J}YQl6srV_90wGyEM36DQhxSYr+Ja{_&U*(OdRg}+BiTMsT5(2vR4~yYi$s%x% z0J=M6;h}0p_oFIt^Al?kDv;RqERsun8Hr~eq}Rm1O$roEl2zhlXIlvYUFUXo<96>; z;YpU`2;r)_s2F}kCF(!75upN!@Z>IBWl|)*;joGjQ{Syr%$%qak6X(m1aw8_n{gk^ zRruv6+7}IIwL)P>&iVI?wjxv@@z>mx>-IPT=gpW(h>lqfiXY=t;w(WXA)u?l^ty7| z>~M@%_z>cLm*$GQx&H zB_D>XoLdlL-HD?G#}2E+W7}mSR3K6QW)HjELWR%E0|@bXpu4)qT$R{rtCWDQd#O6= z7xn${Zwp64XkKTBe4l{CsmgbE~#tFCF-o?+OvH$CTlE}01b4N!><$U{*gocUm$bUPQ`;?j)o4WeW)bT?3** z(XXly9Jhq-jeBB)T<|6t&sYl)Dv;RN&<82c^u>n0>4fOsQYEC4jFAnd5(2uaC(T0* ztwZp{r&|fp%TFaNAsLYqO+~0cB4Tbb>hUoYAJ98Wh>mVa!hCW*7GxVs2qAuA zJP2?3<3xylEpr4rl5rsBy8snPj5NHDI#u?;tuxybV!PovA)frL{O;6A21=IFF*wn3dMWW#kCJUcZw%Om2RQ%o}6>zC9fm|bY=F}!*4bO z;suCUCX=o6h*>4yjN)0HR`?U)G0E7p{+a+4NW9e8;EwkL@u`4xLbOY&6)H$ZY)yfLfUdtPC;XxhkAFL6 z6XMOM8sR3%u>F}YKm`&frnkiHWr5geCfNZp#^HA> z&Je;z{Yhv}&bco>CO`!eLo(fP$EX0j;J`UTaNJ*E70DP`lqDgc>#4UFu0~2MuPP!$ z&Y?fTY?5)YcDDc(NFX;)EKlR{grZ_XVZ6d8&gNk*AZiiChJ*9<@W-jBuMKD1lAX0oB! zhGgu#xln)#Bytma;x5J<_Si_fwe5#C6OBp6+t~>c0=hc%;qaSe2D|>D-P$9KCS+$Z zm3Z>U5CJNX$k1i6caRbnnivp5oNg|5B^lnO3JC#S*&#u=I#+?s&(Ln|ISX^K7s+Tl z#$SL6B)*dU`s7PlJb9NDAto7FiBTlu&e#?b0=m|13B|7){IT&#+O74?vJ?l9j3JKX z*HwT5i4R?av8RT?k+W#G*2mFCv?CeYazCme0bL6_gyX7C{y2`zJd>8*QEQQYc9Z(m zsi6XiR_eakxq6iHJUbMUwPY#h9qOQn4^XY zByOz<#~!^Dcz+oscJ7mj0VE@DYHtYvU9NV~`1PS)IQA87%J!7YM1PXeeJ-bl3MAg_ zh`^q+{qgReJRy#^XilEZ$yhXSPyr;MYv!aFT=Tjo4puq@qt{v6`SLu!=gu_Q$QI}-QbDF4t3M3XsL}4$l zUU+2H7(yKEXDfP;jNF&`5(2soJdMURyL@rq+!=&OE4I-d$KnqK3aCJ$`Ec^Ln$Q!c zPNT%>J=Wq#lF`pwmw^OyUFaBvs~>j9?uSzdQPIj;96^p_WTq|y6-b1hR$=+U9{B1^ zN_?MeDJn@uo}-(DfUZBeDqL;X9UrQt-P)2`3sFHb3=-WKs6gUVU>Lbd`{Fr!DN%Rc zOnbf3j17_y&{evmAFht+hLdgR>Qa4YGf|&p7;Xq+paO}}Ekd!pwJ*MKfwpm@7MqA} zh!MIxHVl@`}5OSbRiiSdlw>r2P$D6qtHaLL zxTdiaE-*h!h#3bS2=OGtVPOda6-c}}?Skbco$+p#HV1sGO9WezVV(IwPd)jA^&D97NB%?#hYY72e;|;8E^&BtE z1gHtouv!%8m|?f!YX&NiaEh|P@;=^p+#uTIuuVKI1e1*W>Gcugx(~>yIDO06-clT^s#(dC+xI{0z`zp#q5;y04J@uN?1uRYVBqxMczz!-W+YNC@aMd{BmJCVAjF-D!(;%(|rl zy(Wev8L&`+M8lX8B=?i!Y$w`u+F?Cb@FN-R=bK0f=sLh&KsARu;?eDBW9z{B(LxWB z(aG6_g$g8gTAxSqWKW!{UrLC6=|RF+l5yvZnS_9@-@o^u>go=7vp}0?{%*a6(Imq^ z(u{=)B!0=Vkv!J}%eK(wnQkvPp%cltH_=i;K-ZhLYfz0_2i)&>1tIvV)`ADg_}tWz zg$g82tyqQR4IQy*32o!P8KNr~l8ndsvot(X!(6Yw33fj)9=T#@3t&dAQ5Q! zM;DO<)u1rm3s-x9sr zw8y6=(2RLo0~M`FM(utn0bQfAF6wV~$d|Md{6-aCj)v)qm?eM#a zG{a=WAqBmEY~ODyA)xEZ;4RAPvNkvcrKAw8qBcDbYW(1w)^4+OM!=p#q6T<#Nu;&JEA~DA&bV3>`Dn_5H&@1rok-H#m8)3s(EkdsNTV;|v`${D}K3A)ss0 z;YzOhd@F2kbd3}_vA|aqFq27S6 z>D&^xPNQR=q>jZ59W%s=j~S>y;&hxo@734}56PrspDm;BF?7uE$^WK=fUcx9mVC|R z7C0w?j^W1Z++*mtJt?r9feIv^+%@Or{;lwW8FUQSi|mF-#|)2~6iNu_Qe`>t)khrh z^_o0Fj4^q|&@sact4j=2ATgrcj`y6?5?eQ;#K0SM3>`BV*qoCP&^5Y!Yrdw&0dMu9 zW9p!gI%X8fnBktwKm`)Tj0-P6*8)2x&@pvd$Pb3zYvq%3Bm{JgckIa5v~|EOy3uEY zqj}#MI?qtG_5cGFNIY|J=e<5T;;l32vw_WIUDkkP`1@{^5YRPeYG=MC(H?)%IZTMZ zPP#0;M;Q*yWS|0x+i{(Exwj*36+xeI0@eB~Jr3iPRT2Wa*mgbm>MT3#lu4h}?v2rB z>6qcalPejhKw{jlZoGVw1K#+RKC2mRGG^(R;r7NP2?1SGd>Fp^adW)>H+_Ok_cdne z_ru$G3Ii2LjQHZm%MaV*mlG+msm_$8V}|7&RT2WaUZw@`H4e>j_wL&Wk+{y3rDKNW z=ffDNK;i_?@m|$-xbFN`LfBokVCk4)zp-3GK-Ytw!FVTcW{0aD(>VY|3u~5+8EnRyNC@aU=-H32-eilH`O&$I!zZj*I%aTgXUsqa z5-Tr<@bcl!ao7twm+`E|hD{(D=QorpAOT&6MymLlavL0Py^;_olWkZ!W(enQD4+s~ zd!PF8^4&6gs+i8n-1L^QDv~i~;7SPrT@gWd6Z1Ixal#qRtvCT+E+t9!Mkbo}F%xJzQ)Edt=nn{Rfvt=wDGdwK*>JJr2oO6ie z<@UB%T{wdfI&GV?_9Wxtye0XNfUc42WBBToRye&so$DKaNXE)Y#{Anm^PvI>1M?_e z-rok>^q|DPav9s7WRz@kRzm{1dR>a)YYHv#jcs%W(ltuP(s_oYNv+jTfrNr585_yY zDtjs6^vRYTOfu>QXG#d@YUoNbbS-hg^KhCm&6bTN8ELDxs-Xgjy}9IXRc3`NEmVY< zlWN1#F~goUPbCC&Wxa~xYZwb`+MUh_CmY+abj)xs`I#CjkO&$Z!OKmp@SwGn*pp?& z(lLWujI{s>=<>K3!B@{S$36a|GuyT;tXMi`xNyW;fC?lm28Z+V5KA2A(wz{IUKVT! z$#5y}Dj}e&xG{{c$uq-cJv$KM)IoDLh-ADYznBgcNOauPmzS@wzylsq;z18n?KSb< zg#i)*x^kw5@-<&fv0o!yo7i)@nf98Pd2@gO6-X3V1oQGs=6G`pMRd*U(ZxolC&?&F-yuK+5*>qk^76T+cx_wyn|jUkzG=q{ahDHE2;q4%g1S&@W*uA7V7 z@iiliu#pknr--@rjG<$OAbdrD3M3p;-FSIt6D(_Yo)9r>ZZmYg#K7&ggn%yF5GTGm z+YoaZbWJ#5Lj~hWGESok0Vj{#-&p*TO|IzWRv) zzT&o?5V>ovFe6AtJH6)uR3P!Ez=oF}F~Zen>DqSci3^N^WZcntCn2C~ho>oD<7j}b zGUpJY>7_h|u0h5xek(u)5?(7zczKN>ew{|w=pXkuqCFpBXTC}Z==wHDkFOc1j}5(J z2{9_>u=adZwf!nU1rjdhI=oj~LmcHuyS3`hI~lru?xN5UApu=qT72ZHGn!&^Z$Cn` z8nA<*YZKF-H40FHL_qL+PM&ChkIe`m#HAgn3>`DfJ^>wjVI*V3ej^D1U4}`8T#b1XY;>D;Sl%Vf zX6V|)lu#oPDv+pf#+>|dQ>;2iJ1hwW0~tDI*x+I+A)u?p;$vJ*Up;Ky{{?B*EDRW+ zooBeAX(mDi5}#)t;k+E0V%w}wgc$Iu8{Z}VwpRuxAJ7D!3@;%>={OsPjv4aTSxE@!+IW5*SAAIrXU^M32*%Z#i6$B2 z+gOQEfrPgvne*JFhd=D1-KeqI&lR+bGi8yDgn+Kty`fx$;)+ds}r>2h39NjSd)xrZ)_z5bWOiy&y_Fwi?~~~t7a9QsW2xQi)Y)4 zP=Umqrgof{nJ!N9n?r~zHf{<_lHqwnCLy3}{@WYMvLC;ZSBEZy$m;H*Fe4dNF)|S< zkXX5>SSb(H!N==p=kD~P1Ad`2!>zf5fUd7^!VkT*30tFJR zewZR|)gLsiJMBOgD3_~Sk&GQZqy%)ij|<$rUVKGwYC98R_mC1b9k>6UW+Orc5+7rV zG;#BPBF{6igxD2sEYNHF2@h)t0bSvKcBnM|3)=NrMF_GtkU*aY=jB+5P=UmxnKBgn z^*b6rVm2Z6{qfMQP3(PSAt9h^u}ugnyYmUf{Gy-40}4-p&NCCm0bTK3=Av>{16o;6->x~P3K!awjP;95MW{exLBMPj|Kx+4W=u8MYW*FbWScD2BUd-Br;zoW(qZeK$ zghk^@p))xS*;9Q90bLd)M^S0O2h_IPGeT6?trWaS#?OKJB2*xea``ZdecFK5Ew3j; zQ+1|5=i&ON>q-dd$|<~r%GB?XVK#lI;rY%?fsPp(n(2yAfkgdmgyII(qj$^A3DK+Z zfOgHRaMKR~63~@n^bnQ%zC*v`X$P`H*#YgE*Vwh+1*kxxI{6-ozx4r){nm~Uk^6JC z=i{yG2MGaPw#z@DvQux+^z-yR$i*XbwdbR@Wt{*ONbEiP4#oAWLn&m-KJq(pTP_K0 z$=~YYlUEV~y2jb);Zl!Uq@OvE5LSII39ZTB>i6T90#qR3lB$Daue?JpD-sA{ez#m0 zMlzaRyC)%_Yxw(Sxct9rWcF+}A-2sf7lx3G1toU{s6b+JunCS=zC~XqE+WKcvbS$v zlEGINO9<$)?jytH&aaV2FWQ0h|MFM}CK)&Dt_e_q#E<1<=Hp^5GB4Rch}5>V+CIb7 z0JVgGu1Y&6T)wRm-QJN!h*O7Zw0#Dh{sjV5Ai?^##PMBgP{Oypgcxr4S#TuB@kIB8 zgn+KVLG5sv%`0?Jk9HszFZd+bk>gObI4(d15=A52aNP0N=xQ6%|8;bOPerDey0VA_F|Dv-GHl)-UZo}*zJ`Xt&55;94 z9w25Ioo6_jW+@IM87_J)1gJn_K#O1;x8@0&)t=5X^ftET4XG`TPD&uWVK_6gn+Jl&7yJnkUJ=D z1MNUgbN||O;9SDm&^A3FwM%jKSrRx6t+mx?AX@ zG@00zWUQ?}?GF`5j9wUp*Z+OaoT8n^u<=B zV}_XNV--+=giQ>|7=0TVZqgF{ZN)AmW4hTz2?1SU)zP@DcNyxONM~y+Dr`h=l5wv^ zz5*(c*f);+t)AaP6J00~aok#@V+Qk39R?E6726{Umx?9mbqbwzUf7YeCP~JZQ#uS( zAb~^`j*YvC^uj3dcA=%%lVp7B*+xP@*P^Q`T;B5rx~rkH=fyuP#BL-*wxkUM6-X!s zhT-_zWyp0dC8k|B)1Hs;)x9MIba`y=hs)0tquy`nD!|;{X4>;H?o@9EDv(&<6N=-* zOOeh-O89Lx5!=(_Fc>2tpsVs^FfNl{L;HTx)r=}D(wZd4(Zpg50~JX8i3!4SS8pKa zG)i=RZ6FRO8O0BlN(ks;ECX?AP7&%~OIKxnB^ZeG{g2BfOBtv@qBrJnEPow+op-En;Pt4QwzT~{-xsTW$3 z`E<3<{N+c1KC5}0KE*%<5(_?e!f_|Bp!UruG4|1GVGzj} z)=VQIpet705tpVR)TM#0TF!n@CD0DNwMJy10*O8O?l`vNWi-c!5>B5UYR4r(%St2! zbh+MYjmwNR=z1MpeQp1h?50aHj&>+vpaKc5#s$aizl6+OC{gBLD$qHAkwFh71au`w zI^y!B0vd2Mj}Sud5}_H%xc>M70~JVgUTlx!oiK_nqaDcDI~swme_ZSGT0%hAz5pv+ z_NM?9wxjFk`c9Jf`20{!Xwc?vo$&^fZsed{Fzba|XI#HF+I zQIj~@1Gsemlt9ir9q)n7mc zJnb_?x$YAZNQTajpA1wWF*@=)ic3`^CzkdZ_GD)WbdIcewjK)!=ps8lqVfrO$m9|2 zK<>G=NucX$zU}o`s6e7F^%WXyP=LZpXfGqLaG5|mMV7}5Bm{K5A775js?H(n*0f)e z|81#YL^8S#Fkqnq3779BC@$q9nr%Y+C7a%l6*!X7dX$NTfG(H27f@+JE>hUio{Y)3 zF#=06B8*H}s6Zkz=RAu2nTIZYDj~$o$$hl*3=^}>Bm{IF&D@L19-KkPj?=zRiB6C} z?@`|rW-L@7@$h&yikopB4PHU}J^_wxg?8jPss~$22Z1 zZ1=^2g$g9}+pI=|>vGXfk@l9wS<7K3ol2avXYZ%*{wVDIE;0LrsOy} zwX-Tn&dJM@}I94VhpsO}Y7nPx7XwIVhgt$;KPEF5;Cs~Ds z3MBsQYShFHK85!2^nX=5qM)EV$?!00E+L?6#)@5RzeZE?sgpvU2sCS#!j z3C9yVH1T&&pw$NSf3;3-pdX}t;Ljt-Qz4F-7PKVIa<1}N~wg5#tavT{QWh_)6k^McF zjV(TkT27`J)&)5V`i%4JsI7#6E^E9=S$-%7{feL&a~>X4&}W=s9=0r0Akj;2qcT45 z2s&;>GqO(9DS}9bOM4p$0bLJ1d{>sYIe=Pkq{OVW4+@@ST-ayLLIo1>jbD`U!Xaea zpPutOBODp}{oov}Bm{I#-s#1a?b?s_PospzSw}{nWQ^Zz$wCDZ-PU<>@jVWrZ$0Q| z_bu{g=$f$G8gmH&U3#v=xKg`)=*1`c9Smvk*Y?N;HZ^CV0*RCJhj6i{51?Fueh2p@ z4Q1%M!Qa`WnC(wJ;#Fkl%70Kwh_74LUNZhz~or~L^jcx?ddz8xKI79op zKeIkd26jtRqgdPD4KsZz zA)xEjNdvxo(pI$X5FPuZ-M+@ykPIF3Ck#{|afR$b7jLu^EjvZWK2vP&G4x%_xQ#a@ z1aw)1S@LDoTae*=I)?l2*j?>8Ke4TxfeIwLZ8zuRmTX5WH_$O$_s*}FK$6j7XrY9F zu1TXD`10YKk&fX7LXh7(VK{Oe4HGUgP=SPZrX3&udn9d;HPoJgF=a(E;F;Ibo-mPwYT;)d8-;olN<{7he-lzBYsS*OZKA19m*`;;p ztt;JMw51DaO_B_&=TjJ{K%#%SA0Ia~9hIfiXU@Y9O<5n3v9d{ogn+I)lLPp2MJn1c zcpD*h%`s)^8cTgzI0F?(7_{d2`1@(->65L5_;T)F2lB={xrBhON}C6f8k4rKad8mW*I{l9B7WT0%hA*z!oeyzL6K690=ky2iQ&r}mLl09+Gs2zqrFxnqyLu$`A~sG zXOk#C?(7P*a}6aH-j=cSordwRTc{xcU5SM;eCejeDE2*VG@1>NvGkpWPHHDLR3I@t zAc~LmT8@0bP-5&)TQ-tp7;V}tA)xDTFLE4aDafTOZ8XlEW6RR}V0W}d4HZc2$dBaX z4lYIEiIj-Vv|&w2#-)PC5(2ui>!SEQ77I|rD%xlqX=}sMXU{V8zn+SlkP(1gJpbC7C;p+nIu< zOr;&j_DTyjnq-u`>mnhbE6+BZ-}!A8I<4KMAoZd-8$mKAx9uuG1riT)`tqke7oy%B zJCcm`VW!%1KL1632?1SEYeV_nZ>OVM;dG^8P*F4OIXC^;Uw{fE96AK^xi0gNpN$hC zmfbgE>ASCdyV()~y2L&~{GMk?=s`GLG3gX##L{Xfr3g`IU6_e1^&2;6*a70sWCv{!+S^+AMa4}-}vqm#f$R65(oVHkp z?L*JGYKMe?uJ!x<_?<=L(UG{%WCd%>_eSk|EVUjx1gJow>y)1S>EBaPv-7kA86Nme zJ7yR?>ac`>t~24?_}%$qPzwK)5Ly0TwePW7pB-dw(pNlS4a@cc0#7=zA>9 z$|@uTbiGY+;&)~aMY}BNZ%*#6tk8B+9Vb-?P=Q3)zLxyy8>7(WG5ZPOdbdd1Np)}g zTtYzC?~5{i_u6A9!=Ke8q81%C62jW#qV_$O^B3Mq z2?#8&1>+ZNc<|0%ek$5eGJ2Np1)%cbY!YIO4+etNFWF;Y>EAZQVF3TeT<#O8z zQEp(P?W8`nwGyEM3FqH)xZGi(C~x>NLcAF9QbFh2^Jmyd2_vQZ$%%Ta`Q z-e#ME_TlW4Y(=O*;#7z|mm3_2CVR{!#Hd^C6?DG+N0CfIKv##QWy)QKz0jQNv;!H? z&_+Q!sl^d85h{=vZBe2;#?0y4pWCWU@YVK@J^g2lC+P z&jkZX#>8Zq2o*@|snKV0-TctAp0oq0nz~d?JE?Z8lz=Wvp8!qPD{o|!LpzYpCzh#c z_X7#GB2*xuc&F6l+V?;~IkW?rxT081J1qMeZ6pMA`8X75cHfhuz%bf@?6=~&HlzIl z8xbmyI8s=sIcwSt4Yj8o$kwBbwB4w{p4Jiqy6Wp?DC>Gh6mCd6kQXl-X}eL5xmF@n zAn~!2Ey~sLL9df%5u(z`L!fi7{5uN?0bPSi`=H%od-PdDcaA)l;33et*H@!0M5sWb z`>ft5_p=w8`7n(Tj|PNk*Ev@@m`MoeTJtFxWu0+DIbO5_nLayAuqPRlH<^l1fy5NU zStz&01HBt~m=LedP8R5QFs;Z~LO>VVo`H7fxT50n^Msh2J6WLL!S0-~2o*?}J>H0N zAG@QH%wj^QH?0(Czog3#eF*_wPF;_ptQ}6MyzNs$=qy_)&^@+f)AdEDKqB7jFv=}& zi^k3QNQlP+GX=UfQF&ZfLO_?T_a(GDtp!RkpdCn;9+}!s>f8>xB2*yJs!D^-7PdxR z>*&7Dy4eQ=I;X$8;)ehU=(>~q0A($;M=7UT5u$(60ii894)5#V1*kyc@|(LTH_sW> zh0_jXV()()$k*dONC@c4`B{guX3NlvUp)x%OFvh;R&--zod6X`-0^saa*wt|ZTfJ8 zDDHJhp#73R9+eUTy5u)?@$L!M=t1!SLcDp41==rh@pvUb1rkR|C;V)-1BxpcN(j%z z<=Rf_PsjTb0=fnrZ-#dzSfKe$Xa~}vO}Vy{8svOWfC?nqn3&)*8O_m;O$!MzSN24p z{gU(^*ChmWshY`fR|+ey8DvOs_eBrY{=iO znq5wI(dpC#HMxC-5WhPA(XMUpI+!Iu1rqZwd*IU}P0;KM*9hS-r->L$j$^HHx`cqP z8Be<6tTsC6s4?w8TC{H>29onp*mZ*d6-cmZADlbL5Cxu~PhRrPh9aF8jWJj(A)sqZ zpdZ=5{imkUjCLSv{S8Gr?^EcPB0vQaj#fSK`F>4N*i`xisneOf@j^02`3{#5(3P#j zVSl^N8r#uL$y4gRTP7mC?pov}2vC8A!V zdo;e+thA#Y$fdPrVt+3YYau`d66@wmvZn)>^cn5}1{?TfxpeO5yPx{MmbFjw|M zVz6RmnI`LX7$Mqc z$+R7>`OU`^Km`(MFCy`{`X`z^`*1>-TQnExypPSn|NJ2VU79~Jn4fY@^J6z{G+J$x ziL{I3RDQ%CDv&6jAB87BxTm>tm=gWw$waz-9@sWf0SV~(buR|{54@zYh^LLlEx&9< zJ(4k4Gg1K+NGuo_g~y_snq^8#*e2L&_fMSQohKon>(Pg3+>KRh>?~#w!tsfXcK^gA znh&N(ktx?Hz^v+n?9$8%SrJoqVmu$>caf zH#aJv0txpL6&{;@S>xwMi395`#m*$dIleUm3FvzHP=y05PH7Cc(%JJ=V@pv^GEDZj zW}pIzvE#z>$zH`{js{8y=o)mYA7;ND);M0GjmEq|X5ufhS8>#x zK(hY)e+3dFLqhR{;TJUF<0(;l+(e}BS{lelO9<$?eIpq6e70Z1R?tS{CU+C#I~d$JSyajrt?@z^wKfZcD4C83IcD5&tSW14g z@&5`W*1Y8KgwDq_uY1#VoPD?c?E`so=q3pPU6wBxtUR$*6O&3;mvqAPMA{=W4A{g# z1rlfX`s0ZXIhvn;b`j$Ipx@dtb%$$vB?NTYyzGIQ%#E6v9qB6B7S%6p|7y*yy$nK$lG~AKYtEs;1Xgy4p9feZBU*3H=yC00k0d zuHJZb?RL$snuCN`-mY4p_mAHvMF|02{XcZX%$OCLoN~Hqne6sjpwG27qeTWPkkEhc zjwjsA&_r2LLND-z*R&6tx^#XtoTO|DzuF{_toY+dO3 zxvR?=fvi@EI{Nhz0=ly64e?9(A=jgO?(I1XPC8YpLQR}v3Wlks6gV@-S6nu>4_SlT_GWc z8g3ToyOuNK^jJthmviZBwBUGvW_saOLO6Mo)+9L&QxiQFDv)?%SBdT{8K(Jpj&>m5 z%9m^3U9#U~AR(X&{VqrI7W!(oo6&yB;bF_P?=FqgpGe5y3;yXbM^)8KpqYY z)^;E-rkY6z=<@K{hh{lBYjStfzR##pFOCw!B4Y!7VY~C_}f1v;&Ry$a;P=Um_sUy&xjcqj98)=*B{s&R*O){SN z+DZuMnslZsde`!cSft&3Z{rcQn)U#SuGp|pfyB)WA5@xXr&-#Z_Q*bDPF0U48QT(N z5(2tncj=+m1&_s+bM6x&dBG%Y2Xgp(vabwKAdxE5Mb~|rX=43pyDh)sQUTrfL!;kZ zLO@q&4SGy172dCx| z0=k?^YDL{YN5r9rXojgeP}_k#zgNaW1rnd_tHo3QRf&e`xFO;z8gM(tXM+=x^()>IisJpVvl*0 z=yKCRyLPv7za$+QXL>DZm$^{evuyvzP(nbL$B8V?ajsWE!+Uy7Y`+*(Y!enLkZ`+umV3TsjiB-?B}CYZ-3)yhtOsDs#OBZ%C zbj(0za~P;V!n5afu2tNAbx9oUKvrHq#?bv4Dqnq;5YXkZ;T0FW!h-$Sm3AOEWE^Aq zkc|4rpBSh>f_?d%>zwmK;nt4coh@AowCjThEZ#^6=t?;JoogASWWDy&F@y7ieC_(+ zJkweRDv+ox_`=Ps-Nra)(=kJ0#WjYG8C(ZHl@QR?FvWoXs6UmpE~H~0;|13kI%YUE zKzc>yF=$h%_z_$rF#LC)UAcQ*UrFNaOZQ&&b zDv+oiV#il@O<)fM`y!KLRU1ay75=*nB}L#$%*SwdX8_+Gnyu6mWrKm`)n z70!IZ&y}psZaSv^digu!NzTXO&Ics~bY0Hv!2i&D$UdKaf)JOxeP`Us`6vp{VW0wu zap&6e!ExDa#yR?I(9-AMK9FGTB1rk#m zz4*9`dFbs!ZbUJ?Sj40VI~W%V7DKQcEFVh{VT16h|OXP^R!nFo6Fo~_K3+dER? zVWSmG=NaO=m`Vufiu==-U+>yonY)%Y8dqjmv2;(T<{wQMs6e7Xrrhp10a!UL09t+wkeOgdrSQ{Bj z*Uyt0*GLHHnu{a(l*Qr7vbKu}AwOZu(mkDizFMV#3M5vAs(ANfot3{n(7C0ar{a|7kIy8;zNs?pK9E!M^%PKnM5AFO@A*WbG#*EZU8Dm^*Uz_q zG|z_wbWK|k!>7I&t<3yAmJpo}%UHU8-nB_!K2#v#X%NMCF$-0;enN@R`!be}8B7^_ zH6)-bAM*Rt1{G3fy62l$$LynRGvRe3AerE9T}2w=kh zd|~-o<ifUb!h!g*Yi zuEdWz5W@C`IZMY3iauQgs6awh(3ju5aH(?mT-t%WnfPxX$T;2p5(2tj?GNQI>1l|$?33f34$ z9hUCbx99410VFXS5`Dpvm62eNyU!x93z66SW}FAqDZG`dMw;>=UN zYR3#8$_@!ofkei@F8sEydz1l_Ul3wjjfDcsl8Yw&S&XlYbid8eF+=+n z6#`TsG4Nqaej9UE+2R%LKrZsSs_mo(-+d+_psUYU8DFHis&uzZBgE)lMcREJ8%8}7 zpaO~EKDPY!0T-3acF_)G%Giqxoo9G9`K^S2uI`6Td3?WAxiexOAvVvupxu+)>Ejy# zDv$`*YsPP$tWnyGUrdOZKmTa*LTOx(3cU=HE!n( zbge^eWSxONAqE=FV`%0Mle5X80*T9AuA#J|N0IAPGy|DDbAYtYVDQyQMnG5C`O`>9 zsE;h0TSbyJ-JAMJ&p4aXj0C7aV(#ISXxp0?k)!gy5aQR7_R>1TtIwt~0=i0k)6w;D zpCZqXr{{)`+83A1`Xy$HmlcfL^Nc@u@jne%aBPYJ68Axr{y;2{@Lr<+`1at+Cazeu%X(HXr zNrYJPZMW2;asD`K0Vbf$X~!$}4bDv-E+qi6tN9||^ zGWzX%sSjid*|CNSBx0BKXJT(^qsKyFQ6rlh?^mUopC(52I)$RDO}hUy2>3}n;8 zAb(rx z1KG6NN`ML^8bfe?>`Oy*WJ!NQykD*-?M*D`W+@|}>%mcLaa3y?)a)Ebh=giADRVav zTL@5rM9y1FG47oSir+Si5GOkNNP80fPH9<}B+O$j!JMvuk4=_qGhW!d;tBs+Mpvq1rpxr z$HX|2kUl)+BO&@Wrb(GQ@oozl0bR)%SR66f9TiU2Ccg$+rD;;;F0p$H0VyTT`lS!iNnTuqC#Dof&62X#fOt|{4n{!Lj@9t2R{^h zhO|ZwE0gTLM+;j`8H%6VY91vs6fKTKm*6cc%WII zh7cmwuY{*Pok}M?k`d75K>EX{qz0m$Ub6|Yr>Iyu)BESt3LYwuIDE$d$Mo|?M{^eu zBH-FHo<6Ic+;T@oK-cDCYdm~s2x1dx1~R?xGoJSBf0I$jLj@8q&8%?rNIz6nLuZzI zHmjt))B#OO83A4C*{yKOk#J;mWFH|G+WhSUxyDGzLj@9D?>XU^DFJAyCe1)DAzdA5 z=5CRAMn*u_j*o5e$g>?$<*##uNdByrvd+_LsG~M{O6{dIlyYQE8;0{xfkeqp2FIRY&O9*|qNK=#5kIej9jaXn=N^$KtfsD9mibjg66JkNruHp8jYA|lsLQHMxZ^NCeKWe5zzJUb0SV@Nxl!? zMiOGHvyDLC+qRq;tAq+9bmu4FnA%?GeH6_=4z#lsXii_{u{;kF&}BF<8ISPlPi7f3 z1G#VS-#(DN>}Kad1rmBsd*j$o320;iC4QOP3ZA5fxx>B)NI+MY=46}_ItWdOp@~K- zGIILeKB@bW2&h1!|AIsu^Ct-f_osw%sg0CFp0r}90usWAv<#}MN4SZnF5c!zNpWdwAEe^0_AV^h$>+!=(>dt)U8k#U@v zcS!*iNDP=yu9e+Dq`Qz3y>D7d_hU*^vy6Z)zobMwvi~SF%%840Z;$%h2XgMcUka!| zB8ns$d$k#YYQHWaME*W=p*^`Dhi1Amkbo|i`goi&YAoWWNQp)Vb0LI`BZYTmpaO|0 z3wq(0fZ=GlHzjP;fBQi8yAvfNpli{sSUh~{1k|vbCK}sL`r8N6_Dd846-Y!4kH*oR zN1{C(CFb8S6lhNWMB-=}0bTj39(csU$>_lenrQT34W<5~LkEv$paO}&)NVMo`xwL^ zN|ZY2N#9pFIxA!ZbWQHv1&>-g74_Y-lMp+$k<+uuwOZD+oPi1?&T4WvE@?b^?m~&s zkD5X^G7iO$tug|-9CTTnvV8`6GKTIhZ5Xd9M3EZJ&$lvAfkdyH5jbYpB-HqbW*`sm z{M!fe%jiQg0=m{2b;QFD%tmc>=`PvyO@I49@|_MbP=SQ-A{<9goPy%U(hTIu8EPp< zHZ9b7TxVzJ-Lym=lsn5e3pRVY!*X=0p6-S(-j&bOo=!vEYZ$0NqQ9Owj?G?-o@h~`X>kspKxz!&8)O7@ zeG1aY!82E))9y3}P`-n7jwCg3hXw{JkWlQ@#o3RSA|HR61K3+|MC#w}Z2VJ3K$m$^ zvl!I03LP}3d4|fG!&3kD2XmVksK|)HKg6tI%TX`W>x2lJyo0B`Ecz-mSxA@z*TI3c zV&Kv>=-R1Ugvi;kowp+6u&ZyuLIo1n4^@jtYF40)2W}E#^3GK}?df!CuC9!Lu5lah zi$S{oBHJG{Eut!1Dcy57H(eGgknp!J7PBU0$2=1m0bNJ$9u|Wv zQ_<;-H1BgYx4YB_@`tSn3l&I&R2~$w=B!2h7@GG{zVP7bzVoOEa~T0$J{qZF;LZ(b zcv%@C9M`txUC20U%FS4)Kw{{ib>fj<|Duc|rGz+TsV((^G@oZFBcSW{n6YAz^Ct8? zm*%g=KG2kU;~q4&WT66yRitA?){6CL<{Fx&x^Y8P(wYzyc9wD+sd0F)jf{Y<##${g$Y(3MF#QoB z^xURN8OT+&)+|&Y5jj{>%-XaOX@}GQ)p+YWd33#Gw3e-mfUZ7Q59J4*-G*K(Xs(at zujSD!XX-j~svuAxkx+IZKhtJ2D*jDt%xUc{WgvB1+sX*&`l$FU1cmQFZ?kC)_itfR z2J+Qm8x|^%=sTfN$jaD)(#O(~*RAZRpgEa8=j8-+IYl34gY(i*&RJR`gNsnmo=z=7 zZCI#4A}jGAo9(&{b)8CU9I-i~pzETZN^2PbUEMZpj11&z^V6Z|y=|_EDnr`#S}FHqgqoWT66y z-{LOazEPri+(2#zqCl|Z8&n19t#yn^bg&OGP~|UgUji6 z;+F3lrL)O33`w6K@NYra>oFHm;FyDG%Rj}082M@=V?eHz*GerGDv)T5ynr(A>_vLL z=&!-41^cAlhF>lIFpz+*Jj;71==~wIVGYedZb;rIolWkl|C@mdB-S1+L|MJ}qx^s9 zuiA+Tr=*^lnOio%^7F9!kAG z`*tps5zwVqYQ_a=A48cJXa=&g!9#}j3m?SYXP^R!1FcQDtnry>!7Vz6>zDRgI-C5o z`3)HXU1J*UxxjVDQE1c^LL6~>&2%L-e4MW{P=Um1cRMcgV-}h)>M|iJztl5yok7F* zl8k_^T`OI=AoG(*bK?a!i%a)@u z0=l>r-dteXDde~73?bTo`Np&)H4dE4W}pIz#Vb9z%%8{5!V3B{!CSRpX-}t;al2#$ zbfvusRSq*`1az$t z62SrRQ_@?~%Yj1rpOLBe_hgv*N*vm5#?tp#JkAEl2;Jn#c&~O09|Eg05af zw{tcSB7L<5>qBZ-KQdyV0tvLFCzs`tgS`4v;@52}=^Wm{TIC8zK$o3gJQo~!8LdpE ziN>}At)z2!@1MD^fC?l&=Eic_nHSK6C`w#zX~WX>M9acd83A2M7kYDn1y_(}Jk3Dv zKWfb?$T&KjTBm>tB-Tg7bD6#u(Rq7H^m#)PjiiQ2prMR_F5`tsTwrW2DsM5H5M%za zkut)Y$LcDe0twac1TOR3CFGY&Gm!cJ-v{y&mvt2q&=t2lnF}h-L-q;d2{E2zAnBZ; z%V_;vs6fJ6Gm*<`e+3;2pv1SQfBQgQU1zO?1a$cqBy++2d9-2sU_u-p^0yCU&R9Do zR3PEiHId6!UPUu^Q=(3voSsdt)rz-UWCV2G?ncJ(LO}ITXrj?!tu-4*#__~yn-VIJ zh`QOE%ZkcF?s^G?czE(}AIOod9?J;m8mdk1$0!l;{xs3}!~1U^$noY+lu&`hp|#|G z+*YDi%P6s|(1Nuk<8V4;#zO+S?r8Mpg5F{@CnKB?CfzJp`t0o9Y{o+c60O(Aaap7Z z`30-?goqz$CS9xTKEW~qy6l-aE|{FldeM(28inV7`#?S#70g2g5_u&tT=s)}lu}5E z@pFvXp=2D^Udb{7x)S(kF6iqGWch(+AUiY~N$1f=bICkZAhCE*4=!sEMtjcE4CE`4 zB&U70_2Oz)!nVe~{cH~dw~+|^7u#zO@XrC&bd{a+=K{9hL;Vft$#R0{d+GV(=!Z)@R3I_yupf8i$8F?ZLo<*g^ZxdM>}*sZ zBcN+`D^D)Su^9E=LU%@2=T%Dm^!=~g;GqJEw{6>USxfGq>&IyZ60dx~(B3;mo@Fuu zx*{8#x!?mOsEr}r;Z=~+v+11SP(djV6-XpmwBoXL?x7(=GYMh3`j*rOvUJT$83A3} zgKW7Vk5bfXHQlK$+(J&zCN-Yhz2u<+iRMYxT-N#`WNW#N5V6;;N?A^Q&v!Bcx?ZY` zxxkZUD8zpuAp##>k@_bdIr^4|3M4v58gZExC1^_p%|PxMbdsTShSjPsG6K2^oVB>1 z;0Nfxor4Imc=q26r0?J_JX9c&v8@G{m3|-PFBnOP+TnYpGuGGV|B(^UHR4Jm3cmah zS&UNBB+4p3pzKypKWgY=%)3vF!yDvOUFGie4g z=sD>eN$&a07Wx8IAkm`Y8p`y3h&C*@AjGl5}*Q!=8mUO=9vn#>+V-VOkCSR`kb$MZ7L(6OXpTP3NCquuJlt9BCKtD z#+i)c;v!Q4Dv;>*dk4x6dyMQ%O9-K~x0RkbZ>3ns2Kw_9^Jz_J=ZS83A4SCtISx5wFm$)5(NjZ|zagz3s~3 z)&f)@@o$+U%Dnj;`I^xay7jyQ6m-2kU9gc6(6#$~d1R34H43u}BZL`wdxZ87+SS8G zfC?l!1(Zf+^?HfkJfs;&yNExXY47NEBm)Tv=rY|pG&1m?YIJ5Z%|N!yuIfyCN1x2L z5ugHz79R#hW|mbVpY=2Y+5h6pydk7U*-tqEU02qnGl8FL5EDi-ke^P~n&5#-qE}7SPM{r zMC_jM{LGhCNZW>HAg8=3P@0h%@m6vIy1J{x{2+}wTdLh4&)FiDq4rs3C9O7h4EWfrM73 zg_!y74T?B5lMu59cuV~M$j^)6oya&&={YSJ*gJvMV>rR&XWt2`a5TF7H%d9P8)}nXF z^4={%92Hmdw0E?hzpjjcuErK8#K3(G=&;c_iK26G`M!sz!BcN-QE*68_)rftdMSczNq%3WAvR?+3Il-WE?yDHt~>vuCM(ciGjyIA?qP6388&5lc&$;I&nXEs6e7;(E~BltN|_m?M{d( z|NhND;%^^i1awWWsTTtSzo0X#+7seI^xq8RZ1o2oDv+phcqeA=Xhi)Vu!MNA2J-=A z9LKI#%LwQi8`}a0UHpo=g_6vV<;{+mr|TsfuT}9-fyBFwf5a@OPiV6b%|Loo6ic6v zwfrL)0bSJrMmRX)J9^V=79k$bE0#VV>v07S6-fAPFu>UdKcj!E=#1>+%x6;8dH0(; zG6K3Xw^`#r`~zh^T~COM`pXFC(C< z%am3)u*Xj{-HT=*HSW|(nbenKlsr@*k+H)GXP)|o2CY3vh~A{DBi(}>=XO>`Kv%2W zwm7J$8J%80Gmy(pt9knFt9$1&JX9bt?^YX}74id(J4idei_xU3BdPJC*FhNpU2CSa z!@WeHxfxi3tctQ#f6-b<(#o(-%Kd9!VHX#;!nhG@g5!x5Y2yFEOTX6ajEeR3-)>6ug-hKN`2?^-(FYkrz^R&3dnVy8W zv(QpHH@x@4FG{FDqIg^kF8$DgyBSS0kowE4rCyWCUC+q~=+fg8u>BHkF4-uQ5N2l9 zQjMO*XOvKZ#Ot1Mxa^!J*K<83CXf@M>3b}dKFKlyy4u$zVh2u#`<+fRkfR-J1o|G! zcFzPQR3NeLp9EY!Q;YLfQ$nz@73hAg$3Oq(K?1rGlajH$jxP6!?L`QuJvIW}uWiw5 zc^*_CF|xcjE^DvNY0jj?J4;)EzQ?kpA}shd<SoXpgPbL;oqD0*Ot_ zd*kw@y4)jgN*pS)6vmTrJoo=8BcN-_@I-8HVZhnwEG0xkJUKm^)L5a_tbht6hSbL6 zvM4<+Hj`!``<*ryd`XSPJ6bc4fUdAV@z}oHfScTnCK_YB%>_?V!?3b70~JWbZRmwd zb@e%~7L*vKV=DFKw0_6Q26ay7V9L(;9%Tf)v7)MI1 z2+@rX8jk$P{I*l!r@d z;$;MMwMg>C_TFaPstt74(lPgql!psH%QH}c#Oio&TsFvrYbrWL2*0YQQg6dGEAGk& z=$bvF4Yq%8#_=0-2yv(82~XdBwe`8nKm`(K*SO+RXH(8iJWq&Diel+K7MrBUG6K39 ze>r0NGv-{c-gFoFWY;3;8E4cm$JzM zJ50CWEbr6Z@a~`Uc{*pPtf*$70*NhA=D6&L8MmW0mk{PJa-_bT?3@M}0bM<(>tp*c zOYWlq%>i8ed|v9ysjL3TKm`(|6}q@=tT~rwMRNd-UYR`Ym*H@?Nk%}|jmOQR{dY_5 z(Knh-Xw&_Olza8+*u+2u5&^G%h-E$&T=AD{gix36;ORRiZml(0NI+NJn_AI6*NQVp zrzwlDpWAubFTURmepHu4(o0b;+f`Zp1%9)J5*OjKv$znspzoSnp;s# z(;}8(BsEED+}6`&p#lkwqs3y`SxfHVJ(__WvS6Zg-o!H(a*8YXx1g)bu3XU`*>HWH z)6|R0m+?|x&c)XZSg1h4bMzIlY=#xLRC$jOUV70yoinT)Z6YI}OMC1Q(O%n@+qszL zea@%%;OU%UzLp6K6-Wqk4~b>r*4(4MH1E?%@RZJ*@boj65zzH#Z>ng2)0WG)QAUU_ zV?CsIunMo6u~30Tzi;crvLDu*MtUhBLYuUu&)}DdmNEjmb{rii+OMhEq%#myAhEW7v{;sB!*y6l(^La8t|{r9VW+#bjDRj1)LyjjWzYRl&>UI!WKl`y z47oX0EL0$I+c8WmTVl&KXwy_!o7%Zj1~Q$T#s>-L>JzLZI+#0fohQ*e+{c2MO1d|( z^_4XX6-eMiT4EVz$L$ZG|Eu4FO7rNPp z@Lg-eLIo05qYvko>DY4*n`jM>Bu6O&spD!ZBcSWfncsqanj<$Ri`Fm<_mnb_Uyj(Y zP=N%W_fsglWzX4*efo{3Fx} zuZt{;bL12)X^ro(bqbo3xzf{0MnG2*d9&aCWlOH(5lZY0daI!CzRoDHWT66yz&;-$ z%PgF@&tvHQD7a`R?M;mCZXqL}%ecS?*&k}fxu#NLhJih!MQUVWa~3L)NWosHtlWw7 zOr(FiE1Wwq9%LMwMN=68UD^EyBZtw>+<9wC4D8*BaVIrOI-9akfy5PI04hsw$+dY& zKZEtp2Qew6M!PFUG6K4GY+j8Vyj-|0EG77cLCjE6LltbqLIo11npUE+fvvbsX7t-< zk9wih+pw#@zKnpb4|De+`*$u}s0sb{iKtn~(05GBm)iD#77GHF5}b<94UqCPe?TlT0$H@p$xS1}czvTKE!`k7>>INTk2c11ID% z14xYt2j9vF=sL0c8?yiA#;MC`1~S+^m+4Dt=P|J!hZ-iIoGjxzhKp+_gV+?$g%kAw%a3 zA2*fC23JY6sK5W2nQ zl8k_^)^V`tGac&O{jjT~oPDV` zccyq3ASchgBl}E8dD7 zPipusE>l1S5{AcOxw2k9T=qmtJhQQ3>DgzRyEe)Q=rYOb&Dp>3aj%kR6T)Jm4f}_j|Iy0U zPyrQ4T=|l~m6iE&tO+GbNd}VcO*~NMU4aC287)rc97g$b>krdJW6!Wh%oCk(q1#6@ zVe(CVZvEV3?DTOm+ICQrvkOed1M~+Xrb&x))k(ygTPBh9_7A`BGS)R=q=yh$^8*Sb zEPa2XnU{Ox)@r(gWpT8gxgTaPgj#yZ2{|o(5#N+Tk9SN~Yc(Ozho;U_=^ z5+UXd$e}(Czs&R`L?`biM(@xEUR4?)BcN+_b|Z2MiN!shyAeX1^^SQy{{wHe4hc|! z#H^oH=+E;QJZL;kq-@Ck&7|Wj{zFAC83A3@dNpX~=N@?8FWQHt{Q3vx(#b4dXG@|0 z6&dm4A?lLR1B)6z$@hRt)uobUH&m+o8EMh5zsX>avka&7=~XL(0*hsBQ`Ns z8_n3ZOS%hCfy9O%v(Sobp*UafEk3C(sdZ)cY$GtjB}L{(B;yYgdBW=aErsM3BjTs zj9xXvUN>(eKm`)L6k#aiav+}cata~#Z*9+*j!9s1CYs0y=(>3_1i4NMz}-938iku% zGd=btu$2!@1*kyccY9+ry}}>!rzaD_`Jx4Lbki_abK3_V63{h7Wr(JU?eL03T4PG# zH-$#YFgEBy0}mBQ6#XfP{OsKh|2$2*a&330RQOp>V#n7jWdwA^Xx)fh66c4Fm z_9mqwr`IIb#7p3z0*QG2$&u$S_+sYj3PQwRzRUC^-|eL&y#v2F;WxEo#&rTF}lG}<*@7H01y|ulJfUY_EAJC?;z45&Kc7&*Tn$Psm4j1w@?FFbn;`;7)Xyw)< zoNE$B2>TBgm^UxN1f3!$83A2)Hdmq=FkOZzh<~F@T!vl6-Y#Mdx6F& zl5na4&1J;RJ<2#e4HN$B<{~4Y>*cLd6m=y5r;eg&5nG?5%xaBr;Xt5^02N4dJye1^ zR43v``qqR9dY-`yuLu)@=eo%V=+eA+9d#N^I;*T{BKISFLk2VIcbIT|n416?#1qYQNR6v5n;2DDn2<5Dt&D)Kl|wF~tv}&o?P^ly4Rt{ZZl5NAIwWAdBAgsk_T0#qQ; z&om1y#=Y^i<>iFXS~r1-Dhd+{Ci%z+=u$WAMEwHe@xoOn3GuvoJTv!4nD8*xM}P_> z*pzhCYfx{zL6t{{oa6*1=x&%WHPuf>K-aR`e^IA`INa}L8X>lyiD!0w4-?EL_z6&f zg#MDXD4;O`SI^HT#Kf8~CZjM+xI5loMnKn;L9@`r0dd%L2K@~BqcElwsiD=?Uw{fE z8XiwaDJv6jceI@lGZjwEq}yRaKb=4s0bTdE^hKU;d*N}LM-f83+mZSBEldb~5g&V3;rl*jk>TP0V8Y1zcsWe?(<@X)Kv#!-nQXLO3|`Z!fz%l9Zl32zJ|Az4 zLItQm;zQM8Hj?XwYh0X2jj(b(WyZZQAzWWhK-Z?-8p?MC(b(yGJ*kl*8YrKAAorst zM1Tq;DpEC+uNq@p9X>+4Ij=D9@Y-6Ylg4mJ!f3CZ#A}8}-Ci$LT#^v-pv6%daqD z=${|~Dv(I&c{jglVhld8h~D#v1-5*j<6**#v4JuIx)=*bQGam{T)M)a5RG?i`7rW# zb=)*ifC?lQEwvYa9gW7-y1fa}5E02AI2I<%e9}%vK-btYv7%0HcXAFWJuy9bZY00t zXP9tkX*&Tbkm#EjE&iTdpO<=PLfzjDRlvA?f0$&~CV2A36&^ymu|X ziCingG!FqPkjN|DA=VA+f%*I^gy8)4@VY0%gq$62G6K4ew>u+#n${I>??zMi$GrFO zFUUPV*U3$Q3M3SxPl>8M-SM#4NTF%zum$6TgeFMN*G!os!w;p3oDulF}>Rv z{vG)}-_fO&02N4#9B^G!J?@5=hU${vb7LLM>ysMk#`ZD-x>6oJ6FZ8x0u%b5^b8s&U{a;>J-TgV9Lnz}|Ue)o#PhkQc_as62t zA31T{ZsFMlK>egl_(lo$RmfW@l9vxInKZ!V;f z8lJB!$P1qTQy?)n)dN?rMEL#c974>PZ7Z}Re^+nc7RU(by7DsstJ4_#WGKy~9_(f- z=#alFy@UcDDv)>)=#Oi~NZhXGCLu1gZ7IZ&8ei63kP*;jVbvagE>qyphcuI#)aWF5 zk{V`Tb9ksg;$m1Be*2rnE8S@(b!tyn;S0G|r{)}#5zy6bxdMMTSK!vpjf6O9?kbd# zYt@@{B!vnjT(@_|^_^K97fds$|CF^A?vfg*)f;34bR~C*!e4qtU<(VHFkO_^Ryap$ zgbdlhLj@Av-4T8}m%*;TC^7n)w@^=N^v;x`e*&`j#e<=#R$sj)9- zIu8{{9O&5%*Z!x#ra3f|dd;DoU`XzV?&)Y50bNliV{zlnPI$T|O_&Be@DmzHjb40D z9x9O7kQR;Ks3Wk!SxUSpBePaAj+EloG6K2|^hm%B+MV#vL0*J-us%S@AvHEPxbRSc z#A5F_ToVw12i8$yWov9!U(Z-NQj^&HH72oG6K3#R3F^9xC6eQjtDW?EJP?FH4MVilu&_$t6dU) zbF32{P(_KDcA-LBazAh>dnLNlpxKFWxJ3M8)hB;wj9;n?dqCElm` z3%|%X3LU~^1ayTy?v2&&L-0>ex|`8Q;V;m+Pwl`k1}c#F^S!jlWV5eu zoz&>OYp9HXt{r>hu-ZEW8;+p6G84jmg(IZK_3J|!s6e7>F(0-9iGA^Qln|#3I-~W;J5d{wOc~5fg2@8Tx=s)lKb&# zM7oTCuFOwevHEZju3toV$&v@P5!9rH?#OfoDv+4_F$$}SLhv;+N_=_kEPW^TyL?LoT<9R&B-bi&mQqGQ z*Vmbyu)0+scKSniEp6N!gltk{?=U3;6-eCL*a5401mpM9DY0&&m2ijD*gU;hMnG5L z`4Frg7=YK$rTevwp;p34Qo}y6n1Ko;m={4HZ9BjR%d0~JVk?{UMbKLI$;loBS1zxXn8t#*#nU?BlrS*M(^+M*qv zoO*>2>$019`Wv;shXxB3NEFyPVpT){-d9L-0Q*+g^97`aj-V$aplifROROdE*+)x~yr>r9alrgQG_t;qdI`DiU8plf{MFHwEV2X9ss5aQI6 zLjDh_aVg)Lg$g8W_$E>H$qyeHNE0dJBd+kz$vB=|ca#y(wZ%dus&#y@#tWL0d7F8e z=ShvUEsiWyAaU+Oji?Ir!+RTV5n|GqEMBD7O0TtyfUX|R_eC}5jpzH`BSg}}O#UFL zQCaN5LIo0fkBddsWM4cloF;^tHMj9}4%cwOT}D7xvS+TSUgCw@wxLO(9HKN$gCQ?{%Z)jvFNp$^T9-eZ%cajZA;W1#|x zw{6#nDtB)j+gwJ7Ioo~td~&U>1qR3n=t}rDT2zO7V2}4S57+gak90qF)%mkffyCwm zBSlq;7tVQ3|5t`75WCj^uvCn}o;+=o-?$g{VI3jzcpZlNv)homBoL_xzO*%t8ecUiunh z?NbjduA={|CeHvRoihaJhsp@((pbJHUtQk@Yoq{iwYp)vxxw9G36wND$IdY#tTICnt%!=%Q0!%!9~kZ`Cg7gT-P zV)K)qTy%rh2>9l!xJYVbk#Rr;5~B}bwsxyKjz2|f zbQX>)=$zr6y_|rq%kR=78;`i+Tcc==k^?6cpGb`kIGBYBBnk$nMb;L#!QFb%8Yf$6 zFvg_D=p{ii0=nW{en+ZbwZ=;iQew?K4dy+m(bF@Cg$g9jt@;_MGH!#L*VB7mJjI8( zO|I2;E=*NO{4=#z76yk#LJhF|nyj*%LOd_JH8i7izDNY&E~8%&@n);G;b41KQc zKGsi0K-YtdBawQL3m*QBeg-xClbKVbhN+1k3l&I=8<~P?SG(d875xl)=PzXFoWba$ zmyCcecIG`0A*$L=x$ zx-M)ugwzjOVZT!Pow#DyR^~gYk-ONPg$g7-R3AVpjn;T_5&ce_`681kCD)4gX)Pn5 z>!`(5^vSXnUJypV+ebajWX_Wsx5)1YR3Nd&_cBr`TySa!`rW?&zst-GQlmq?ql|#A zKJQA9I=&?~h@`(!IfpJYbk1;et0M~)NbI;>gjDmLaalC|je7K~kfC!1CwJ1Z7yMh$ z^;oACHKsb@i=XLlt%p#^&^bewLTeT(keGbD8r5EEh3)kzF(COF<4o?yzs0690=fpq zHY4?IM?AbQoilv%ea6r^gD~5ag$g7lT={`ipIhRP8FbEo%Gjz@{XrG3RfUcW*mYh1u z0k3OE=Wu*>GjoX4*hl7xP=UnMb>^JvA19nWjLzYnRqL>H&X9Pnk%0tsrH49kjZ5uu z-SI1ga2Iu0I%g;{Xk?%QiTM2vTwQnQnT$g}Xr+vRuFKoHa_SFOxZubxLX6$&$vz`BVo$AL zpaO~4o1!?CgDnmZr^K;&D1D;;8t%)|IfDy7lz|E) z9(U})sruUBemyC1YneYw*BQ)H!es<>1$U3*8pm1UrR(U*#L~|GEL~?CLG#$vcF->6*-o1A#1^Gi=&q#6SfS$413- zsuC+a$dVF0bAnkTQe(vWYYIp}*E79jPW{>(XSP^Ih@E4C*>|MIQBACX3M3L<^yXA1 zR(NO`U0X_e6vEyj*J^~pAQ=H&Q8)T;f&an2MOpJ`6hqAYN>e$`;F8{<98^b0ts~}snNn5=T%a|@LLc| z*TO6Jyp<8q^~{Xi4=WQq>>XVZj@}={(tU#-!`>;O0*U=qiJXct!^YwCH1mj>0G7@f z0`|M`kbo}rw?s~zV2mer3?oEfY5+^;42pLyJX9dzdq07zU0{kkkj{(bJ29jk`;*jA z#PyUB(B6-X5R>BXroo8aP(-h`-n<;~JLgZJR+ zG6K2|u8HN;g@$-mBHd{SUE|HtIYaoO={!^*kv%P%Q++YUo&Qi`S7TdNT4(6HK}JBA zV`vXft!Ic=hSD9A^t86DlGK7t}LB1SgH>2P=Uk-U&N`%sfDY1>JS3=X~}*dHOgvoWCV0&OjK~{ zW%{`A0^Rd6`{~3MlNuKuH|3SPhD(Y zNB1C)Ih(V0$v8|)D|o0tqNAHXr}8ksO_OO~oC|`1G-v4lLM0=h%dDdpr|zJO`}aCW zh`O-`EZv*1I<4ZN0*U*}JUG=zeXOrf`}?fjsL8%2{`&Jxd2iXVE?3 z(YKp zuUda$=-x!}D=h&kkXXFLhO2$9iwoR05~6BV4MRWYU)_yl1awW^XTqsJYT*UH=^p)+ zSJl$9LFz9<0VZr-StZ2M|Ji0yA{Zkp0L`MnG5U4mDDb*Tg^6G&3>S5;Jtp za3$MLfC?nOI5nW!?b_Hhurnb-Le4T)cALs?Pl_=6D<@#PROqnbV|3aUUN6 zDv&Vyv;)@iuG^?gMwn9PoCf@7`krB|v8Wu-ZAO0?m>}O7hx`^cpx*z30a(z&N#KL!X zBa44Di~imrgkYatjG)gSZrws<1azfY_Ktkr>YLcw>p(1{U$XczJ&--frQ@Y z1VwTE5Ak4Int{BoyIA>*j3e~8oPe&LGo$lsEIx}pp8X&-K7LxF%p*0{B!>u4frQui z9{D9>zl%H6w3BCTPPvlK8Cs4GmJ!f36yM3OUauA(Sknxo#j*!VI%nWb$on5afkd}+ zxATi%eHGu<1rfsIsx|+S)Y!EsP)0!4iCgw!l}@90<)3zh$T70v?~@u&+ye!uKq9WQ zome#Ji@5i5JR$N9G1A_I<>z)X0=hW&7_oZEN3laS?H1bbf|2$ny6kQzKm`)Zta^&Y z4?c;>Kj#rbPkS8ymW*TTEgu;HU3+dU6l;Fei@NRB5F*WI9A8Mrq4$rE02N3K3YjmK z#H+BYk60`1P1tFA$_VIk$xjojXS^4UV`(SPXlVvqyPKtB4@LfWI>>HLF-3M4GM+u#zHH==JR znt^mmF_7{MDatw-0bTKzU2t_&r5Jx=4gEQ9?1yknriBWtCTOq9$je$a%`iSaE4qf=a@%4R3Oo%zXvY1suGWC(hQ_^ zlC6~e=-&6HjDRlfI{~=5{d2Ln{5m1}wXqfGnoLM;0S^^O9M$m0B^zFe>ju*dWQ}%9 zf!_0gUl(KqbZvMOj;qf-6<>F!8OYrwP6EB>4&yHHP=Q32K^QL4s}!@+pAmxbaTSWm zwZfkd$_VIcKTv_IeV>R4>uF!k?TxLaYt??tK^`iQm_MmAE?M?M{B=N0i2E1YO830= zgpD!+y7F5`;p)ssqV^3f@|5~}UR#0A6Ej|K;GqHubsL0>e?AkRoTE>|bB=opzsWfM zyE#)vK$lrq4_xh1A$CnSBgB8>y@i*g#?hNIc&I?)k54yTGV7_>Wi)-FPta*6(C@@! z*J5M@blGo>#nrnWh~?*;2=OT2PoUq48_J@2s6ZlXVKgrO_*i6y({+aWYXQO&GLHS# zt}+6;dW4X-kgUqZTLyH!q1rkH8<8aCNN8+z^N<4WHB+&1~1fP#e zNI=(DnqhN#i>DAwnss5k7jS5-O0WFiOHDLmr3&H`5g_E1yt-?oC+d>d6S` zI-AxPS1&6TwanZYjtO%sjk zIDesnjN?*FC<7HpEV&hrOFG^aALdgcC(Bp*eDLdr$Oz~P{EwuHhZKmDJJMZ%-m$&{ zoijYVID~-;Bz$+r;*y*~vFkh9motjIV@W^f4W=t(1a$2d$s0ly*TozC>25~K8BZaX z)bMg#!9WEP)n|L);&!*i!%bTW;Zoj4upyt1->=eS1ayhk-EdX!YocR6x+`NouZ=+0 zsr$W7W1s>F#=HwI%DyS~soO~iJ8c(%zK`=b;<$`}uDJ7&xcZJL-tw3BqmDTXZ%B=p z;NuKbAn_=d!6mK*;`MWMKPvm6gFv4-&o}4E2bU7{Y!&M!XqRDQ$Yq{@(u~1LOam%`rfeIuB zD1C6zCQ%GgpC-hZ1YIfjDu@j-0=l%8xZ~=aT(R&l-F@x*Lr0+dQR7k@7^pzPwbTt4 z8{~_3KI9N0{o*g4&ab|PYp{@ju7+ACT+{BV_+T>KcWyTN#nXKp7ds6WDv+4l+Yy(n z;KjZt>Atgzx}K+NODb~uDI}mvfh}=$_GQuhGu;i}A5+iM42$kWJr*jEI2>hxi+|;b z*F7llw*PZpgWQjj38pdvy4Kkm;40Tk;)r0H18}uM%r)E{H)lY06@r^A-LwsgXL_k%bB*7N9p`$;8W|;nQ1m z9MxGo&3J8n<02!V>-45lv3k?_|D)=x!=m`!Hm)cLN*gS&ASEEV3n(b;%$~6o1Vym} z^=mhFqGBf|Dgt)57zjH%BPd{ih>3!MVk=@`@}6h-UDx}}-}n9D?Ch{JC+_nc;Yl;d zmt5*~KnHyWgOkl!tYG59*g~P;<$1x{1@a{!OE>Ah(l+w9xycCZ8o*x`UYMK~ZdgK2 zrug?p-Cf#-;XpSQE0}P)cS*Q4Bwf%nDWHUr&q8UQ;m|ox8G&89+wBv|mz)%A&p_VC z$a8@-&rm+ilf?=qj&0i`6g)gD96tehA2&8e2k+WBZGB_}b`kX&;pN}sf_Zf@CEj%H zt;?a;@$tJiixo`RWv>znqt6IuKi;9l)Wbf~JcGWuzl^}Hys%M1`OIU&qo$|J(}u@5MbsH>}k zzE6rm&0+-;6UPS%w>q5^lKVoAY|y)tSuhWmQ9n>dVArw(215C`!@}du4=K@X+=(oh zhZ}MsfW-IKON>~eHX)xUgsP;eOpxxTcUsw~(a zb?_^ zn5gl)$lmflEG#(zZ46y~NCRuzPR?=yyF9vX(UwQ=6_$^NHjE}7)<7O^YkmNW6--!* zo3w?84+_7dppEoq|1<`)4PmufMqt;z#lN*LZ|)Y(OXs-HDfp*(LEAVUsAjQ(iHD!6 zwS}$+1ec9)&qD^bl=5(0qy1$Bb{ULSk@8My0w*P9d+%+@?5Az~{prVI1rrmK{Yb&C zeZsz}@V|TWZBHrJ$IbJR5!mH8Wf*xOqzLN_fS93-W?(OXxr+~r6-*d74<)y3_6pCc z;5X=zK9@14-_^SYbd4MRE$j*^UP;PBcL@!hfe5@km-$57a5?VDVg(bG<5rNu#N9#% z8+iKoKS^XPX&a5cyU7UbYE`+LyiDIII6jAGV${<_2KM&V+3Ch&1rx?y(@0^XG@(@~ zJQHnW_cO4^#Jii5jKHq`voDcy{~f~lPVj7x?6;qRJthI4o3mKK#MR9gNa4~HA*m}o z+o!d?$mG*D9$4AS2<$rDr;xljoFuI71@EXrkBiI^+D7{eb}UvfF*W!WDX2*nnh$|@ zRN}~drXKySg2S822<+-I^A#y~-6l+IlurrwF8NG3ZDYWfCM;Gk5#>-$3TN*WHao)G zdDY7j1~OiuzszL>cFnr-o0RX`BD9zUeTIoyB@AS|#_Tp{v4V+@>Yt>bYP)c8HS`&# zA9&9|p8B|xv5dej%O=KLxy@!Fe=+oZa^l`I>9mbDc@0>sVB+*CBd%~llCb?S^nD&L z_`|?nviQO}G6K7D@~pV>#En9$u8@H&Ab%LxOLk#S9TqE?u!yka3M#e=4dy`~F87=v z3**5p_FouGV3+cj9ar8cQJ9!_i4y8WL-qk}<6B%MgB46n_|KLrjNKyCX#m8Uv!+tM zBlU=#;rR#->_0uU)Z*^@3GR*uLgB47KRyuP9C7Xn)-JusgG`O*p0~kK0 zP)1-^&qLl^dCgj3a1D$N8Z>MyWe4Y6FJQ2O35VA$xx(HXg}E_61p3=b`I4U#but3G zo;C~Q%4e?;{^($g)B3MM%0TAa%VMyCiB9d*TtUGG;ifAPUAH;1u*Nd-_E8yuT}Pj_ z;a*g&5<)ve2J+!hNA^0s9~0Q43|27F?@usS&}F^Q{W6Tzye7L!_oGo_l8nGE#z4!x zn6Of4mIh8)I)yw6?3BnB&(_+-j(1y|Mz@0@`szTm;a-q(OL2{Hn^dQI)Xl~*hm z)?eRF35(etEbQU!|H9}8J$c4n}Qz^;-0-MR9T1fhX+HhFYQ zKUPn#gIV92!3rkqnuc=)>XpKt;V}D=^jO8h-q#-U%wz<1-JI5gd(nG|z|Mh0qva+Q z`-`?QZ@(#n6--q3h~#b^T`n{Y1>)u50Jer+M~7268cbkU;m>HUykL>g)q4>oIu8n9 zVSYE&JzIknOpGq4Yni3)PgIIX( zhQ_rE!wM#rtm?%T*e(&89tPrgP!J36^W;aBnV7(?jo)IpoVjy^8LuGGxcF%xTS?nU z4EUaj6-*4v?#12Ov`Fap0SLv!K-P@5@%G5ZEKFcm&e#|(chXGZY@dvd!i<_lj|0MYoHKMVbmMavv@n82>)#?johK2wCKHLWONp6t&;zr^v5qYf*W z@Ou`;rPP}vwEqD_zfQiaA^on}hjx|`*wrN{ip%LXQTY4MlM;PD_^=hUjfGP?>#%}} zi%q(7$v?+mmNb(P0G> zJy&<-Qr^W0Mco}J(a6l5g}twD?yiv$*v0qk%;g7;7Puv_V$$$T3l{pj1tn{ASdkH( zI&!p}PT9f6e}Kee-zNj*C%O@|dstmsa- zlsglIDO(@|IeWH}g?%jT$DEZB*p;wJ!{yiy6IOMB6|5_cN*4C9Oz3x3hZRh$+ZW0u zUmGhN?f;b$_3k#4-cgUQ=g0```tB3V*!9t=isW4GBJ4XIM2TgqMdk`^<9@sij}=V3+y9v)kLoF0dk-1NFDFh( znbdhK&6l9RgHRQ@ z_mUCV^;?-luCCSyPEC(e!n^NS2IhB%F*E~-6imde-%54`cN8k;UWPVWaZ)Dr&N-Ta zL`9ELlm3E?tO}NnJU~m&Xbw+W(kGQoY*=>0}2bR%jKB3H=SmMyO;2 zc2(8wPV(Y{g_eg0P(t&cjg+%IUSGvy1rweX-AGzsz{%OwTcT@Bg_v#&!2QoPNF!FMIJ3z8AowYhJM zHZRgo=#&i^$ctw(!XUpp=}RDw6-=nzdTCRCw-yxTkbzuqa(gDM4?YMFk`dVTC&`4# zYv(Q8@PZ8FB2`KzdwytAfQ-PdIM+P=HSZR} z#u?_6_}=MG7GzR;DFS$`U?RZwnm*;euh2dMGLURqV;$_H@4ZGPBe3i3TpJ;;xrEbMeA{H zqja$DJTSqF#|kD^Rm~Do&$ilvkbLZ{gAtSKszVlWg_qRfb7z)*NllbM* zT2YAT&SM1=!j8>C%0Xu#zwiPj&W+xwgM3MHwu_9wt}#E43wfWK3PbE}QDXVXojS;u ztRLgTV+9kALC1vDosL3b3#g!+wE0ABy^OmKG6K84t8;{FFRX=b(|=K7_3{(aTG7cl z4m?&c(L{AcNLg<$#2Y~dGB7~ULB6CWN+Bb#YkGQ#kayogIOYx+$S!t*w8nDzyA6*O zOx&IIP)J>(5aLfeP-4IJ9cit|uD!L4z^>{^l|tTiQ{nGLH6?z2Dw5WUF1)qkv4V+% zzdj17Gn)#}euq%vMvHQ3&1+S9BN>5R79RD)YrKiD@?In*5~^S5AYamVP$M2InDD(_ zS48VSk~lPPg8=&SVmu#2qEV+9j^Pc#yD z4Y3gB)q@P=yvn-#cX~fq?_WAhVAs=4bdT`CdV=}hm6VuWSXauIEMN3fhZRgjHfk!S zMw<#@Icq5KF~CI1mlU^rD20n z#h;}hCx^O}snu3kH6&8P<7^iR7&2J-9grc%D-q57H*E0~yB=qo1s*Awa$ z!-yr}xjheK=j^z28G&6Dms*R7pFZlRUVKQ2m=pH=OL`s4OV8=Bf{AHQg2YtUI>Ip% z$Uv@c=*+`D$j4Ln$O!CutJa8{@4wa;eE&p=zqg%uSSxB#zFUVCOl)l*N(;FE($|>5 z2-1H?3n^z=^>Lkyz%J{*oVcEUuAearGLSO|wcz1Bs2R0RhZRhu)gfY-%~ySskuVZ& zxUr>_Nga51x{Sasn`WKG)dwHzFL$<}L~M_i{CnC)Me#HpRxr`ew4)eO^FhDd21fLE zDt&k>dOxCXgv$u*x;!&n+_3(hetj%tAmfhv@G!SjP|{6@6-=xf)>YJ0ROs_9VP?Yp zoWJy4t^e*KBV;azC~@=5LcPsJ4@z_!?=NLipEYyQWgrC;#x)TlDR`!L?gTR|nMG=; zF6Weym06g;uIYx+;`$-^`d8W@N*r0P<{`rpl>aFUE12*rixR^wl;}fG0@0c7c!B@j z-L}ax0=p*o^cGij%GSqx)>5KFoj@rY^=0>tEUaK+Wu0g-IQ6c6qXsgNx$Z$cM#+YPb1rrB`_7XMA3-qb4fSB4ph==+mAG*{J z!vuD{@arRPvAL++ z6Ec@^A8~!nX?^%qNHn(J6v!vjHV#dX)MOw96Tk2G62tmj(OV1vLR}OfWm0Di$&?Y; z)!H#eTv2gUf4kE(N_3bTAZ1eHL$Wkj!9>ZYXt5QWrMIyF!d0Q>J?Z^0<&7CkVAr!r zy~One`}O-0VAfgBt9TPS&#<$Z34;|(yy?(W4D-04-`fX>yO;c=>p1!+Kt^EKoPRyU ztrt@Di|<0BF?^^W5BqUSnLq|Bn0WpuQfy~&THhcE2sYE3&!G3C$HIX!0=stJ=q_$d z-L8LV39A6lW4-wuw2f~^2QpZ}MBmflBKzZr-tG!yApf{}@~{S3v}&=8z^>vaUBxxa zH|c+>U^PRT=fOiwryg4uGgy%kH#&=L%J%8mJCK2VUFj;lqxufrDkF-KOV_HSm^f*z zK2Qa#GLN>_W*~o20xOt!scJ7W*HZL%#3V{&wrDQp`igXiWdwG;xvv#B^;xd}W(=!K zck&&j=kDrrhZ(G3!txm-YELHVd#!;CwK+ z4IX=o!3riCw+#^6E?lE;n*ZxWtjB9@umdOd^+Hu-PbZtIX-*OhLT27*sBVk-S z_COhf75sO5fASKUQOooP3n2rUyU>V-{k0GJe3lW|H7(subon$$zhKEZN*wnz;$eSn zj>~5TE10Nf>?$&m@%lTr&QhY4{XZS-OH~@yVKISSjxI{kmG7Ysjf7R?MU(&PU|;Hu z{2B%;m@r>$C(fTZO>esHA|*Trew6A!_K0o3VgkE<)mV$l^&Rx=eOL`&o&G_p1Gyor z0gDw(@bOmSOlGuR(-4T{+$TENH!*-Uml4=?xQ~hGI;4$$vJK<_j=Mk6!MfVnrsgbG zFp*Z-K%8$rP~V4z9KgDvH*`>+VQ^{_8G&7WhyN3t{C)K~jUdnP;>vZY4&;}~O<1g8 zBJ9R*VNPkJen3meGraW6&_OoJe4d?*z^-!NH-c-8lYa0iNLe&Zx}<~tu3;BD7Au%& ze(RMm|3o`|Ty`#es@Q|lxb{O%a~XkM^<3`=&IL{M{suQG5qJB5w3lqoy5=laFd_UY z6y`1n*6%Qbe92?O%{o|jem}=eMqt;ZKAD1RsscZF+&jtidh5pRnrv7>*CwAV8KnKz3SOt=kOEzJLDt+#o0 zhZ3zW`%2^5Vsn2Pfn6OIj}|&Q=JE^fLI%?9zK;&-Gu*%C$6^H&FWg27^L0l0j@Rx| zqHupbX|JU^MlB<-YxcU6Z!!#$XU*;yNP8{cH&U}$!Ni2KLBf0GH~v~Aq{7M^ z&t$>Aq6U8gWCV5%`dUv=58lR4TKA9=`*xkmf_+806SNK_QZUhPmx1sq^D(cS4WBBn zIyei~yhhdp$_VVrzp`7u`por^@jT zybSw_)HDN$3GA}-e$Q_Yi{Og{Xd`r|>1EhAah_%%v4RQ5n{W8Ke~^OU?>F0@hYAdpyY}6s4m_I#_uR3ICj;}McZd7S2<&Rr z+@Ji&bItm&7Kq#po(#;3ZfNMwVg(a*dixUNUrqRsx$wW+y0nK>pFzLDM@C?mv3D%- zHMe69nE^50vZqv^VMA*l7Au%wO9zw7ak;wEfAAYjJ2pqE&#=g;rHsHXZO}?GaQO*l zr5_M&^5#gjEHZLDS*&0p_2zQ&{#Ar7%?+MD-}@y>^%;Ixw~!IorSG+y#Md)s>wke~ z;?O~f4D6NpaKVkm3MRZyrV_h3ewklBz%%jg^L-4=g&!Q}BqOlv`O}M}|CJE7vL9q1 zdzI{CU=HB8r4x%4Og#R3p5)ksFnb5Wvt1M}FtAtVKr4G0fnE2m6p-ah#<2fJ!#irk z$qP~)NQ)9X7Au&r-G7sWt;u2r#lbtO>6bj_4{hT}TvHi=T_<0^BoCa|vh`gc1Npf) zk9k6`U*4ow9f!toVu8hF0L#wSgf!t&7jf4!O_~f@#2QvF; z9TqE?*lc0RnZ4M+4o-qT-0`J`EYxS%xa13i3G7R+Qn4y$ayH`@l zUkP z7dE9M5Fe&HN?Fc@;m2eIc6nWB!@afi)Ls}58OS8oQL5=QAmb>56-;z03g$M%8fwRt z!C1|vrz;EdKIg2q%Lwf9eavz%v_abIIWXp2VB^Zdyie4!BnB&(AQc*JWulc<(+LP> zmxollO%bwGMqt<5s1DqN$y)8eFWV`xX|RWsllgTvfx!wU#E5p>0>MFRump&u5w&$7 zcNE6T2<+Nq*_A6x3)f!y0doNL8hK0kl9KyF8LVKULEX+=LS;+s&uu_l8sx`9pJC_H z5E+48qinj@<3DVi*DK>sA^cnni1v6N|ME}3txOL90wdt#2_T$to6$^a^U81>+ zz^PT$+JY8947?f0LZ9K+p(8-pO}ReO#Ic-HpoKl(&a!rG7Dg#&+wReB_pt_v3qZBlI1q-{f&@l z^gFL+q0cb3UPTsGFp>PTCnsK9sx2r1Vt9QOt&mRVmZm%CFo9ieifC?%=PoTNYek8X zM{Daqx?ge7VFeQ=l~G)F;cD&RF0Cn1*xy&GDZA9SlZ?Qwz%EhTB<)`9i>Q{AxLe1U zg+4?0f1Pw#!NmBM-8u1fqE;*cLSyB{!d|k3j+12sc8$0k&Q0!nSlh7*RvNaPudM@l zV)SGkRxt5mUsvu*oh0p;6v#l9dAhUEX9(!IMn+&)?3B*jq{%0>RkvZqr0ix37WxcB z`>xSp1rt^yJ945TMVpxn8Ax)}nT2uGY`qdp}Gsr-4$x5lu@Z{4O8G&8wWeqne?XvbtDXd_vBuc5z5b^Yk4l9^= zem#^EJ0H;w{siK-%=mkrCL{v|lhc^`fA4Sq>|4%00DpAkVkS(P0G>L*}&N za)zGL8sCNtWcM4E(td;NCii6oc8%+<;wBYd)4u)-D~eummTW$~AA3LD(_sY@O&9xd z;;i%9I`MgwFtTqb)q&h$_*zC_SG_%++{D*6we~TvGU{=qwhrXeyA?XDVB*XhcTU)l zskJdrr-a{U0~TsJHELEZBd{xGfD<>hPO;W-4`d+Aw;Qlf)9Fpg4;@x8vFoBEmvcze zj$I2G$kj>TrOe&Bz4drZVAq>PHrzDDBdy_A$UtU(s;vV#Vo^OFE12l?y(xE1pR4t> zTu+JbLtaTW;)VQ%G6K6|9L%{%s;AmbnbRn-{_;x(<{6UqHsrB_iPlFb?AGob_$$kzO>F2DRb9hfDK&%`hNuz8UB?d$GAj0=|~tQ z7_ZY(=B`NTAS1Brt!XKlKI@Bi=So{jOx=2lfq9113MDU7(BG}dEg`v1&$QbcIa6X* zgA^%qm+9gnBe3hk?rUW7hH7n1hdPvqoU=>H+_ie+%wq);qmy#Tm5>VU8xvzn?2TF_ z%`?p2>@Fj)YtX_|Wc;Cj+HrI5(40(K{z_?{p(w_k#|kD|ojF1Hs1MpZvtCf*{DAQc zWbPuzc*zLt`kIkM+6*-y(S~qtc=_OQ3}o)IJ-m3VV4~1_8_6DBr9CqS&JEw$zMb@4 zmF@PG5!iKgY&;1aZ%CpeR#Bp?4=2qt92@A%V+9kgtrw8I_@CNiCOau%GSHTR%w4rc zB_puwW|v43GRv5xeHciIuJH<~4&;Du{ybJNanw74TuG|YZvC-<5)<}3k?KIMZWACQ zu*UE2KJ*y7hrF0=qVJ zD%OTJwssSc#_H$8_CDVUfuZ2&XuPb2cc7c!7et*2)}7H8UdIe}gK->~}D z=WNJ>!_~Bn`5rT~AahqXG?2#%CJcXR^h1m-$cO%rfo$BfFbnz&`LhFL1a?^_T-Ar@ z?a1UuW|W9^zAe>(jCTp(v4V+%@!9%eO{_`A8A!@Ht+$reCU)#l$q4Kk>)%WWz2QK( z&EAyAd1ftT?%op>j}=VZJ=8=P;nbA;aqLcsw(()o+C(Q?KN*2t9XE9rLLNAg_2YU| zVntS%v^KGn_vNvIiPkGS3d4L9#9`%3N?d&RpY9F)uAYDMk`dU&vU7yi<*wxFo~4u+ z-gu-gkKT_VJH2?UU}DSJnZl3|dlEiwD<%GGwOm@87#r*%Bd}}yk}X2WXLsT^{QxBf zG0UaQ-K@v%JXSFA_3tJjw!I^UTSI<@7odpS#Ei?7BbrxDfiMB{|ygCMCMQ z*&)@LNLWr&lSsit^PNY95mC(Cez{b=;V#kP+DB?p7kSZlWTAbDC1(N~&J> zfZmTK4hkMCn5g^afiPsW2gx^r4CKwUB5Cc;Zi2Opz%JAB&q9b(Ald(0MTt=hilnu> zQ;n^8tYE@(@<$VMV2`$M=}Uq)b8?^07SIHV2vv1}40Uf-zFK^Qg%@!IT+Gbs%Ftz0qL> z6Wb0th_U-xlSccXhy3TNg_LLbbn~H%z^)VfJVZMDCxx1mlxUS~A>|o-d>`ttf{87k zT8P6>wINUc!fvN6^_xlA501-~5!e+y#$OB`-GO)rS18f+Zc{1yQGVc>4l9`Wp6V;c zW`>c|-C)FWIm=$kGhC`nml4?2Zf$EZcv@#-))F$1M_1ZQd4{Ng={l@nqQmJRF*cVa zS#wJ$G3%|9R5SBn9OGInrQm zB_;MOXu(5Gr^OT3%Lwdh`;Zgctm#gwy&(f>$ko=VB6c!rF?uG`-`iNQ%dNboOnN^Eb_l7C9C<7>%u9ab>$--ixj?EB87FwcS# zZcluqI(H+UN5}~5>N_Z04B6L<#8^QFvLvat4&;=|a2-}KF{yJ`F}AuJ872VnZLdEM z&ypZhHyMFl!NyTy@Topz!blHFs0RA;P*b*vkE;$Vn8e5YU@C*cvqQ)6-?~A6(tU{>`BO=AWB^67|2)AHnz1( zkrCLH?9^Kf&K*SdCPN0Ys5(G8o4h(dISVV8xJN%#tdc$t-vSZnT3ZJ)BGFPtVAuSW zF=CtIp=8_02uf@zs;vY0`kh%8Rxr`9e=l*EXI~Pz35XU0g7{bTI{Jo{hhhS|hWqvr zgP#s3ZM6L<;c6Tt)laPW@;wwQm}vi}mlzu~fQ;=1MDGu^bs#zGE*eZ=SC}bng?}>KQXw!GuqAPjPtP5oCxT5J}hlXwr}puhcREyDBVuiouqXNzY@DXly#wkH1UX zDD@0ru!4!^A0oxr5u*q<2?)a?Zystoy_z^cMqt;U7v05RWgKb$8WN4iX4ck$v`rqs zU(1h^c@xOpqmY60vv89#kQ1J6krCLH9Me&38#J3lnZT+{^K-6zHT|w)o^4^Uf{6(| z+KVGrOeQhONt8(K*qn#DI3wB}mJ!$${Z}gnYv&Qg3rIAcd*{eQU7QhZ4l`K6gsl-P z#%_)yk46G<_MNSi(^oXglo8lvP}Np!9lnsXN`zH1ZKAD|(+~N6nZXJsRyArP4o#au zS{MP5Vcdj=bC(>O-jWg6bwd**Dt#7_G;>((Ge6l_$~w>ec9X#hChm?35QiO`O*GRF zQ^F$EjEC9@ony;n1a^IY;UhYnSxmZ0m0K2cG~++g@2aMC8G{u}_&R%wBQDM(PA4G) zx$T4z4>ce@ef%UNug&`#Mp91SIpY(96-?x5T*cVzg{1JrSxW3# z_)iDtd5t|#BO|b@?LehyuUkqss9+WOZt-6o)K1_=)i7AW#HMUJQ8juIIX&qjCG4+# z)IsjmJ)i-L3G6zgX)G$+FDJt@U^RTG(?{JA+Qu@o1}s)EaU;u0RDWDdj(^Cc#HNr^ z9n>RBYiuqfuxo#kiRh5Of-L$2tNM$#KG8uvGMl$%ELJd4r8X7=rX`RQZa^3n-_Su# zr?i<(WCV7tfA>#NMy(?GUm%?j9DGCfo3;_%r3s4_Ojt($5!AnylHHa-tT~vWgR^Un z^s$o>*!AG@8^QkGY7)0Sj}lufGjx!r=H2XAtYBhy{|Z4Bznok-4jIUPPY>##MoQ+s z<}w1iyw2Ydlmpk2=^r31k`;MS2Q^auo6wxa3MNLcyDg}VR+45vuT!FG`DPvL!AczD zCL^#b>{F)T@O&L9dsjdS^}kIzsPSvy;>KbH6R!;}3ju3Zk<*`UQDRw2Z3c4OVow=? zUDxOC7nGwnkgJK1_i<6iOY;nmI8PQUm^gG}ub{SGLr$!NywCgoeRZ&pzMG|wjKHpq zlh+FN9}~&c$B;sL@?Rh64CzldyjiSZ!mG|2L6x+Y483!Q67Ge5I(SEI`|BqouxsA6 zF@kd1CSsir8OVL_e5HAYQ|J9ytY9K<2F zugyf!4{~JL@9OEGrc>EJ6^j*2xc_P;1nk>D-gkslSi8t`S&$LdRt3li>>9`!3d;Dc znq(^{ucsDYYJ53&PUtYE^v>s~e>b1OM}3a(>Qi+viX=@h4y6WFzf;Rd# z+^_jX+o)3#z+wdx9oMeYs<F@>a#$Mnf*%x z+4HgCY8ES)&}LU@Rk_jB)x;^Oq6WHLuv0DEFn31zUj#n(raiptHC4 zXR(5b17pr+HB{XQ}RyUbn=Ar4-<$pr-v!T+M9T6)8}`LI~Q z#MYgINI-EiDKLRw?fBiZr5cT1ZCc6*>}s@m1yP>bLrlWpSG!d=n}L0Iqe?wltjGxe zcZ1!eCY-TYC65Uii4OnmlWH`6SnVVuu&aOPi$s~ZpO}w_4CM1c`=lC;UsO&kRxt5= z_P10A@>F158G&81Bdj=O|Knuz639SC%=|6YVl8-5hs6pe8WdS@>iUPt{&Udl>)G2- zs_B&T;R}Na?0S)4$0?tlAX=jgO8iqBN?ElJ9lkJF!NkZ)1*ckdgaoy_M2VvQwHe4K zYoE&q>^kk|$|*;lA_v;1Q(|R6Z3c4H+h+_`FtI(snNwRHBL?fB7rx@9HCsmS$A`$< zG6K6MaNeBq{b|B;kbxY3(VFGyJ(}@YU^?0v;8C`{K^$lO{bMZco~6R zp_c+U<ZXL=6^ImTcG6K;osIn|z1WNkelOoM9cKyFXmE+eqZ;ylYaEI3c* zzJo;Lx}Pr6Iip5K+Zn82V!5E<)ShR^>hVDAT;d^}m)h*oQW=3=UHm(6%K8_H!gdEG z8g}rIYE$I~EoHERiP=8wIMs=>q~GD~l<*DqmhvU4vSBg;yAD@%;gqW`kRj8X&rE4ParvnrM};!31_qeiF?ol{&J10VEplbg8Wasack*!3rj# z&-LI`S1%ERNFXj<3uK|s@VC=w8G&6ThkJAOdw6n+ok5AovjU~F$q&r^PlFXqn9hsl zRNY_}d zhyvojw*RXG>G3oZ6WBHUM-1n1LL|f2L!vR}RUrF?-jCwt<(XK)gx}R(TtHtPS-TmC zPV;K(K-OuuE(;Ubb#ey1j-YH3d@d3Q_dpirGQtDaXJG{sc^&C>l=9@mBOsdD*4BX> zWK}LBu&Yup;#qs?=cx6W8nX;M8wLGIJ*o(gMqt<5=Nitj^cKm9tD-AdKSwB8 z$f`MvIHSV~CR$X5aseyyNy(ybl-TBCBb|jkzBF4#U{{N!!JKkLA?axYCtJ_Xt<6A2 zXJ_lMf{8!$Y<0Ey4Wb`XMu~UdEZL{@I*#nSCnK=y&;k`_|MoUHx&ls?(HGXd` zyr;toChnZ}rUoi#r|El}UhCEgE10c5l7Nf% zh}CEfC5rc-mTKLX|5VBd?Ap7llqfepCPPB(C{b#4nt^$STiHq;E0}Qa`?(VCPwT)`kod0Fl;}~qLIdj`J=zBHSi!`OGsRl<>*u7EO)E-#Sa3TG&R2WD1jz{O z`uJ?9R+;^p7&!l>*}?aHZiK-+!$G>5j1^3nZyc;uO)MuTH$Vn5^~3T^IP+y>RFI6o zu4V1EGs>=SNuPF*fqXJ!O(x7UJpU8OV+9kJR&8TcUtbcY6=Wbs+?$*QHH1cG$_eb+ zysxcZS@@1@Q9uUrUf$F!s2yE8lGd|C3MS5pA$rxE3S!b2GLT>FZe>9oFH5>&jS1{} zP<}0lqd;YS~-j(>j6@&8LJ)^)fbj`^gCGO6=N6 zP!9V{OakCP@#l$9sb0qY+rB(jFj3O3gP=BjPx5-spoHy<5jvPdb};pp5!m(g_bfsA zx{~ZKOQ1wlqyKbJJ38gG7mpQ8ERLHYs5X8eo=3J&;?AjM(%OW3PY)S^U6tjV1?9vl z(tGuON;J+`CS~q|f4K8l!9=szO@dnSiEOU}8OS+fcIZCP>xiuDDkHF~Y4$Ne`SmL~ z^y@k$9*^3gyG5^qJK(}&1rwEZj|!@k&tz|-$CR*3J}%vlU$%}i0=r@ivjvAa-^l{g zYDy%pKQ65mx$q7=Rxq*DNEFm=UkH1*9({j2AbK6tj{caekP+CGy77^qtW!;Nx0+C5 zzL#EilU~PePQhaZ6W{wj5L8F1NRvTIO4Ri&lJ3Wet=2LEyI%MCEGU=#B$W(gAfGj? ztplmiSo2uH#MGA`1eNL=Ik_~L5~sVq&^@EqaoOBdMqt;wGy~DW^f$Q{+MN<#9bQOl zUNg@(;<18>QQ>t(_4)6lV-U^!SeNXqlGaiW7#hk5?3$foDmrZZLky2hq{N{SRnnPy zPfyh6v4V+m|3+d!ST%VS0X?#l>+15q>2=)u@k@sZ?D}ojOjIiV5sM2eDDinhT^@et zZzF!`u!4#Go0^C!@h90|0=sKI-7}W*S7M!aG6K7*%$ke#DK*442r`hKr;Vj^c&!G! z)nNq_?I$^is?NX3R8Po2F579r!`|1i+D9@1yN*us5Y0E%;W}lUphW3p3;rU#j-{y& zby&fK!{rvD`qm$EVlHGL|2=Lhl^R}AR&k1W2DLy7Y{?4`O<8^)ZM5!f|;Xlv12VZgoE{(ur!M%weR_q9%Ox(+Lt zc%Bd>svp%5e}!&>mL_m%syUPfS7WI89Br_|$?l^M`c>Vg(6q@1Pi*!4QBU}F0v zt!Nupmpgk4Qj>dQTT17++q{`6Bd{y!ekai)wmz4C6f%&$&Ml?y>VCxx9ab1^_2x^G#Sz^?BvdWz;pjJU|1fs`YSc2xK$q4K!ZPHt`n9zVT>jH_!%Fh8jj6ZhOOU=RxCW7De6m2;}?%y^b4m1yv zGI!%YHIWh6b#`%#Xs$BmhFC+Q(Y7E^%G@>XY@LM_O!SQIB`OS!xGENiJ2V3cwWD1I z2%(t3u0-!XqFI$Or+e3r63=J`67uco&c&fv!Nkm8w2fRN?)ncP+R^Ieuzv26++KqT z>`JicBbuK#;pXOzq(tzpK&dwF>U|wGSi!{EQrgDq2HcOcK#VRA;2{Sf)Lf7e*k$S; zBU;RE$el`!qeSA`0I7cBn@1NlSi!{7I=w{O?#A4S(LfvuQ1ec-Pkm~O5rYZrI=-rx zXdc#x3sXa)G3JqquS@Gd_WWqXUmewIz~QF$q4N7b?qsd z)imO)mMo(LS>ngT-q$nsY6dHqXjKy_DvC|G2_1phQ{gS0O+I}{e;I*YUcbAG=AtQg zgoi|9#9D72YHS6s?ayEZ6BEnAMa8y;oPH`01N(ULu=h13evyp8E}LfEM2n?nT<>R) zXpH~k!NcCy<7*Z%SiwX>{Vt+y|3=)n1Ryr~xJjAqK|{942<#d?v!iI<*_<2tJc$xZ zZ@Wroldm1Rg~19YUQB5(+B%qWH>Ly8c~o;=sZvY+`Ge+cy+(A)Mv1WzsX<) z6Z5wOh_)lmxv-uJd`0tKR$NRFtXfW^l_O#A>(pzd3|25v zH`-fNw6x%su7wQbkHbRvH6YYIKQUOr#Edvs zQSsh_)2%y8iQE_ebWkrNjjNFn*rnR46wM!5bC0cH75RC{KONNA%BWYvU)eFJ3MO(M{t*<_*4%?1 z*_3c_x~zkG8GeCwG6K6cc6lq9ziP_OUz|sYe=9O{P%mR>9Xl2)n0WK5LQq_8%$aP1 z4CIBuhorikQAT+0Ma5KZyuEHdF$}zELJd4*}O=wUC@MEQVto& zebtR6!9@G*mjwlB%JqH%8OW#3 zi*%6F?>@#;Mqn3jb3icnS8z)gK?bsGYP=3|`uAFTvRJ`H{Zab_Mg3-6(gMi)oEX_p z2YX-J{Pva+*ww&tonZb&!L7ViOo`T0`by_be7oSyVg(a5yVeMbYt6WwId>=_mitS4 z`?&XhG6K7jOvef4>9*Wf-91Va*7!;D3{&^}u~@;xmP4Zj#VQ+a`zc6MJ-u%z?R~u) zs+JMh_1LA2U_Q%^vyOrsS!jJjsV-+mrHaK0CYq99K@p+gYBZ1vd$90)7MxKv=xugTUAlH}hO_K$CU*ir0vRJ{yyXgIT#T{GjNgMc7-L9lvhP8>9hLj^12Qaz#35V`=}jQU=nJW+1VGiR&l-@rtc>+{gpahMTQI1AAXX9pnUd)x_;! z%@Z6rHeb4q)^?g4dL3tS16izKBIjNbYunGBOFs>5-1@Rx12wj$w2>3owQ}TIt$8O$ zu4f#yvG>6q4b<2&c@e;31rsJ!tF^XDC3j>1v@vAc4-KqA#-CEl2<$q3tV(O%usQel z5)gxtc%=_9zuBBKNCl$n z72#A^9@eic~>A_zK>)s z)9WxC>ce6M69@YaB)0!Ka%Y;rueNOKEU9+F;T|nz1a?(bEhiSy&Rkp<_|-*QWg^a+i9`hI5Vg(a^ z@i&O=6ld<_PI#+zGt6Te(%(nVD@|ntc9~xT(Yqiby=)nV)$kYPBGt& zdr=6zzF{p4rEJvAm?{Pn*rn=f$61W?;I_DBP{Pl^kcGXk4^Dhxu!4#33kuFw+k$Ho zdx;X;{Qh?q_V4F10=o{;Z`Rz;lRG^EGLRFEOr>)z%lbTLu!0Gv?#`T|o;#Ou9D3o& znbs`y8IFp#WdwG0v+(B3Dm}T_5s-mQTW`(6JVSrC+YDAPv7c5IQ(SfDW@Nxn_d1_5<1~QH18LVJJaZ$x7R(f!bYaj#JTkR;-*gDba zxQxKAZ?SDSiVtYHYPiKgM7M6QAb?bGG50+zt;Qem8Yxp%3S8wL?Z= z*NycoXWrJEbF)gL#P$*w7W#0LmTqUTf{8;rHJq(QORgvpGLW0bc(Abdb#&-58G&7^ z%{p*qf4#Y4b_XTwy*=1m+D66sr3_Xu(YR4NPEpj7J6W)u5?SlakhQExw}&3$gyWsEX@1#%(9db*cBYwgEQ~w z#~u6ziAI+(Di-E_Twhu+Si!_kMI@)N_u<0Qftb1`K&s2>9(F^63G6Z~h~~^p{5jPj zNHkh&16b%Y=uhTru!0G*13frJsSg)C3y3NqQ2Ko=P9HBLuxn;oZ_fONKbN#=1||5Z zfov*mV?f0i4OTFrpBl|6cKdSAdI9mIN03yP)3o=ZFic?A;uA5P#d;NYQ$L0h&Fj== zAiwRK6^0c|G+NM$vmN2bt=@YdeH$+U|-M5iw-nQ8oytRK}woUQjIftH_%?T7AS*?tc?r$4K_i1AY>_zPt! z9xIsenERak-PT>~9SLW+#SVDK`0f6v>#~f<2<)2v`vs|a(oGyN#)=Xv`#xrR&ittR zud)M=6->PSdzTa>bQ2S6Aj9H*=_9jBIG}62p{I<%uJWJvNc)|gMWN1bx@!4gS1B{) z$N}BJFEKn;FflDPmxMB%Mf(=8GAeq%V;av0(8c^3EF-Y1pFdTZcs{f4X)c!!`h4TMo1a|d_&mg^dB3`or;=tnq zCY2n>9Px4tj}=V3+<2698$iU{^V2ACZ_aII?1u)-X8Tbx0=xcdkCNTtj99a1FC|>Y z>Y2x18!*%I#`0Lf#0V zsBCm6aT{~u!6T;e@%}tkFyU7`v^TKjW4S#l*v z^iPI*WD(cmnI~&4*q|?6c&uPTsq918UP0n871TV-T|1e%FxQLi&b5*e*fn8SZ?fP} zfOx8393>{qk70@rc(Lg&!8}$laqehqQqwv>EV=-7quMDtGkwo6Y)yo#jKHo4O)IkV zqDmZJHVHmedtYYeYlh{1xbaxQ#Er>ja*k z`?aO7`1{0kN)*NCYyK-3!Y=4rrNas)nopdh_1NGeP8~Xu5{JZ0%@3>b?63ZM8G&7q z#uK!s&v}bX#u7>>6Sr$J!YSb==&*u`Gw)N_a9eLN;nhM)bUatYqy{PYop)6{eyZW8 z==BXxNt|^LapHQ&GZ=izW~Me(@CzOX$q4N7*-}hyGTlZ0x<-^>v$B~sfeQXlVGxfM zOqAsok#?7E&KDY4?ldFG(Cf`79rL`GoO&f!-{_Ol3a&*g z@L0iw!TD^`c3_m)Z8@A8+ARJMV{ED5TfPsK5!iL--38*XC_;Q|cbgJlyB%WMs}wx{ zB$USrCK3jnCmVl9iVb7oEYmfOb}+n|f}dTNl@Zvb3Or02J4A@4Wss~%xwoCM^;htl zDj6Otn7EXEkgVJmDZUw$O^HsES2Goj6nw>XA|tTNirqn;r-zHlMJbdhY`dC?@Kf+h zG6;_qOnmvAMEa;A#d%91(KvPRY-UYE1s@*XUPfS7QIpjqyIZ)({9QqbpI>G%Z+#W~ z*f#BXtYBhM#Y)olYIm_u*;Y!JZXCcIF;VaxmUNU6*tI`yCi!04P4vAvmJ;jS2QVLL z8x<2f@>s#dqyf{(xPEk{EDnAj{_`}9ingJe(pg4eSLw*UM+`M)kaRxpu2FPI#j6(MHc=tzmQz-rCiqEGtq{NdR zKQzGs3O;jjHy$gPXdhrfe!U16Rf9b!k+t-Y#)`I4=S{ebz^-wxZ)qD8brD6Mmoz)r z@7O`j0oq3B)o>mwn0V}dQ#(IAT#PzvLEAX@++UON&xVgY8zCdG>(|d&+O{EG#4G)B zXdAQJ`)Mp`8yD9_@L0h_RLKnO$ur%==?`isafF=~TIa6~-?BzdV3(QYLH2HPXK~rQ zbV>xi7!dk^{?30th~Tk;iI9~2Y*(9Z;&O-2l*k^HpSj3F!3RE+6WCQRrB2rL@Xq4+ zo@XhMXmvBQxtD@3&5q!)f{FDz>SVpU+Eui#{y+()?c}WXRW|&wIdTHK)-CIyU%IlB z_$`g6#IkKuvWC-V`=ibgJXSEl=S1rNkE-(wh+1i*u+mXFC`c76s0c_en`}bwC{pb8 zD(Y3SUsVyN8K6<3y-HV%VVTDm^X+ zTkof1{`Ii4koDPCH0c^EBcQ9>KnJu^HxfJVwEBjAlgR z+jF`S;^6W`Va6w0(RxypjDW88Z4=RQWd!#45=w|CI8oR`#@v~U5}^W#;ltvQeo+*8 ze`YU2EXm6iMw1#-Ou}UZba}O$g%;$7V<+EM+7>OTzr_;mpx)=je#50Dz8wpoZ!fkcjpE&ljaiKnexM2JHl z?ZttlM(j;P83A2u5?t}5oeF&A%tk^m5ADT8q{f6Th9XoT@%yt2e($KnmpgAEM2*-+ z+(>HNJM>e41a!SW=8mU64aMj4XqI#0zBXbxsj)rwrvMd5bPaREAA7O*b;VIa=>B#W zEye@(NT>cw8-3{oTL@Erjvkg&84!1Z?&*td#iIa5#ii?*c3 zvGIb8fUdqSES|kK7%Kj;rx6)Jup<1v}v zD;kE6*<^f^SdAvG?(-y=W;5w%{&3c%NEX)?3(nq=${iSz9rv}diAq9Wtg7B-ClxQ+LUVKMt@XgCgAOT$+Y|`;` zJ%4;SgCj&zYP@Jmj$@})X$e#y!G)#bx*37^);dZEI}^mVWIWcqj*}74^(89<=W>4d z1z95@_s$aWMVW1H9#s6fJDOB&V`1z@Kkln@OQ#U`YN+0D}PkbtgchQ0928NMX- z+lLSb?j?wwNR0vItItCP5+A+Oaeae7j#p43{Rnx_BRN;q2CM=S&=pffj-$v2pYfz` z?YWbiApRud!DT5GP=Um{rs?>LpFgg)r$n1g@#0HT!)`8;5zv+OG#%$Obi->}XA`1t zX1r)lj-#?QRzL+3l_um|jrYSvpC%IG>6KWq6**T`<186SKv!hjG@Rqt4X6L6$xOZV zv0@mh@%e-$0~JV&?Aa4*j`(8T`;@3sMvDfd#_I}BMnKn|ohdkHyf<#qm+k_zFo`C2 z*>>WTCOiWbNccYJjy3OnaAQYG1pN*dZ;^AwjT|QK~7D-h1MWH|VZRM5R*HBgYY9 zPWI7(0*OuqvAAwnH@s>VC9H>qO4qA#<%Kc=y4vrG!ny9ASihR?E|q$QiXo(i_GTdi z6-X3(iNKm&-uOcq&`vD{t{WIak`k+cE;WhBgnyxw|}Y>_ocTx8aeObWNPJ<2C~oNPL?Z zj5SVP*x?UNf5uMfB;Fx4wvT!xBcN+qupiEO)&(EX(p^h$zLWTijQP$1uNbI6qT;v@ z*7Wwoi>FW`F08He*}a$1C?lZD=AsAAb?SmAM$`S;LesX=XLn>|BLfvk{1@K^Yu0te z4~po1?XC$9q9YlPZUKfYB%o`_;r2MEcV}GChweM?W*nrsXhkPO7Ala)iEf8Al^)pQ z;z>eGval9w$hpdhvyc(cRoBD`=dSC7v%1p#^TbD_H#`~h@(>FaDv&5V)L41jPDVi2@e9^Cr_vptET%btltyDwOUC29XvabY5`*VjVNGHeTxCIt zv$vXvKS+(&6|H3ibglkljB_pBaf;3bLR4&PBDN#Pp*Yo=g$g8w+%>|Q#hr0r0!=_< z)PE3M$arM?y2%LW8Zqq;%1Ly?VU0AG!L9lr#F82h9Nbu_$cP@lkfy8?Uap`-W3LAS zonM_Z_LLDLfXl!6Ta>%FBi^)&=1cT{-WTX^E&H)63l&JLuzZa)`kio-Cp2Gj+vuV| z?>}Ay`pF3Bx^U?Z$|>uBV;X2qCi(CM;Tsu`0((CeDv*f&c?)THcRZ~#B~Do!7Cw?1 z8X;IlK$nhKjB@om;L+b`-lyT*A)ysH4vVe9EL0%Tvttp`%yh%;+fw4lxOLKaq;6(q z1aw_$UVw7=_PFyVnp-k-StrDh8tjg=A56`$va zaw4DeHE-;n^j|(37oqh)=87yM-$+>#bnwJsKb!q2fF2~ssPpzX@2A7tL7&_n8 zf5)*QoRH%><&~@eWE-t5!15P?XuZioL?O^Eo)%Gj} z3l&J5T)LCf+;zfTZqsXGMfMry1vyu5rU%Lh=xVmNh|67XkKY;5MEs;FXBawX$cqSM zp#q7$mgl)IR!(@jAHBAp>vD~ua|YGHZZZP8mYulG<=kzB4NPgWzsBtvL+1>ACv{_? z0*TajH#tp`BQ{pj->6?MFBm#!*b&-AMnG5e@7G+8RVzH_JpHXb(BcI{ukGUvy0B1z z!~^|TT-_1}e7c_g)}BiG#cn>G`pwA(m8{W<0K=X%Vef8pS#2s4{@S%pK(|8SUP7|5#_`}1rj4u zjrcDH_SiI&&V82XnXz=vP#D%iMnKo{NNYZ)yd^GtKxbqXUrpKXWIV#mTd+`pM5&<_ zuL)~~7kN;^;Xq54&Kd5I_ijT1x&|+B%iB| zvcqropCd%`E6yyP!_A1)l@ZW&A+SB46V?JdA=#@)!scC1}czf_pmenQtHo!%vTd6v!@j#tlPpaO};m{?xZ*&0XhrE37MvLo4tq=vFr zKN$gCLyeR9oR8*s!Bx64F(5ILrE`Xj7y2?#fkd~cM7}Q53eQ?Zi8YxqEL{sPS?wVs zpi7&a!sm82$4lnX6&5uU!_qm!6(bJ@Dv&t3pgXTAAkW@)r$l%`981?38qIzvAOT$) zt5W&gOf#J7Mw6M#rpB>!ogu%uRsj`AJTmObYhGC3f~J&+>X^XNIYXRwos594tLxJF zoB~svlATS6liGNe&KVM#uTww;5@DB8d5wz&#$lAGf0w{kk{X*HRfR$Vx{j{O;B#J> z;O}$k%H84O1ok%>^HI5XLZJc)12K))^fkwW$5LWaeF96@$S&+UUJME7nqQv5=en5S zm5cijV&8=Xmd+VG_M9q)3M7WtlNuYy^UEtKF;SVoI*=OkI_8%^0=kw3lH=&x9DiC( zS5gzq64(e*(yOy0!sxl0Z4-i36>r1NfuZAoglMr;zG z0ttKHXkKI34A1qoAcW#+kThp_aPgRofG&eO;e1Yo0baR*?iD@U8zjvcyvmOWP=Q1X z_b^@)Zj6=v>0Xf&$vS@`<1u=Bsf>UwBV(4&H8jB8v~IuRW3x&}F|okk1L%#|_iyyXZcbct~@GKNlYfP=UmuCIP%g zFvNbt>AUFW>UU(dQ9M{AC2DDGn zt%Lf~b+=Ecql|#A4R_4=-0Y_K)wI!sP)^gAuDc;897U)=B5SKDulb~lr~jsV^r?0Y z44pGZzGW zw8rMnG6K4~?WyM)p8P>IwKOvkQ&7Vgknz|!yR!%tNGw^S;WQ(3uvSAe6OOJo7&>S8 z?$Av}K-a}W+yJp#q7_L)39)LJG4q_9t26E6WCV1L zIqSgv@coK1EMnKo| zCq0zk-89I3t%}rGy}?n@fgDGh%?Tn@AhFh}yYhw8chuUYkr4g5ITX`5Lo2;R83A1} zk?9KU&d+G}44Q#_o9S9i=M0DLB#2Of#KeDkD%9(~qKdY(1Llbl|4R8p+gWk~x;D7S ztA03sLd%nB2C`$>=#mzsMtEX^2o*@gOpI04TGpfJM_NK8h2Jls>kNO`co_j*`*NgFv{y8=Uz7+HNbv3Akb34P6sS%o#HrvMp@AI7y>{U;0=k~Rn}L4ly+H%J zk0-?XfjNRJIS%$ym+M5Wv!HQ6Ugo|n|Qcrk~Oyc(vVo#HU0?jkD4GNJF(B)xq6n$@~MLw4hAr2ZG z6fDSbT+s;;p#q7Is}7@@4{y=TE;k4fXNm=Sz51#0lM&GMVNxk-7_UZQ1s@4<%LEJb zdX>M3yqN?jkgzDbgw(^|pu7W(gz(J0C+LwHWt%-^1auwg@C5yM{{qF$w;;c2o|Eng zT}h28!#qW(KmtcTLbcCdp*OebQ*_^4-wQO)P<-D_MnIQc^LnHm_8hqn>q3YrHtz*G zXDHg~CPD=gcN*)Ey3b2AV^TLle1G&ucuZAapey4)J>2l@DN3q}C4@=&AHk3u z$A7V|B2*ypV}vfQxvNGub|({Jc)GDj_YID%Y9%9}>(>f1tnK{-tvWx75D~G)BHcH5 z+P{?u6-d14XNuKHHRv-pju4Mxt)$FEjUM{c~>6}6Rz)*w= zB$Cd$V0G9tlyqP-A&#tQBiBI{bN32RfrP4c7*-eD zM|zi}JK!VM6^#bR$Eu1GGn@4 zGKM72=vw%^XDws|bUD1}iM8vmAu-IEAOT$)^?PBhK{*O|-kT5$NCwh{)bLHUFMlVU`i2rsP9{j>vHNR~ z0us>G@E`+gXO*I>)x!xfcXom_9+3k>6i|T#qfcsNUqywNDY0vJy!eV7NAgcWMnD($ zIvqFsxP)F`nMerrxOmZu9LLcOq5>+ASlJ>C*LU5-Mq;kt>Xq@>jdFEf}akqU+$ExMoB-N;yo4T`AF`4yoa_N+~0tYxb!Wto?{l zPB*%G{@OWO>_U#is$9uH1ro2`b;s(OQdIJLJ|Q$MBBTuDQJ*Xs0bS*jld(1vp$pUK zF2Jm3;nMZ$LEkI}Dv?4Cq5zL!Dr^n2n- zSt%NkIalU>g)#!V7G8|P4Sfal_&D8_S)3dy zGNc9yEo7hq31kzAYr0=R_UV-P;1?j8ks6^hie&_JrKj;&TUCr=*3sRiyg&Y8AgM8O zW-$X5NF*LtV)Ye-%DPe_D%4x7BIoL+!)+M>T@U?2@sH#pG$e`clD*USmVVEtw!FQrkJR{e=?4Q9NI1;w zf@^|Gkm*Lc&uOyPL9`<^9{(_4Apu>BYTIM2a2mA=q5HMQBsEE&%~^ez^x@c?ZvwPaoLPp43Ax`*5;3>4$obEecu(1~Dd^c3bf;|ruNPK?ffNMR@ zqr0Qe5kfn%ne-jJu*ptFK$o?S4Q@Do0v(ZhA>9mUCgqU57uvB-`-<(YZfYySk%%O*SMZRgS$~8 ziuB`fCgahJYLXrf@8~%LNqy7i$8Q^p#lj_^)IB}bP647M~R#l z4}{m`IO3jml@ZXjZ{u6^qxBKAX$j3`jGOyFup~8VE_7v~0*UL}UZdI;Cy{q4&1JZ+ zzbM_;Hn;MV5zsZVcO}wpJcMS}Tq8uj;-Yk4`|Jzpz6TUY9L~Ru)cMEJgt!#fL&~Jq`bEhI z=xXUZ0%>ROK~BhxunQ{qI%!`dufMnKnS ztcw~Z?M6aQH6b#OzbJ_!HQqmuW1#|x`Gq=2J!&6%w3_}`*`xDHnvfceLla~KbhXUc zuKMwLCps7TkPtKG&MI*yHJSt@uuy@7bK7mI+S)y+O+Wfy^*BGNnBL=vzvTpUF|%q! z?Z_Qy{~=nVRfnu%x-RN)BY}kqB#v)>E~*C(PpWUg0Ao8?N4B#0*U+1=h>PEyODk=t#SNpfubcjS9w3MQLVB;6&cNuWSNXQUUWj@pj+dGxaz*mS70|Ka;9LPkKB?ZBa2!_^I_Qcb^u2R(*L zIm`E@5iC?7;XH0ISA({qWjd5F9lek-CFjZ_l$R0E)vxJdP8+!%`Te2SlIJ-KnE-OG z0t|Q-Dv`!BWv+Y9(EE=*Bf7{4=*k=On$vb&iT=Gse{1=r&l!6E(aEO^3l&Jj zOn%9!+pa@nnp0xojjs&7Ch~pS%LwRd_vRO;-Mbuxdz2F5z3`Qx*Tl)~+p|!C#FPt- zoO;U|)P5|TGoTH6(!TSMqfRmcy4op>d2O3~)H{%7AQj8>qf-y;u7*!bi%o1}czXbd-F}=!K}|c6!gbqdUXWXR5E?*&-vL zi+7FUwVK%|*=-{s-gITyj-W6cYq4ZAgG3hZZz2`i1tE-HFt`CY7{u9bU z&5zJEnF@~>mfmwtjp)ii1rpaLcIRtS=OA_(B|;~ z5?DQQ9L3_}P^dt{p(u?nU66||T%g42Zwah9Iad{v_7+0|x=vJN@Sj#>p%c&g5aQOQ z1eW$Z+m>^n7%Grh`8AEdG<_PH^pX;BQ3)(vTUu~*NeLvN>kN~@Yvzqcuins=)GjT^ ztd-P=>AkcBDv-D|GL0`AI~kp5okh#`3~(wN7z za}%Hf2^XUjzBGLtN?t;VocknIOwQHul5`mXU59^k=RXY^ihLH)75YKDB3Vyz99bXI z1*kwG#y6S26gLL_`b~+*urQWB$8vV^92o&!od%NRa_S)DY)uo5%S^*q`W%b*!Z`v| zAhCQ$JYU9+LXUS+Vq62m(q~_P7i^Rf&{c03%h$*BN0txhjtS|nCS6PZ9Nj2D1rlP9 zXuiTH6Y+B_2(elxSh^;j^ExIYpzFZ5aK28_2fceqcYb`X21(b%P`_gWR3I@rK8!Ex zNS^Q8Pcx7ghx$s-8&p}8$_VI+_GbBy-Wez$pYCAw>gp>!Z%}PkDnJDib5|+&OAdq4 zq8JS!^jx}1bB3vrk7NXNT`Ugd>)NLxXA`;;r+V!n%^7|v9tlu^#J4U1e3|(GH;4d5h{>q8fnXyKJ9^Yb7=-r{aRo8 zT-mR7lo8Oi$=aN+e-nYe_ZdToxYPPlCe?HnNlgL;60aVc@)dWI(C)_*2$8q3fuVbM z(~{cB2a_C=R3D~(4fmMMfVT&8AdCiUBx z&N2eJF8X}sKHXxGM`2e&q<2*_G?S{A+F67OBtEwO!d)tgLA#In5~9nY8w|~)w*NvB zjo@!VmzKP_zV1>edcV?&5US)G41JDeXqC4J6-cD#JmyM|L?VZWGy{1-dyb*c0;E(2 z$O!0a5X!hO=Yr79o*I%!so#E%q1WB&^8q4MAQ5F!%2n(NLoHMO5Tbb7F6o|gqOC$k zK-a*3j&Yhp{)jiYM2Ls6yQEC&z^|bqR3PD1dxR@nr$lpy-6q7$3rnPXXPsS~jDW6$ z5gWKqJA9DCfGvc$JAR3jNzIWnmHc~Vytb&+NucV-$E)9>Ifl7WN-bj>tw&S-kMqSfGC%VRpP=UnT>9(lcr44GB(S;B{=EX@FNSnea83A3|m2s%fy#*TIE|?GvXXB*) z44uYBiBN$AdKiPstXE{}T zi%@~Ytl861xuGL!mpPvh)9`9(ujpqR(zOTtE$F(KxenD?nj@c`8wkKr@h^H@%a_V}YT&jDRlF8DEg*xejW+r!yh8EPE&I70tQjCPD=g4P)z2 z`4toN>#R2+BKrIhXuc%ogsY5zu1Dc|`1yf9swe}Rft(-pN1*wV3%RZ$R3KsAQ5Tnr z#%N(x5+Ry67)$w*_2sQ(1aw{KZib(&(yAU-k0gY~)L5jMRJ6L42o*>KJDcLmCk&9| z&@4jiv$m4*B`$H6G6K4qKeEN@>0eY+^=JlivaXesFLAfE6rlo%XRF9F!h3X)*{+3z z7&ymX+S|SzVE8t;kTs!20>erbKglLszFYRr=Z)qe#1rqCKx{&9Pn;_S&Gy^I2 zZzJXUju!qBAOT&oqTKQGxL2z0*)#(=Il7IM>l+aEOMnU__CDx{m$@{mG7+8W_b+r8 zTae$6J2&6R2!^jQ|x$^xQ~#Mj3upJ&UDxEDpWA zqTW7Uaxo^KufC?n6e(^Y8e4&~&h29CDov)NKsov*T z$_VIUD`N1IJ7uazn`j2|>R_elOU5HJbENd7}%wr433wJ=s1kG>sjWdw9hy44fE zSbSQwU+qJP9sOga@o2TJr65zl-_6!f!TAF!R4pZI-4L~CN(^&eHD;^uAIji_(kWfs&n&( z6QcEk1o1QZ4mOMQS3m_4=Sjje|Iaa1{CG;VKO8U8=U5aYi)93KX+Njqht}&=p_6H% zarE?fkv_-58I>qx3i!MJ&T06+R|izLq9~z!9V_)i?y50kAOT%kCJn!6T%iivPZN#f zPsWPfNsW)b<_uII@qBDgoPVQ0wR{3 znrHZDHOoK+64QQn$7|1QQ_a3hGmvo}5n?$xS38=Fl@ZW2JUQ46!9)AxP zzmV~8>NJ*t3M4jNO~fm=ZBRY1pu}CCllr|5ySYq8K$pq-cw91krmDwGx|{LbgcIrd z?)fLn7^py^@^LI)vS5`;>qUuzJ%2Nh&$boF2dQCa88Srn@rz#{JDe3VR9| zs6aySi^MC&E>-z>QNpWFfb?uevkyfw0=mL;cznfhxT@Vay1R6tU4Zm##?X&N3{)WT z=AII-N?)M*YD9_bf4rrhnX8xIk`d6w4-Cbn_j;?cJJDS-J5O)vTn%4*i-8IxoXUgo z3N}wwdx~ZtP40A(p3R6I@KQ!VSM&9LShXinHS0Lt?fbO3lk{xHj+B=SR3Nd^+!xy? zO;Y{vrbJOeTj@87$@n27pi8GyS6n`sS9$BveNOYNw$gJG?IV9MP=Un8V_k4tmys&> zDRiIHC`domA&O(_Q3<1Z(LxYHXP~3l&H>w{gU6#qO$*BueZl z{M!d|**|tN0=m+bHWM>CLt$xWr+ z(INk|mJ!hPZFVzUe#TPO@+#fc4{~lQ()-%8Nv&C^Kw@9IF>X5`NR?$viRM2)N|`%# zRYw^CT~(1y@Z|;is^;Z1op5dAM;PY>0l=9KXLw_2e4&PgY` z$_VKC=Kcq^jyPe@F>MN=sn+X znw`H-JT~nPA%f%9OZT-S6)X!CNSuAT8wEUR5WnxBd7t#5c>>L4G~XO1BcN-6?lQE( zbA{Nn>@Fc5w8)d5wM?57#zF-W?`|$Zku#o&32!S2;W;No%0R}rM#%{1%E%ds+H4yq z4n9NERC%9zNaGQ&jbxz$iHO;mDAKu1oNg$g95F7rnL!V%GUFwK!A`n)Wm&rRIciI)-3ASfkeN0UF6+=oj7(G{jY4ab4utQWKRDC83A3nZFZ?51VOmM(_G(T z^EoASF1pk!frSbrDlY6$E&iD+DtgiXsxE6@F@4su%P%Y_?&-)9iQVUwkxAGCiv}A(B28F_2dL}WxiXb44WIryhx=rjy~9>p#2%T z+>U3V0*Qh9Rw|E9xhHh`m)2OkLPuH)-$qgjkbthShKcy20v|(3HphSIlA89ST?qxI!6-fBG zcyiy%+m?J-NT_aXc zL$8THQcg?#8ID~Dlo8N%VB&dh(9r|zF%O!7Y)m^X^=F7#8puKg5-*;e<$9C9Sl5gG z8su7EmHI&X{OTqnplk8MTig_ji)>GSnt}XjepTuNdF^sH7Alaax^RPAFlPk&JCpuK z)mS`dXn%&JHC1U0oWDc(3&a%9IqEfqa#u$I>~&vLj9`R3NeEwjqD;c?tX97HRI2Zz}Dd z+n;D5BcN+=xfLH$W~Cf!Es`4JXZ`I1d2mJx7Ala~yV#OPToqe2jL!PTgtlbqeXa8^ z($x|CE$GUybmSu&oRwP^ohL+5rzHxS>nXWNkR@ICm2feIwDvpe(mj+!eSTq!X^=Wid#H1t$PK-cCm zzI^b1VM@=TGy{3M6g4U)YIwI$CEMwy57&=c*W~J z$~B(b3DKc3Q0nP)aqUS4DvY3aXwFEvrApha3{)WTHaUX7pQTjx)odVy(FUHSa|WHh3uFXztuBw_gQ6!YFG}z9 z{y2eW>6~GJ$pQu{kXU6A%Rk*7uRJGpj&zHSWa*s2x_p3)fG)2GNxW<5EM=(kGV)&u zZ6aAZXQ=BofPo4mhTA6cuWx23=SuJNW+P*y9*xsXy<`M*?d+7oJNBHfTza3b$?S8C zk$N=h=Xo+vfy9A9-TB8&{!tF!MTxv6aZ<0z)h2%wkbo}5`BdKi-zCb_9GYmXB~Ms{ zkmG1+^jiTHNR+%w;j6vIDL?n5gp*wYOM5ys*V!T?pzFVR>Ad6ImC7^DlL)cnZ9Gf+ zGkmhztbht6I-E%5pA5`Zo_&{1h|w?q_JItvdlw1`=vu!xgKxcgy|TUM2ttIO`P&C_ z=*d^1P=Ums(`o#KvboCEc9dA(kRbIiMVVWRApu?6u4M3zC$}m;`Sm5l)6xW%&KbUC z?I?x{Byzr`@nfGYQfh-Kp-lYS2hzr6Q3)iV>rObSado%yDU(D9cgMedASahEDuIfO z$RafwmMgyuphVTnIF|NwTGji#jJN|_-1juzQN3SzFO?_6iW6}x-9O*){(T8lAYt7h zl^<)dPI+%7C0<%R3OpLE`=ZG zwnfQ3r9{?;NS4kSI{cR=BcSWDc@N&v=8W>nBbsRJb2gHta|ZvjX#!Lr;S-b0k5%qc z)+DcRKw@Z7JfD@ZPZ{}z z61ujmG-sG^woyhv*ZmH$ykqzUO*H0y{M!ezOTkeADv;PeHjK~u@07CWJ0;Q=`$}_$ zCvz{$2xiZdzc83W*{x<_@^t`DE6-emrbmPaozNU0YJ5GpkPyY6SoEh9)MnG5p zbFREg+{Rlo8OiI>nrK z)T>us4IN7e#I1+eM%G~`^;VmPetF-PZXW2oQd(B%D zqRXQj(z_r|9r6~T0*SXKPq^$U8s(;itq8Gp>3M0L;qJ)*83A4D)N;D2pssEmNFCryrXri?K+&*?I$ zAwJn9tutiY4HcmRiGDMWa{uoAtsFJ|4k2nXmP*fa-daHVW`Ms1U0aPda^|rnT%Ydr zz2QzqOBtHE>p6rIp#q8e^Xs{wfjV4@K7DU^Skr$Qnz_3-EJ8*=SLU2N&N9uM+p=v6 zAx>wGlGYh?lo29SAaQx*EH30ENkhm6;#05?<;^Mv7HzafS zZ{jHh&D_me7cW8u63e41m4VC6xX_)Jq=rS7n}TNU>T!aMfUfy7`zfvFJ8&D{(+uQ- z;~f-qZ^CPJf(R8zFq``*LpED-2gm;;MCy{-BD#N$^b=(Sban4GkTGBB%qjC}2J&AM zO%dHc&%B!;LIo1rjr%i!du_O(zBB{5z31Q(`aZ~Ui{%7#eOth(Ot!jma~)^~a^9&S zCG>a5OFRKDo_FPuADH)GaFMDZkV)oEj83A1%vTc#&>5g27lQcD1kYz7r z?xuOgico>XNY9oiSGX<&R3G$8meAv^UW*EnG%Gm(IW(WOChubN3%hh#uOhLTfS}^IC_C zP=N%?PDB3cw%p~N^9ZrZ<8KC1V3jfgx|)`+MP}7roXx5Agcu&YTH2d9Zmblc0*Qa; ztU-aFJ8)gI3kl&;ywe(o{?x;}l@AhTu)uI1}agz#(rPN3JT@IP)MR3KrZ{)_@!`)~z~ zUi80u@>}WyIYR9!BcLm^sUEg$spQ%R#1O*m%5Q=8bb5N&mAnn}{|Y2@)H*o0i$CXP zoJ5EXPmH9VPDB5+k`d6AVrhoW+Jtd;8b=Uf`z<4>r_+BGtwg9mBK3(04hRh5zR#yK zGWC5+kv>0YKHpMCKv(B&w%D|5Bp0!CJ|RlWEJgbKpj8h`5h{?Fo8A%!gokpEyU>}X zO`^TjZ>jZsBN+i*e|EISX2CJs#UPr2d=YFf^;_~uF%qEyiHDIcI53&z8jjMZgc?2C zNHr$3{VhNOx~#0+u|;G&rz9^?Aje_bMyg?{`XxXG5{Gwp#N_s$yQw%ri1Sn2rJhb* zQ{T!6=-T(n3!C*w;!<}YLaZO)E@kev*S`^<0*RCno;V;gg6oKA=lVJiFR7w7|ofw(+p(KaDS<%(=}(6jDW5) z1q?PDnaUm5@qrMV+WSj+hA$UI0V$`ISKPZtsHC&|6pI@+=A|s%y(moNJFYC{B znqg0ffRw*|ARkPgEI6$wD*q5%xD<_T_>+~$7UM`ac&C;Mou^NJ)vB;=g?$2UmM4AOT(a&oi)DRTj6>Yd9gMk{KoK=`=pdTLBeF zgqn~V?}u@ZohadcCSLkn3H9e>1a#$S)3L>i30!6ynrNIlH(u&5+JDt~1ymrhrvo`x z4I{XdUnUTu{kK@@J-i=JnKF=ouH5)EZ1Hgt7ch$^8vEhDeIWldm@-g-L{x519IQ8* z`_h{d9i~Q$P05&_cV=V+bls^=!Dc_Ea{ZppCq#C5wA8zePh}XWK;o4}4;)~T#q~Hy ziI?FKQs3IArK4p8bTt+xV^f1%?v2}0Lgbo9NPQrW)Qx7K0*RkBi8#P<0vG;z2_dTc zb5hnhEO42OfUX%QI-h&6jPCYLt>`4ZZ#(;HEdv!uTvhmD$1Zu?b~8$t-D@kIE2Dc_83A2a`*y{~ zdlqnpFKD81z>c=kxoUMu%RmJZ-)g#Gr{lA^*fdJ)v2>Jj`ln|Zu#kYRkdzMC&|@L@ zMoSZo8y`7HW4?5v0SgsKl)h_+9Rufb9fwfj#W`z{?yJ4rVlE?~OXrvqHaM|}yK;-} zBJZ1LEz*6p)yvFTs6ZmPuOoI4=5fotC{g{QnMmLHa;&SJjDW5sGi%9}x%Wruu73A^ zO{Hgkt~<77p#q75^~Tt#;y*6$CnCh?8J`5&(`n@1jxqwe<}Yr74I}co2S;c+!O7{9 z5Jk?F!SaqQR3MT6?l*EwUc^;sE)im9eYNynfGrEV$_VIkIrk13Tv^Ud?N3t{|E{eT z=yTh>$B>nJpg`h&(HrDYwV0c*fo34fPyWq7&bsI;BcLmD&Rt~KeI-|Nj;2Na^tvQH z+c&k)mxT%>I-S0Q9Q!WivOioU#M$yA(lc=j(}QIMbX{E}Ame+hxFf<%LL3}_M9L0U z`vxa_;g9spr|xJZZh8dUlwMfUZTmmm!0iwVcuUyQIdILwQn`)0gaIK?M>k zvz8)n|DO-E&ag2M^ji{{A8kH0RVbL!mhaWVqB>W3I0qwGyw(b#H23_AX*g!XhAdnJyA3MBgS`p9wC zTJGxt`d7R@W6>xo@EC9qI|#HpdXRSpL0 zxsX))U)|c2UrgVRGo?{ZK$m`bgJ_tyl`G#*YZxXhk@`Tsxst#_1rp=Vd=nj)Y~XsU zXpN+I+EDsDj{XZd0bLmr3Rxq=?Oe$TTBGdo&rte2&iA7UEL0%zT&I9_vf9K2m(v;} zj&4`bo=)>k(2LfHNZp~JJ)JsK$Fop@#7@KIO2_q^xiV=4OVXP% zw5L;-X>l?Fy2POkN(1X%obzV-Uwzrql%YMH_C&_9P=Um#m~To4=dE1B2|DJV9(qgf zsn#VOryv1cCzCxn!wtJRzZ^;&YSB%4PxV6b_H3v?BB#=WbKJd+o3WODc9*Oe#L%8j zmF|%;0=lMLAH*5C6mlMo^gFmm7{t(?PHXBTSg1h4v~~dJ)M*EI#*PxpLjGgunoQ+5 zUPeIIG+`lUSg?m1YfrzmXX5^2=$cGZ(m4_;kZ8X8KhE*!PR=-#UVTPIY?IziK2ML4 z5zy7vb|+`pc^^0EA-yKrCvB7VqY|$xSg1h4;;g_S=!!>}Wq z#eh;m*c6lG92xWMdF@%KKqC3r56&@hKR16non`!V(v$kbx!F0(2C!a0&a)6Jn!= zbfqNcYOua33l&Jz%(dqopB?6u29y}{KdR0$po*uB;)s+epn@V2ibzU_h;Vn=5u_y~ zMG>&O8?FI%cNcbd!!9#+V__G{-vpIVK$Lf8{qR2f`J5jQd-vYm*_kuXy8r6~xkE!s zK|t5L6&;ao>M5b;%TtuNWNyW?eJr7O8uL(r#4%SJWHIolusetmiT7>gUPuv3syIkM zm+GuD(tUecm{D|;635Ql@@;4h!MTcq3M59LPRQcxF+oC%&?$D7^CkVm-zW&^>dAW} z-9cxC&2w1>GW&uv&*m9=eR<761rq+}JdwrN6GE6aBM$m{$?r>@6ns-bK-Vg3f23P> zPS~rvn-VE)z2x3h7eC$LpaO|?i~Nv9%}K#;IeWT0_p6KC2lBx5QwjpQrX>qVZ`=i8 zUBWg>oPOLz?&-A6{uBolNEn^wk)`^yFzpL_N}X-Y$+;!-zgrXpbnSZC9qHEQ3$rF} zphWq1mHhtN7W=nwP=Q34TQE&PoE6%bF(Ocn|J} z_eva@&OrqdsSQ!cviSu;Q;QK#y#Muq3=H>B5YQEG+7sz5#DccwJW9kg4ddCqyZHs~ z98@4NuSYDhT$3;4-kM8^E;Ii1f%KL%6a;kLKahZQTU->Pce6y}-uMVP*LOTXgM$hr zM!%%VObbz1s%Av5=KuOY8qM9Rf&_FqWF;Y;wU>lJ<5&jrb4jH9Zu0(Xx2T{3iRb$g zka?aY9OW4?{N2CzZQoC>4uAx7rO!=9dX`rN|6G=6?0zyz?rpfU;zs~fAW?LjCIKC= zpdQYMNp=7FK!ydZ%7+AW&AXM1n(VkLv{=XzjaRS#>jU|r``Ub{K%yT_8(JPFLcgVq zcoF}v52WMnxdo7bt_$JGNZ0YY@O&jpG~Vg(uMebM?7RY~K;pG05`DHQ`_$BTmuXb8MbrP0%a_0bT9r zJIQs0yF&UYIngL|uNdVXE;BcgK zwMh6jsW~Nz{&MoQ#D4aA5i0QS-dq}r%n(Kxe3SNWPay2ViuDv-Fmq8qYEzAY>W(x$|bG3Fs>B?u2v)JP~x(ungpm(tjDq zbK4t9P=Q2ru>-Og{XjVNjAbCDR&C@lL$km73Ie(eE38qIFHeQ&qbvhy{MJ++GbDf4 zr(I(HuRvna%J#_e=R-lK{Z>jmn`I=A8A=}*D+uUn-oy;)jd?B{y3basO{0zEF+W$<;%h%0dCaiL$wEOu*YK4_NaxoJVWk!8IuzugBaa#Ow6>6- z0*Rfy4UxI#6JhM836#*;R4ey^BxN=V0=g>JYa`w4S38 zleLh=yr+WEv3N>s*ig!`c?O48ofHIgjgI^&G->=s*x|qu5QSY!IW}hK{o6@`3MBk} zs{~8K=R&c2XG$!*RK&4)hF;q}6a;i#$ay2^&VMU7R-|R8I5>y~D&G(vM z-u9I+_8rSW_P(}Lo@dZ}9H1be%W%_iL3j0g!KL$6TBG~8o$|W}TAT`ypaKb%*)hRl z+iT(E@`sdoUa?qycHS~fP!P~{bZNuff=n;ot2`Z29M^fShUHebMpNj$|Zv76B=NXf`G1!UF`(jL!X7*eyu3sytx_2dW)R87cM~s63rs41dFq!LMIk^-_q!0OAfPKh|4E>p*H>ZEz|S;u=l%4IiuDU0x+GG93M5{a76)2#AA}j< zEoqGt^ITMN?`SbfK|t5(qRc?ub7jKtO@!8Hbfc4s?c1(h8YMvm66J5x11&Cn6f~{> zP-61lf(xv7^gz1C4GHLKd~yS)%YPH%x3COkxyOwQY@WfCu5m*J5_7t(=PbfM3rXEr z2C|`Np90o9`qMHc0bPT90}6F7e;2go7f_<@>9hhiXX)NIN`eX`R_*LsXi@Yr7qg%*irLeD^! zf$aFLjaWx(%ROctU5S?68YCBi>CBA4s;2UpaO|Ezb9jhk3WQ6uX8Ekw0fny zHqo?Cpn`y|PL6A^Zoe8K;QCrhIOeUC*Cra-2TD+Z#E)BaBKv(L~%UEyJZ=u)U&y>)k1j`6_t@n|j0*OTJOW1PiPeI+Rh7!`-NAmZoUt4zt z0bLJAzQDTG^}_jVLwd*IpFa|9Xbq$HZW2@=v312WY@tyjh<8jVv24X>xqr!tevS$P zx;FHyz`E1^3RfIi26Dyh&+^*ceiugxDv3Yy@z)&tLI9Js)N?Ne2n& zT3OJT$KoV%hAsaMhoUWVG}2K?M?1wlpJ_E9wR9{Ua&yX{s0*8bNA^4(HB2`Z4NG-yZ6w=@W5-Yf$d`kQvwq~~M9&c7lgplj0$2a?;Y zFra(JK?Vg6-XQyWls!OXrPeuhbb{6+)-*lKdaZzJ}U_5iaG60a_=@q zD_XM*WG@d#iS^3}nEF|S3M2+}aU(_vjnHymmVvyhoKxHk#<&diM70-Bz6?w)b`8p+W@#U9Yk@l9Ql~ESs?m zWJ80G-2ZA{&q5I@kZ^n(Kn$O2q9%9BDN(ZUUmwUvYxXM$=(^A+h~&1>L90zQ=~L?Z zq5t|oo($YCLIo1hrw}pR(iqKo&4|j#KuMRL5512o6$EtoE({~N&uNW629&UO43ya3 zSG_eWMW{ex-Qy5ql%a)g9AFv9X&FHh>mA(~O;HfgrLEC}v5-7vAI#EfC#a>lD*tC@m;uER&-Np8m`Xuryf5>lFL)J%j5B!0e& zBZm8RQ1nJdtV@ZMdu%;4yI%ka=t?V1B)K1&pys+P(P-upDZiV%S-X1$P=UlNlLTTk zLKhi?G2-pwe|;c#{OYS9per^pndI)*N5NksDY2H0tJpnio=aK*R3LF;7ENZl>!H3a zqbT7=$A_$Ubmsc|7a##$TQyQh&WNVyz}pl`^q}KI_HOc7$1h)i3M7_yN+Jf8dPw}j z2zMMM-J&%n7dojR0bL32>2bI>LzABmq{Ll1&izTpaM;yZ1rx_sm zjFf)R8ruh-Q4r8o*@)JtY=#cq9!H6Ti~se3EYUryf(j&>xg`>#Df;N>Rz`ep93f{? zT_!i0!&hR@YL@rTD+uV?eif0N*>r}p zBU@dH7~w0iXSJL6&vQ_L#EuREF>GptJPKF_^3WL%IYS>grAR?Q*VdB(Bsa7r8ux}J z8hgz2kb6f@7+=Ie1rnDm{E6WmBa}0o5pJ%X-ug{sP|v3FbpHneGl zq^B$cS)KJL?Te!ML2B}27T3)&|X<$uf|$R?QOG z-q$%px+w_g>J_{c=L|PTjSjLD()Hk3^7|&f(>Izy1roZ=mtez+Hfa7GmVqp;Ns#9m z{C;&;5YXk*Yy{4Avp^x+Sej~o{{(rSA?0p&9x9Mn*K8OzJY|N~h%8NIX=Nv8IYV!U zDG2D=c&aPTsjxuqK2IrecBQSHP!^v$?%NUj;)AIOIz|M!>*9Zjx zU29M3;@ne~$Y#hhO5Ak+T)@T*I)xEDR3Kr$Oa~kKo1zOF3SH$Zn@0UI;ywTa@P0tq|qJ%xsU&5?5~`>(oPT%XV0QMI#HNkG?` z(~Syq{M#XJAFE+Kc1=FpSJduC6b}_h49(LhG{hF@zQ}6qIQK1py>CLlR7pVB$lDwF z+`sM6rIW12g2NR7?49@8bR8WkkZ2gUfj7*xMEng_W3=-Y73<5HXrd&b%XIUSz+BuO zne|~cKHsA&kF>_$w~;(lATfX7;y}X?D-<`5)v&P8;Mm^R19Ku21aw7{>cE^_Yvi8C zi0zpg9NYWq(ldgG3M394tPC{NZ-@SzW9PhTi@V$}V=@<}AfW4$rmK(>(gEGdWrWsK zce!7NmVOuy6-dmQ?<^SJZih6tvhS{UQabmFo)62w?g|3BM$S$ba`kOckPahu=B9IO z?`w&EcOEK`xUnToFkIFiowi}W+F^NfIrh%U6^oF9fG%yz`9kh(8}!Sb{c6SIb2;|T zNkThELIo0)m2(B7p4Mnw7`ysd9p577EE8-v1p!?xr)(E;mUTqkzp!g!&4n#o7h1!* zOvOV560zpn1jE)H(E1jPaP&OI71PhEQr}NOK-ZnXb3#r}TjU_!a-Z6v)2(h&;(qmOj=gW99 z6-XG~d@mUF?T9)?v%7Qqh$^{XM%iau1p!^I;q^l9CVTXD63akp`d7*QGR#id@=$?9 zV~0Ayu!AkKKF3BGI&T{Duj%=C8DXU$pzHHQJ(Sbe0qM+V8A$xFG0(ocTU%T4P=Q2F zG<`NNwMEzNv(b`Q8w2^hmhEj?D+uU%5z!Lmc5p;GQ7i*_sfmI7Uduh@t$3(F!o#Qq zGTdv2zHeotKJ~3u^15@~I0FR%U4J&1quf$Q^#1C3N>rV1CHL4G7ihpk1rqMzZIRJ1 zd$hVEBi_|p$~~Q8IV}YNU74vJQSM$RwCfK`G)8^2l-JM8n`-e;fyCZl9gv}`13K29 z5p(ivc{a~*�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

;Ia|1a6HHdx{1pn(#-$VFuDC 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