diff --git a/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.cpp b/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.cpp index 3d7ffc08e7..c618f8b0b1 100644 --- a/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.cpp +++ b/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.cpp @@ -101,7 +101,7 @@ ShellStressRelaxationFirstHalf:: gaussian_weight_ = three_gaussian_weights_; } /** Define the factor of hourglass control algorithm. */ - hourglass_control_factor_ = 0.005; + hourglass_control_factor_ = 0.002; } //=================================================================================================// void ShellStressRelaxationFirstHalf::initialization(size_t index_i, Real dt) diff --git a/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.h b/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.h index d8cea3c57d..61dbfd100b 100644 --- a/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.h +++ b/src/shared/particle_dynamics/solid_dynamics/thin_structure_dynamics.h @@ -202,22 +202,24 @@ class ShellStressRelaxationFirstHalf : public BaseShellRelaxation { Vecd e_ij = inner_neighborhood.e_ij_[n]; Real r_ij = inner_neighborhood.r_ij_[n]; - Real dim_inv_r_ij = Dimensions / r_ij; Real weight = inner_neighborhood.W_ij_[n] * inv_W0_; Vecd pos_jump = getLinearVariableJump(e_ij, r_ij, pos_[index_i], transformation_matrix_[index_i].transpose() * F_[index_i] * transformation_matrix_[index_i], pos_[index_j], transformation_matrix_[index_i].transpose() * F_[index_j] * transformation_matrix_[index_i]); - acceleration += hourglass_control_factor_ * weight * G0_ * pos_jump * dim_inv_r_ij * - inner_neighborhood.dW_ijV_j_[n] * thickness_[index_i]; + Real limiter_pos = SMIN(2.0 * pos_jump.norm() / r_ij, 1.0); + acceleration += hourglass_control_factor_ * weight * G0_ * pos_jump * Dimensions * + inner_neighborhood.dW_ijV_j_[n] * limiter_pos; - Vecd pseudo_n_jump = getLinearVariableJump(e_ij, r_ij, pseudo_n_[index_i] - n0_[index_i], + Vecd pseudo_n_variation_i = pseudo_n_[index_i] - n0_[index_i]; + Vecd pseudo_n_variation_j = pseudo_n_[index_j] - n0_[index_j]; + Vecd pseudo_n_jump = getLinearVariableJump(e_ij, r_ij, pseudo_n_variation_i, transformation_matrix_[index_i].transpose() * F_bending_[index_i] * transformation_matrix_[index_i], - pseudo_n_[index_j] - n0_[index_j], + pseudo_n_variation_j, transformation_matrix_[index_j].transpose() * F_bending_[index_j] * transformation_matrix_[index_j]); - Vecd rotation_jump = getRotationJump(pseudo_n_jump, transformation_matrix_[index_i]); - pseudo_normal_acceleration += hourglass_control_factor_ * weight * G0_ * rotation_jump * dim_inv_r_ij * - inner_neighborhood.dW_ijV_j_[n] * pow(thickness_[index_i], 3); + Real limiter_pseudo_n = SMIN(2.0 * pseudo_n_jump.norm() / ((pseudo_n_variation_i- pseudo_n_variation_j).norm() + Eps), 1.0); + pseudo_normal_acceleration += hourglass_control_factor_ * weight * G0_ * pseudo_n_jump * Dimensions * + inner_neighborhood.dW_ijV_j_[n] * pow(thickness_[index_i], 2) * limiter_pseudo_n; } acceleration += (global_stress_i + global_stress_[index_j]) * inner_neighborhood.dW_ijV_j_[n] * inner_neighborhood.e_ij_[n]; diff --git a/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.cpp b/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.cpp index 130aa4e791..fd1c84537a 100644 --- a/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.cpp +++ b/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.cpp @@ -173,23 +173,6 @@ Vecd getWENORightState(const Vecd &e_ij, const Real &r_ij, const Vecd &particle_ return getWENOStateWithStencilPoints(v1, v2, v3, v4); } //=================================================================================================// -Vec2d getRotationJump(const Vec2d &pseudo_n_jump, const Mat2d &transformation_matrix) -{ - Vec2d local_rotation_jump = Vec2d::Zero(); - Vec2d local_pseuodo_n_jump = transformation_matrix * pseudo_n_jump; - local_rotation_jump[0] = local_pseuodo_n_jump[0]; - return transformation_matrix.transpose() * local_rotation_jump; -} -//=================================================================================================// -Vec3d getRotationJump(const Vec3d &pseudo_n_jump, const Mat3d &transformation_matrix) -{ - Vec3d local_rotation_jump = Vec3d::Zero(); - Vec3d local_pseuodo_n_jump = transformation_matrix * pseudo_n_jump; - local_rotation_jump[0] = local_pseuodo_n_jump[0]; - local_rotation_jump[1] = local_pseuodo_n_jump[1]; - return transformation_matrix.transpose() * local_rotation_jump; -} -//=================================================================================================// Mat2d getCorrectedAlmansiStrain(const Mat2d ¤t_local_almansi_strain, const Real &nu_) { Mat2d corrected_almansi_strain = current_local_almansi_strain; diff --git a/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.h b/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.h index 81fe69b861..bfada18d9e 100644 --- a/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.h +++ b/src/shared/particle_dynamics/solid_dynamics/thin_structure_math.h @@ -76,10 +76,6 @@ Vecd getWENOLeftState(const Vecd &e_ij, const Real &r_ij, const Vecd &particle_i Vecd getWENORightState(const Vecd &e_ij, const Real &r_ij, const Vecd &particle_i_value, const Matd &gradient_particle_i_value, const Vecd &particle_j_value, const Matd &gradient_particle_j_value); -/** get the artificial rotation from the pseudo-normal jump. */ -Vec2d getRotationJump(const Vec2d &pseudo_n_jump, const Mat2d &transformation_matrix); -Vec3d getRotationJump(const Vec3d &pseudo_n_jump, const Mat3d &transformation_matrix); - /** get the corrected Eulerian Almansi strain tensor according to plane stress problem. */ Mat2d getCorrectedAlmansiStrain(const Mat2d ¤t_local_almansi_strain, const Real &nu_); Mat3d getCorrectedAlmansiStrain(const Mat3d ¤t_local_almansi_strain, const Real &nu_); diff --git a/tests/user_examples/test_3d_thin_plate_python/CMakeLists.txt b/tests/user_examples/test_3d_thin_plate_python/CMakeLists.txt new file mode 100644 index 0000000000..31f2038380 --- /dev/null +++ b/tests/user_examples/test_3d_thin_plate_python/CMakeLists.txt @@ -0,0 +1,27 @@ +### Python, for ${Python_EXECUTABLE} +find_package(Python3 COMPONENTS Interpreter Development REQUIRED) +### Pybind11 +find_package(pybind11 CONFIG REQUIRED) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # main (top) cmake dir + +set(CMAKE_VERBOSE_MAKEFILE on) + +STRING( REGEX REPLACE ".*/(.*)" "\\1" CURRENT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR} ) +PROJECT("${CURRENT_FOLDER}") + +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(BUILD_BIND_PATH "${EXECUTABLE_OUTPUT_PATH}/bind") + + +file(MAKE_DIRECTORY ${BUILD_BIND_PATH}) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/pybind_tool/ + DESTINATION ${BUILD_BIND_PATH}) + +aux_source_directory(. DIR_SRCS) +pybind11_add_module(${PROJECT_NAME} ${DIR_SRCS}) +set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${EXECUTABLE_OUTPUT_PATH}") +target_link_libraries(${PROJECT_NAME} PRIVATE sphinxsys_3d) diff --git a/tests/user_examples/test_3d_thin_plate_python/pybind_tool/pybind_test.py b/tests/user_examples/test_3d_thin_plate_python/pybind_tool/pybind_test.py new file mode 100755 index 0000000000..4ec076b804 --- /dev/null +++ b/tests/user_examples/test_3d_thin_plate_python/pybind_tool/pybind_test.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +import os +import sys +import platform +import argparse + +# add dynamic link library or shared object to python env +# attention: match current python version with the version exposing the cpp code +sys_str = platform.system() +path_1 = os.path.abspath(os.path.join(os.getcwd(), '../..')) +if sys_str == 'Windows': + path_2 = 'lib' +elif sys_str == 'Linux': + path_2 = 'lib' +else: + # depend on the system + path_2 = 'lib' +path = os.path.join(path_1, path_2) +sys.path.append(path) +# change import depending on the project name +import test_3d_thin_plate_python as test_3d + + +def run_case(value): + parser = argparse.ArgumentParser() + # set case parameters + parser.add_argument("--restart_step", default=0, type=int) + parser.add_argument("--end_time", default=20, type=int) + parser.add_argument("--loading_factor", default=100, type=float) + case = parser.parse_args() + # project = test_3d.thin_plate_from_sph_cpp(case.loading_factor) + project = test_3d.thin_plate_from_sph_cpp(value) + if project.CmakeTest() == 1: + project.RunCase() + else: + print("check path: ", path) + +def net_displacement(file_path, output_file): + with open(file_path, 'r') as infile: + lines = infile.readlines() + + first_line_values = lines[1].strip().split() + first_value = float(first_line_values[3]) + + last_line_values = lines[-1].strip().split() + last_time = float(last_line_values[0]) + last_value = float(last_line_values[3]) + displacement_z = last_value - first_value + + displacement_x = float(last_line_values[1]) - float(first_line_values[1]) + + with open(output_file, 'a') as outfile: + outfile.write("run_time = " + str(last_time) + ", displacement in Z direction = " + str(displacement_z) + '\n') + + return last_time, displacement_z + + +def copy_files(output_folder): + source_files = ['output/PlateObserver_Position.dat', 'output/SPHBody_PlateBody_0000000000.vtp', 'output/SPHBody_PlateBody_0000000001.vtp'] + destination_files = [output_folder + 'PlateObserver_Position.dat', output_folder + 'SPHBody_PlateBody_0000000000.vtp', output_folder + 'SPHBody_PlateBody_0000000001.vtp'] + + for index, source_file in enumerate(source_files): + # Read the content of the source file + with open(source_file, 'rb') as source: + content = source.read() + # Write the content to the destination file + with open(destination_files[index], 'wb') as destination: + destination.write(content) + +def rename_files(value, output_folder): + old_file_name = [output_folder + 'PlateObserver_Position.dat', output_folder + 'SPHBody_PlateBody_0000000000.vtp', output_folder + 'SPHBody_PlateBody_0000000001.vtp'] + new_file_name = [(output_folder + 'PlateObserver_Position_' + value + '.dat'), (output_folder + 'SPHBody_PlateBody_0000000000_' + value + '.vtp'), (output_folder + 'SPHBody_PlateBody_0000000001_' + value + '.vtp')] + + for index, file in enumerate(old_file_name): + try: + os.rename(file, new_file_name[index]) + print('File renamed successfully.') + except OSError: + print('Error: File rename operation failed.') + + +if __name__ == "__main__": + + file_path = 'output/PlateObserver_Position.dat' + output_folder = 'multiple_runs_output/' + output_file = output_folder + 'Displacements.dat' + #values = [6.5, 12.5, 25, 50, 75, 100, 125, 150, 175, 200] + values = [6.5, 12.5] + displacement = list(range(len(values))) + run_time = list(range(len(values))) + + if not os.path.exists(output_folder): + # Create the folder + os.makedirs(output_folder) + else: + print(f"The folder '{output_folder}' already exists. Skipping creation.") + + with open(output_file, 'w') as outfile: + outfile.truncate() + + for index,value in enumerate(values): + with open(output_file, 'a') as outfile: + outfile.write("loading_factor = " + str(value) + '\n') + run_case(value) + run_time[index], displacement[index] = net_displacement(file_path, output_file) + with open(output_file, 'a') as outfile: + outfile.write('\n') + copy_files(output_folder) + rename_files(str(index), output_folder) + + print("------------------------------------------------------") + print(f"All the cases finished! Files are saved as follows: ") + for index,value in enumerate(values): + print(f"For loading_factor = {value} :") + new_file_name = [(output_folder + 'PlateObserver_Position_' + str(index) + '.dat'), (output_folder + 'SPHBody_PlateBody_0000000000_' + str(index) + '.vtp'), (output_folder + 'SPHBody_PlateBody_0000000001_' + str(index) + '.vtp')] + print("\t" + new_file_name[0]) + print("\t" + new_file_name[1]) + print("\t" + new_file_name[2]) + + print(f"Summary of displacements saved in : " + output_file) + print("\n") diff --git a/tests/user_examples/test_3d_thin_plate_python/thin_plate_python.cpp b/tests/user_examples/test_3d_thin_plate_python/thin_plate_python.cpp new file mode 100644 index 0000000000..33d3a8082f --- /dev/null +++ b/tests/user_examples/test_3d_thin_plate_python/thin_plate_python.cpp @@ -0,0 +1,314 @@ +/** + * @file test_3d_thin_plate.cpp + * @brief This is the benchmark test of the shell. + * @details We consider the body force applied on a quasi-static square plate. + * @author Dong Wu, Chi Zhang and Xiangyu Hu + * @ref doi.org/10.1016/j.ijnonlinmec.2014.04.009, doi.org/10.1201/9780849384165 + */ +#include "sphinxsys.h" +#include +#include +namespace py = pybind11; +using namespace SPH; // Namespace cite here. + +/** + * @brief Basic geometry parameters and numerical setup. + */ +class Parameter +{ +protected: + Real PL = 10.0; /** Length of the square plate. */ + Real PH = 10.0; /** Width of the square plate. */ + Real PT = 1.0; /** Thickness of the square plate. */ + Vec3d n_0 = Vec3d(0.0, 0.0, 1.0); /** Pseudo-normal. */ + int particle_number = 40; /** Particle number in the direction of the length */ + Real resolution_ref = PL / (Real)particle_number; /** Initial reference particle spacing. */ + int BWD = 1; /** Width of the boundary layer measured by number of particles. */ + Real BW = resolution_ref * (Real)BWD; /** Boundary width, determined by specific layer of boundary particles. */ + + /** For material properties of the solid. */ + Real rho0_s = 1.0; /** Normalized density. */ + Real Youngs_modulus = 1.3024653e6; /** Normalized Youngs Modulus. */ + Real poisson = 0.3; /** Poisson ratio. */ + Real physical_viscosity = 200.0; /** physical damping, here we choose the same value as numerical viscosity. */ + + //Real q = 100.0 * Youngs_modulus * 1.0e-4; /** Total distributed load. */ + Real q = Youngs_modulus * 1.0e-4; /** Total distributed load. */ + Real time_to_full_external_force = 0.1; + + Real gravitational_acceleration = 0.009646; +}; + +/** Define application dependent particle generator for thin structure. */ +class PlateParticleGenerator : public SurfaceParticleGenerator, public Parameter +{ +public: + explicit PlateParticleGenerator(SPHBody &sph_body) : SurfaceParticleGenerator(sph_body){}; + virtual void initializeGeometricVariables() override + { + // the plate and boundary + for (int i = 0; i < (particle_number + 2 * BWD); i++) + { + for (int j = 0; j < (particle_number + 2 * BWD); j++) + { + Real x = resolution_ref * i - BW + resolution_ref * 0.5; + Real y = resolution_ref * j - BW + resolution_ref * 0.5; + initializePositionAndVolumetricMeasure(Vecd(x, y, 0.0), resolution_ref * resolution_ref); + initializeSurfaceProperties(n_0, PT); + } + } + } +}; +/** Define the boundary geometry. */ +class BoundaryGeometryParallelToXAxis : public BodyPartByParticle, public Parameter +{ +public: + BoundaryGeometryParallelToXAxis(SPHBody &body, const std::string &body_part_name) + : BodyPartByParticle(body, body_part_name) + { + TaggingParticleMethod tagging_particle_method = std::bind(&BoundaryGeometryParallelToXAxis::tagManually, this, _1); + tagParticles(tagging_particle_method); + }; + virtual ~BoundaryGeometryParallelToXAxis() {}; + +private: + void tagManually(size_t index_i) + { + if (base_particles_.pos_[index_i][1] < 0.0 || base_particles_.pos_[index_i][1] > PH) + { + body_part_particles_.push_back(index_i); + } + }; +}; +class BoundaryGeometryParallelToYAxis : public BodyPartByParticle, public Parameter +{ +public: + BoundaryGeometryParallelToYAxis(SPHBody &body, const std::string &body_part_name) + : BodyPartByParticle(body, body_part_name) + { + TaggingParticleMethod tagging_particle_method = std::bind(&BoundaryGeometryParallelToYAxis::tagManually, this, _1); + tagParticles(tagging_particle_method); + }; + virtual ~BoundaryGeometryParallelToYAxis() {}; + +private: + void tagManually(size_t index_i) + { + if (base_particles_.pos_[index_i][0] < 0.0 || base_particles_.pos_[index_i][0] > PL) + { + body_part_particles_.push_back(index_i); + } + }; +}; +/** + * define time dependent external force + */ +class TimeDependentExternalForce : public Gravity, public Parameter +{ +public: + explicit TimeDependentExternalForce(Vecd external_force) + : Gravity(external_force) {} + virtual Vecd InducedAcceleration(Vecd &position) override + { + Real current_time = GlobalStaticVariables::physical_time_; + return current_time < time_to_full_external_force + ? current_time * global_acceleration_ / time_to_full_external_force + : global_acceleration_; + } +}; +//---------------------------------------------------------------------- +// Define system, geometry, material, particles and all other things. +//---------------------------------------------------------------------- +class PreSettingCase : public Parameter +{ +protected: + /** Domain bounds of the system. */ + BoundingBox system_domain_bounds; + // Observer location + StdVec observation_location = {Vecd(0.5 * PL, 0.5 * PH, 0.0)}; + /** Setup the system. */ + SPHSystem system; + + /** create a plate body. */ + SolidBody plate_body; + + /** Define Observer. */ + ObserverBody plate_observer; + +public: + PreSettingCase(): + system_domain_bounds(Vec3d(-BW, -BW, -0.5 * resolution_ref), + Vec3d(PL + BW, PH + BW, 0.5 * resolution_ref)), + system(system_domain_bounds, resolution_ref), + plate_body(system, makeShared("PlateBody")), + plate_observer(system, "PlateObserver") + { + //---------------------------------------------------------------------- + // Creating bodies with corresponding materials and particles. + //---------------------------------------------------------------------- + plate_body.defineParticlesAndMaterial(rho0_s, Youngs_modulus, poisson); + plate_body.generateParticles(); + + plate_observer.defineParticlesAndMaterial(); + plate_observer.generateParticles(observation_location); + } +}; +//---------------------------------------------------------------------- +// Define environment. +//---------------------------------------------------------------------- +class Environment : public PreSettingCase +{ +protected: + Real loading_factor; + /** Set body contact map + * The contact map gives the data connections between the bodies + * basically the the range of bodies to build neighbor particle lists + */ + InnerRelation plate_body_inner; + ContactRelation plate_observer_contact; + + /** Common particle dynamics. */ + SimpleDynamics initialize_external_force; + /** + * This section define all numerical methods will be used in this case. + */ + /** Corrected configuration. */ + InteractionDynamics + corrected_configuration; + /** Time step size calculation. */ + ReduceDynamics computing_time_step_size; + /** active-passive stress relaxation. */ + Dynamics1Level + stress_relaxation_first_half; + Dynamics1Level + stress_relaxation_second_half; + /** Constrain the Boundary. */ + BoundaryGeometryParallelToXAxis boundary_geometry_x; + SimpleDynamics + constrain_holder_x; + BoundaryGeometryParallelToYAxis boundary_geometry_y; + SimpleDynamics + constrain_holder_y; + DampingWithRandomChoice>> + plate_position_damping; + DampingWithRandomChoice>> + plate_rotation_damping; + /** Output */ + IOEnvironment io_environment; + BodyStatesRecordingToVtp write_states; + ObservedQuantityRecording write_plate_max_displacement; + + + /** Statistics for computing time. */ + TickCount t1 = TickCount::now(); + TimeInterval interval; + +public: + explicit Environment(Real loading_factor_new) : + PreSettingCase(), + loading_factor(loading_factor_new), + plate_body_inner(plate_body), + plate_observer_contact(plate_observer, {&plate_body}), + initialize_external_force(plate_body, + makeShared(Vec3d(0.0, 0.0, q*loading_factor / (PT * rho0_s) - gravitational_acceleration))), + corrected_configuration(plate_body_inner), + computing_time_step_size(plate_body), + stress_relaxation_first_half(plate_body_inner), + stress_relaxation_second_half(plate_body_inner), + boundary_geometry_x(plate_body, "BoundaryGeometryParallelToXAxis"), + constrain_holder_x(boundary_geometry_x, 0), + boundary_geometry_y(plate_body, "BoundaryGeometryParallelToYAxis"), + constrain_holder_y(boundary_geometry_y, 1), + plate_position_damping(0.5, plate_body_inner, "Velocity", physical_viscosity), + plate_rotation_damping(0.5, plate_body_inner, "AngularVelocity", physical_viscosity), + io_environment(system), + write_states(io_environment, system.real_bodies_), + write_plate_max_displacement("Position", io_environment, plate_observer_contact) + { + std::cout<<"Running simulation for loading factor = " << loading_factor <<"\n"; + /** Apply initial condition. */ + system.initializeSystemCellLinkedLists(); + system.initializeSystemConfigurations(); + corrected_configuration.exec(); + + write_states.writeToFile(0); + write_plate_max_displacement.writeToFile(0); + } + + virtual ~Environment() {}; + //---------------------------------------------------------------------- + // For ctest. + //---------------------------------------------------------------------- + int cmakeTest() + { + return 1; + } + + /** + * The main program + */ + void runCase() + { + + /** Set the starting time. + * From here the time stepping begins. + */ + GlobalStaticVariables::physical_time_ = 0.0; + /** Setup physical parameters. */ + int ite = 0; + Real end_time = 0.8; + Real output_period = end_time / 100.0; + Real dt = 0.0; + + /** + * Main loop + */ + while (GlobalStaticVariables::physical_time_ < end_time) + { + Real integral_time = 0.0; + while (integral_time < output_period) + { + if (ite % 100 == 0) + { + std::cout << "N=" << ite << " Time: " + << GlobalStaticVariables::physical_time_ << " dt: " + << dt << "\n"; + } + initialize_external_force.exec(dt); + stress_relaxation_first_half.exec(dt); + constrain_holder_x.exec(dt); + constrain_holder_y.exec(dt); + plate_position_damping.exec(dt); + plate_rotation_damping.exec(dt); + constrain_holder_x.exec(dt); + constrain_holder_y.exec(dt); + stress_relaxation_second_half.exec(dt); + + ite++; + dt = computing_time_step_size.exec(); + integral_time += dt; + GlobalStaticVariables::physical_time_ += dt; + } + write_plate_max_displacement.writeToFile(ite); + + TickCount t2 = TickCount::now(); + TickCount t3 = TickCount::now(); + interval += t3 - t2; + } + TickCount t4 = TickCount::now(); + + write_states.writeToFile(1); + + TimeInterval tt; + tt = t4 - t1 - interval; + std::cout << "Total wall time for computation: " << tt.seconds() << " seconds." << std::endl; + } +}; + +PYBIND11_MODULE(test_3d_thin_plate_python, m) +{ + py::class_(m, "thin_plate_from_sph_cpp") + .def(py::init()) + .def("CmakeTest", &Environment::cmakeTest) + .def("RunCase", &Environment::runCase); +}