├── .clang-format ├── .gitignore ├── .gitmodules ├── .style.yapf ├── CMakeLists.txt ├── Doxyfile ├── LICENSE ├── README.md ├── ci ├── Dockerfile └── Jenkinsfile ├── cmake └── FindSTKFMM.cmake ├── docs ├── Makefile ├── make.bat └── source │ ├── _static │ └── example.mp4 │ ├── conf.py │ ├── config_generation.rst │ ├── getting_started.rst │ ├── images │ ├── SkellySim_Logo_RGB_Full.png │ ├── example_ic.png │ └── example_plots.png │ ├── index.rst │ ├── installation.rst │ ├── postprocessing.rst │ └── visualization.rst ├── examples ├── analysis_example.py ├── ellipsoid │ └── gen_config.py ├── listener_mode │ ├── README.md │ ├── gen_config.py │ └── listener_example.py ├── oocyte │ └── gen_config.py ├── skelly_sim_slurm_sbatch.sh └── stokes_tests │ ├── body_const_force │ ├── calculate_velocity_error.py │ └── gen_config.py │ ├── body_const_force_in_sphere │ ├── gen_config.py │ └── velocity_vs_z.py │ ├── clamped_buckling │ ├── README.md │ ├── hopf-bifurcation.pdf │ ├── plot_deflection.py │ ├── sigma72.png │ ├── sigma72 │ │ └── gen_config.py │ ├── sigma80.png │ └── sigma80 │ │ └── gen_config.py │ ├── fiber_const_force │ ├── calc_velocity.py │ └── gen_config.py │ ├── fiber_const_force_in_sphere │ ├── README.md │ ├── calc_velocity.py │ └── gen_config.py │ ├── ps_const_force_in_sphere │ ├── README.md │ ├── analysis_example.py │ └── gen_config.py │ └── ps_const_torque_in_sphere │ ├── README.md │ ├── analysis_example.py │ └── gen_config.py ├── include ├── background_source.hpp ├── body.hpp ├── body_container.hpp ├── body_deformable.hpp ├── body_ellipsoidal.hpp ├── body_spherical.hpp ├── cnpy.hpp ├── eigen_matrix_plugin.h ├── eigen_quaternion_plugin.h ├── fiber_chebyshev_penalty_autodiff.hpp ├── fiber_chebyshev_penalty_autodiff_external.hpp ├── fiber_container_base.hpp ├── fiber_container_finite_difference.hpp ├── fiber_finite_difference.hpp ├── fiber_state.hpp ├── io_maps.hpp ├── kernels.hpp ├── listener.hpp ├── params.hpp ├── parse_util.hpp ├── periphery.hpp ├── point_source.hpp ├── rng.hpp ├── serialization.hpp ├── skelly_chebyshev.hpp ├── skelly_fiber.hpp ├── skelly_sim.hpp ├── solver.hpp ├── solver_hydro.hpp ├── streamline.hpp ├── system.hpp ├── trajectory_reader.hpp └── utils.hpp ├── misc ├── logos │ ├── CMYK │ │ ├── Icon │ │ │ ├── EPS │ │ │ │ ├── SkellySim_Icon_CMYK_Black.eps │ │ │ │ ├── SkellySim_Icon_CMYK_Full.eps │ │ │ │ └── SkellySim_Icon_CMYK_White.eps │ │ │ ├── JPG │ │ │ │ ├── SkellySim_Icon_CMYK_Black.jpg │ │ │ │ └── SkellySim_Icon_CMYK_Full.jpg │ │ │ ├── PDF │ │ │ │ ├── SkellySim_Icon_CMYK_Black.pdf │ │ │ │ └── SkellySim_Icon_CMYK_Full.pdf │ │ │ ├── PNG │ │ │ │ ├── SkellySim_Icon_CMYK_Black.png │ │ │ │ ├── SkellySim_Icon_CMYK_Full.png │ │ │ │ └── SkellySim_Icon_CMYK_White.png │ │ │ └── SVG │ │ │ │ ├── SkellySim_Icon_CMYK_Black.svg │ │ │ │ ├── SkellySim_Icon_CMYK_Full.svg │ │ │ │ └── SkellySim_Icon_CMYK_White.svg │ │ └── Logo │ │ │ ├── EPS │ │ │ ├── SkellySim_Logo_CMYK_Black.eps │ │ │ ├── SkellySim_Logo_CMYK_Full.eps │ │ │ └── SkellySim_Logo_CMYK_White.eps │ │ │ ├── JPG │ │ │ ├── SkellySim_Logo_CMYK_Black.jpg │ │ │ └── SkellySim_Logo_CMYK_Full.jpg │ │ │ ├── PDF │ │ │ ├── SkellySim_Logo_CMYK_Black.pdf │ │ │ └── SkellySim_Logo_CMYK_Full.pdf │ │ │ ├── PNG │ │ │ ├── SkellySim_Logo_CMYK_Black.png │ │ │ ├── SkellySim_Logo_CMYK_Full.png │ │ │ └── SkellySim_Logo_CMYK_White.png │ │ │ └── SVG │ │ │ ├── SkellySim_Logo_CMYK_Black.svg │ │ │ ├── SkellySim_Logo_CMYK_Full.svg │ │ │ └── SkellySim_Logo_CMYK_White.svg │ ├── Favicon │ │ └── SkellySim_Favicon.ico │ └── RGB │ │ ├── Icon │ │ ├── EPS │ │ │ ├── SkellySim_Icon_RGB_Black.eps │ │ │ ├── SkellySim_Icon_RGB_Full.eps │ │ │ └── SkellySim_Icon_RGB_White.eps │ │ ├── JPG │ │ │ ├── SkellySim_Icon_RGB_Black.jpg │ │ │ └── SkellySim_Icon_RGB_Full.jpg │ │ ├── PDF │ │ │ ├── SkellySim_Icon_RGB_Black.pdf │ │ │ └── SkellySim_Icon_RGB_Full.pdf │ │ ├── PNG │ │ │ ├── SkellySim_Icon_RGB_Black.png │ │ │ ├── SkellySim_Icon_RGB_Full.png │ │ │ └── SkellySim_Icon_RGB_White.png │ │ └── SVG │ │ │ ├── SkellySim_Icon_RGB_Black.svg │ │ │ ├── SkellySim_Icon_RGB_Full.svg │ │ │ └── SkellySim_Icon_RGB_White.svg │ │ └── Logo │ │ ├── EPS │ │ ├── SkellySim_Logo_RGB_Black.eps │ │ ├── SkellySim_Logo_RGB_Full.eps │ │ └── SkellySim_Logo_RGB_White.eps │ │ ├── JPG │ │ ├── SkellySim_Logo_RGB_Black.jpg │ │ └── SkellySim_Logo_RGB_Full.jpg │ │ ├── PDF │ │ ├── SkellySim_Logo_RGB_Black.pdf │ │ └── SkellySim_Logo_RGB_Full.pdf │ │ ├── PNG │ │ ├── SkellySim_Logo_RGB_Black.png │ │ ├── SkellySim_Logo_RGB_Full.png │ │ └── SkellySim_Logo_RGB_White.png │ │ └── SVG │ │ ├── SkellySim_Logo_RGB_Black.svg │ │ ├── SkellySim_Logo_RGB_Full.svg │ │ └── SkellySim_Logo_RGB_White.svg └── module_template.in ├── notes ├── model_changes.md ├── model_overview.pdf ├── model_overview.tex ├── planning.md └── skelly_sim_installation.md ├── pyproject.toml ├── scripts ├── skelly.blend ├── skelly_blend.py └── skelly_sim.def ├── src ├── core │ ├── background_source.cpp │ ├── body.cpp │ ├── body_container.cpp │ ├── body_deformable.cpp │ ├── body_ellipsoidal.cpp │ ├── body_spherical.cpp │ ├── cnpy.cpp │ ├── dynamic_instability.cpp │ ├── fiber_container_base.cpp │ ├── fiber_container_finite_difference.cpp │ ├── fiber_finite_difference.cpp │ ├── kernels.cpp │ ├── kernels.cu │ ├── listener.cpp │ ├── params.cpp │ ├── periphery.cpp │ ├── point_source.cpp │ ├── rng.cpp │ ├── solver_hydro.cpp │ ├── streamline.cpp │ ├── system.cpp │ ├── trajectory_reader.cpp │ └── utils.cpp ├── skelly_sim.cpp └── skelly_sim │ ├── Smooth_Closed_Surface_Quadrature_RBF.py │ ├── __init__.py │ ├── kernels.py │ ├── param_tools.py │ ├── paraview_utils │ ├── __init__.py │ ├── body_reader.py │ ├── body_reader_request.py │ ├── fiber_reader.py │ ├── fiber_reader_request.py │ ├── field_reader.py │ ├── field_reader_request.py │ └── trajectory_utility.py │ ├── periphery.py │ ├── precompute.py │ ├── quaternion.py │ ├── reader.py │ ├── shape_gallery.py │ ├── skelly_config.py │ └── testing.py └── tests ├── combined ├── bodies │ ├── test_compare_quadrature.py │ ├── test_ellipsoid_assphere_constforce.py │ ├── test_ellipsoid_oblate_z_force.py │ ├── test_ellipsoid_prolate_z_force.py │ └── test_ellipsoid_prolate_z_torque.py ├── regression_tests │ ├── fdfiber_compression_data.npy │ ├── fdfiber_compression_finalpositions.npz │ └── test_body_fdfiber_compression.py ├── test_body_const_force.py ├── test_body_const_torque.py ├── test_body_oscillatory_force.py ├── test_clamped_buckling_sigma72.py ├── test_clamped_buckling_sigma80.py ├── test_fiber_const_force.py ├── test_fiber_dualfilament.py └── test_fiber_uniform_background.py └── core ├── CMakeLists.txt ├── belos_fiberpenalty_test.cpp ├── dynamic_instability_test.cpp ├── jnewton_fiberpenalty_test.cpp ├── kernel_test.cpp ├── performance_fiberpenalty_combined.cpp ├── performance_fiberpenalty_combined_compare.cpp ├── performance_hydrodynamics_combined.cpp ├── performance_hydrodynamics_directrotation.cpp ├── solver_test.cpp ├── test_files ├── body_precompute_body_ellipsoid_n1.npz ├── ellipsoidal_body_n1.toml ├── fiber_container_fdf_n1.toml └── fiber_container_fdf_n10.toml └── unit_tests ├── CMakeLists.txt ├── julia_fiber_penalty_results.hpp ├── mpi_environment.hpp ├── unit_test_body_ellipsoidal.cpp ├── unit_test_fiber_chebyshev_penalty_autodiff.cpp ├── unit_test_fiber_finite_difference.cpp ├── unit_test_fibercontainer_finite_difference.cpp ├── unit_test_fibercontainer_finite_difference_mpi.cpp ├── unit_test_serialization.cpp ├── unit_test_serialization_mpi.cpp └── unit_test_skelly_chebyshev.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: LLVM 4 | TabWidth: 4 5 | ColumnLimit: 120 6 | IndentWidth: 4 7 | AlwaysBreakTemplateDeclarations: true 8 | ... 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /*.pyc 3 | *.out 4 | *.vf 5 | /examples/**/*.npz 6 | /examples/**/*.toml 7 | /examples/**/*.*_config 8 | /examples/**/*.cindex 9 | /examples/**/*.index 10 | 11 | _region_.* 12 | notes/*.aux 13 | notes/*.log 14 | 15 | /skelly_sim.egg-info 16 | __pycache__ 17 | *.blend1 18 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "extern/spdlog"] 2 | path = extern/spdlog 3 | url = https://github.com/gabime/spdlog.git 4 | [submodule "extern/trng4"] 5 | path = extern/trng4 6 | url = https://github.com/blackwer/trng4 7 | [submodule "extern/toml11"] 8 | path = extern/toml11 9 | url = https://github.com/ToruNiina/toml11 10 | [submodule "extern/msgpack-c"] 11 | path = extern/msgpack-c 12 | url = https://github.com/msgpack/msgpack-c 13 | [submodule "extern/eigen"] 14 | path = extern/eigen 15 | url = https://gitlab.com/libeigen/eigen 16 | [submodule "extern/autodiff"] 17 | path = extern/autodiff 18 | url = https://github.com/autodiff/autodiff.git 19 | -------------------------------------------------------------------------------- /.style.yapf: -------------------------------------------------------------------------------- 1 | [style] 2 | based_on_style = pep8 3 | column_limit = 120 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](docs/source/images/SkellySim_Logo_RGB_Full.png) 2 | 3 | SkellySim is a simulation package for simulating cellular components such as flexible filaments, motor proteins, and arbitrary rigid bodies. 4 | It's designed to be highly scalable, capable of both OpenMP and MPI style parallelism, while using the efficient STKFMM/PVFMM libraries for hydrodynamic resolution. 5 | 6 | # Documentation 7 | Flatiron users should read [Installation/Running at FI first](#installation-running-at-fi), 8 | since users there should not have to install the `C++` portion themselves. 9 | 10 | [SkellySim documentation](https://users.flatironinstitute.org/~rblackwell/py-skellysim) 11 | 12 | # Installation/Running at FI 13 | ## Basic setup 14 | There are (currently) two components to skelly_sim, the python portion, and the actual 15 | binary. The python portion is mostly for visualization, as well as generating config files and 16 | precompute data. The binary (C++) portion is for actually running the simulation. 17 | 18 | To install the python portion (in your virtual environment, conda environment, or using the `pip3 --user` option). For a virtualenv 19 | ```bash 20 | module load python 21 | python3 -m venv /path/to/my/virtualenv 22 | source /path/to/my/virtualenv/bin/activate 23 | pip3 install git+https://github.com/flatironinstitute/SkellySim 24 | ``` 25 | or for a conda environment 26 | ``` 27 | conda create -n myenvname 28 | conda activate myenvname 29 | pip3 install git+https://github.com/flatironinstitute/SkellySim 30 | ``` 31 | Due to the complex dependencies of the C++ portion, until I finish packaging things, you can use my modules. 32 | ```bash 33 | module -q purge 34 | # REMOVE python module from this if you are using conda!!!! 35 | module use ~rblackwell/modules 36 | module -q load gcc/11 openmpi python trilinos pvfmm/1.3.0 intel-oneapi-mkl cuda flexiblas skelly_sim 37 | ``` 38 | 39 | ## Building from source at FI (developers or externs) 40 | 41 | 42 | ```bash 43 | module -q purge 44 | module -q load gcc/11 openmpi python cmake trilinos pvfmm stkfmm intel-oneapi-mkl cuda boost flexiblas 45 | 46 | git clone https://github.com/flatironinstitute/SkellySim 47 | cd SkellySim 48 | git submodule update --init --recursive 49 | mkdir -p build 50 | cd build 51 | cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS=-march=broadwell -DCMAKE_CUDA_ARCHITECTURES="70;75;86;90" -DBLA_VENDOR=FlexiBLAS 52 | make -j$((2*$(nproc))) 53 | ``` 54 | -------------------------------------------------------------------------------- /ci/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nvidia/cuda:11.7.1-cudnn8-devel-ubuntu22.04 2 | 3 | ENV MKLROOT=/opt/intel/oneapi/mkl/latest 4 | ENV STKFMM_ROOT=/usr/local 5 | ENV CXXFLAGS_="-I/usr/lib/x86_64-linux-gnu/openmpi/include -march=broadwell -g" 6 | ENV PVFMM_DIR="/usr/local/share/pvfmm" 7 | 8 | RUN apt -y update && \ 9 | apt -y install curl gpg software-properties-common && \ 10 | curl -s https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB | gpg --dearmor > /usr/share/keyrings/oneapi-archive-keyring.gpg && \ 11 | echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" > /etc/apt/sources.list.d/oneAPI.list && \ 12 | apt-add-repository multiverse && \ 13 | apt-add-repository universe && \ 14 | apt -y update && \ 15 | apt -y install libboost-dev intel-oneapi-mkl-devel build-essential libopenmpi-dev \ 16 | cmake libeigen3-dev ninja-build git python3-pip && \ 17 | apt -y autoclean && \ 18 | rm -rf /var/cache/apt/* /var/lib/apt/lists/* 19 | 20 | RUN mkdir build 21 | 22 | RUN cd build && \ 23 | git clone https://github.com/dmalhotra/pvfmm && \ 24 | cd pvfmm && \ 25 | git checkout v1.3.0 && \ 26 | git submodule update --init && \ 27 | mkdir build && \ 28 | cd build && \ 29 | cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Release \ 30 | -DCMAKE_CXX_FLAGS="$CXXFLAGS_" -GNinja && \ 31 | ninja && \ 32 | ninja install 33 | 34 | RUN cd build && \ 35 | git clone https://github.com/wenyan4work/STKFMM && \ 36 | cd STKFMM && \ 37 | mkdir build && \ 38 | cd build && \ 39 | pvfmm_DIR=/usr/local/share/pvfmm cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local \ 40 | -DCMAKE_CXX_FLAGS="$CXXFLAGS_" -GNinja && \ 41 | ninja && \ 42 | ninja install 43 | 44 | RUN cd build && \ 45 | curl -L -o trilinos.tar.gz https://github.com/trilinos/Trilinos/archive/refs/tags/trilinos-release-13-2-0.tar.gz && \ 46 | tar -xf trilinos.tar.gz && \ 47 | cd Trilinos-trilinos-release-13-2-0 && \ 48 | mkdir build && \ 49 | cd build && \ 50 | cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local \ 51 | -DTrilinos_VERBOSE_CONFIGURE:BOOL=OFF -DBUILD_SHARED_LIBS:BOOL=ON -DCMAKE_CXX_STANDARD:STRING=14 \ 52 | -DTrilinos_ENABLE_ALL_OPTIONAL_PACKAGES:BOOL=OFF -DTrilinos_ENABLE_ALL_PACKAGES:BOOL=OFF \ 53 | -DTrilinos_ENABLE_OpenMP=ON -DTrilinos_ENABLE_Kokkos:BOOL=ON -DTrilinos_ENABLE_Belos:BOOL=ON \ 54 | -DTrilinos_ENABLE_Tpetra:BOOL=ON -DTrilinos_ENABLE_Teuchos:BOOL=ON -DBUILD_TESTING:BOOL=OFF \ 55 | -DTrilinos_ENABLE_Fortran:BOOL=OFF -D TPL_ENABLE_MPI=ON \ 56 | -DTPL_BLAS_LIBRARIES=/opt/intel/oneapi/mkl/latest/lib/intel64/libmkl_rt.so \ 57 | -DTPL_LAPACK_LIBRARIES=/opt/intel/oneapi/mkl/latest/lib/intel64/libmkl_rt.so \ 58 | -DCMAKE_CXX_FLAGS="$CXXFLAGS_" -GNinja && \ 59 | ninja && \ 60 | ninja install 61 | 62 | RUN rm -rf build 63 | 64 | RUN mkdir -p $PVFMM_DIR && \ 65 | cd $PVFMM_DIR && \ 66 | curl -o Precomp_stokes_PVel_m16.data https://users.flatironinstitute.org/~rblackwell/pvfmm/Precomp_stokes_PVel_m16.data && \ 67 | curl -o Precomp_stokes_vel_m16.data https://users.flatironinstitute.org/~rblackwell/pvfmm/Precomp_stokes_vel_m16.data 68 | -------------------------------------------------------------------------------- /ci/Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent none 3 | options { 4 | disableConcurrentBuilds() 5 | buildDiscarder(logRotator(numToKeepStr: '8', daysToKeepStr: '20')) 6 | timeout(time: 1, unit: 'HOURS') 7 | } 8 | stages { 9 | stage('main') { 10 | agent { 11 | dockerfile { 12 | dir 'ci' 13 | args '--gpus 1' 14 | label 'v100' 15 | } 16 | } 17 | environment { 18 | HOME = "${WORKSPACE}" 19 | PYPATH = "${WORKSPACE}/.local/bin" 20 | LC_ALL = "C" 21 | MKL_THREADING_LAYER = "GNU" 22 | PVFMM_DIR = "/usr/local/share/pvfmm" 23 | LD_LIBRARY_PATH = "/opt/intel/oneapi/mkl/latest/lib/intel64:/usr/local/lib" 24 | OMP_NUM_THREADS = "4" 25 | } 26 | steps { 27 | sh 'printenv | egrep "(^HOME|^PATH|^STKFMM_ROOT|^MKLROOT|^LD_LIBRARY_PATH|^pvfmm_DIR|^MKL_THREADING_LAYER|^LC_ALL|^CXX_FLAGS_|^PVFMM_DIR|^OMP_NUM_THREADS)"' 28 | sh 'python3 -m pip install --user ".[tests]"' 29 | sh 'git submodule update --init' 30 | sh 'cmake -B build . -DCMAKE_BUILD_TYPE=Release \ 31 | -DCMAKE_CXX_FLAGS="$CXXFLAGS_" -DBLAS_LIBRARIES=$MKLROOT/lib/intel64/libmkl_rt.so \ 32 | -DLAPACK_LIBRARIES=$MKLROOT/lib/intel64/libmkl_rt.so -DCMAKE_CUDA_ARCHITECTURES="70" -GNinja' 33 | sh 'ninja -C build -j $OMP_NUM_THREADS' 34 | sh 'cd build && ctest --output-on-failure' 35 | sh 'PATH=$PYPATH:$PWD/build:$PATH python3 -m pytest tests --verbose' 36 | } 37 | } 38 | } 39 | post { 40 | failure { 41 | emailext subject: '$PROJECT_NAME - Build #$BUILD_NUMBER - $BUILD_STATUS', 42 | body: '''$PROJECT_NAME - Build #$BUILD_NUMBER - $BUILD_STATUS 43 | Check console output at $BUILD_URL to view full results. 44 | Building $BRANCH_NAME for $CAUSE 45 | $JOB_DESCRIPTION 46 | Changes: 47 | $CHANGES 48 | End of build log: 49 | ${BUILD_LOG,maxLines=200} 50 | ''', 51 | recipientProviders: [ 52 | [$class: 'DevelopersRecipientProvider'], 53 | ], 54 | replyTo: '$DEFAULT_REPLYTO', 55 | to: 'rblackwell@flatironinstitute.org' 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /cmake/FindSTKFMM.cmake: -------------------------------------------------------------------------------- 1 | # Find the STKFMM Library 2 | # 3 | # STKFMM_FOUND - System has MKL 4 | # STKFMM_INCLUDE_DIRS - MKL include files directories 5 | # STKFMM_LIBRARIES - The MKL libraries 6 | # STKFMM_INTERFACE_LIBRARY - MKL interface library 7 | # STKFMM_SEQUENTIAL_LAYER_LIBRARY - MKL sequential layer library 8 | # STKFMM_CORE_LIBRARY - MKL core library 9 | # 10 | # Example usage: 11 | # 12 | # find_package(STKFMM) 13 | # if(STKFMM_FOUND) 14 | # target_link_libraries(TARGET ${STKFMM_LIBRARIES}) 15 | # endif() 16 | 17 | # If already in cache, be silent 18 | if (STKFMM_INCLUDE_DIRS AND STKFMM_LIBRARIES) 19 | set (STKFMM_FIND_QUIETLY TRUE) 20 | endif() 21 | 22 | find_path(STKFMM_INCLUDE_DIR NAMES STKFMM/STKFMM.h HINTS $ENV{STKFMM_ROOT}/include $ENV{STKFMM_BASE}/include) 23 | 24 | find_library(STKFMM_LIBRARY 25 | NAMES STKFMM_STATIC 26 | PATHS $ENV{STKFMM_ROOT}/lib64 27 | $ENV{STKFMM_ROOT}/lib 28 | $ENV{STKFMM_BASE}/lib64 29 | $ENV{STKFMM_BASE}/lib 30 | NO_DEFAULT_PATH) 31 | 32 | set(STKFMM_INCLUDE_DIRS ${STKFMM_INCLUDE_DIR}) 33 | set(STKFMM_LIBRARIES ${STKFMM_LIBRARY}) 34 | 35 | # Handle the QUIETLY and REQUIRED arguments and set STKFMM_FOUND to TRUE if 36 | # all listed variables are TRUE. 37 | INCLUDE(FindPackageHandleStandardArgs) 38 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(STKFMM DEFAULT_MSG STKFMM_LIBRARIES STKFMM_INCLUDE_DIRS) 39 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.https://www.sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/source/_static/example.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/docs/source/_static/example.mp4 -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | sys.path.insert(0, os.path.abspath('../..')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'SkellySim' 21 | copyright = '2022, Robert Blackwell, Gokberk Kabacaoglu' 22 | author = 'Robert Blackwell, Gokberk Kabacaoglu' 23 | 24 | # The full version, including alpha/beta/rc tags 25 | release = '0.9.12' 26 | 27 | 28 | # -- General configuration --------------------------------------------------- 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon'] 34 | 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # List of patterns, relative to source directory, that match files and 40 | # directories to ignore when looking for source files. 41 | # This pattern also affects html_static_path and html_extra_path. 42 | exclude_patterns = [] 43 | 44 | 45 | # -- Options for HTML output ------------------------------------------------- 46 | 47 | # The theme to use for HTML and HTML Help pages. See the documentation for 48 | # a list of builtin themes. 49 | # 50 | html_theme = 'sphinx_book_theme' 51 | 52 | # Add any paths that contain custom static files (such as style sheets) here, 53 | # relative to this directory. They are copied after the builtin static files, 54 | # so a file named "default.css" will overwrite the builtin "default.css". 55 | html_static_path = ['_static'] 56 | 57 | 58 | # Remove excessively long class signature from dataclass objects 59 | def process_sig(app, what, name, obj, options, signature, return_annotation): 60 | from dataclasses import is_dataclass 61 | if signature and is_dataclass(obj): 62 | signature = "()" 63 | return (signature, return_annotation) 64 | 65 | def setup(app): 66 | app.connect("autodoc-process-signature", process_sig) 67 | 68 | 69 | autodoc_mock_imports = ["function_generator"] 70 | 71 | html_theme_options = { 72 | "extra_navbar": 'SkellySim on Github', 73 | } 74 | -------------------------------------------------------------------------------- /docs/source/images/SkellySim_Logo_RGB_Full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/docs/source/images/SkellySim_Logo_RGB_Full.png -------------------------------------------------------------------------------- /docs/source/images/example_ic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/docs/source/images/example_ic.png -------------------------------------------------------------------------------- /docs/source/images/example_plots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/docs/source/images/example_plots.png -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. SkellySim documentation master file, created by 2 | sphinx-quickstart on Mon Mar 21 15:26:32 2022. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to SkellySim's documentation! 7 | ===================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | installation 14 | getting_started 15 | config_generation 16 | visualization 17 | postprocessing 18 | 19 | 20 | Indices and tables 21 | ================== 22 | 23 | * :ref:`genindex` 24 | * :ref:`modindex` 25 | * :ref:`search` 26 | -------------------------------------------------------------------------------- /docs/source/postprocessing.rst: -------------------------------------------------------------------------------- 1 | .. _post-processing: 2 | 3 | 4 | Post-processing 5 | --------------- 6 | 7 | For post-processing that require knowledge of the velocity at arbitrary points in the fluid, 8 | SkellySim supports an "interactive" mode where it runs inside a containing python process in 9 | :code:`Listener` mode. This allows you to exploit the SkellySim machinery on demand to 10 | calculate various quantities without intermediate storage. This is extremely useful to generate 11 | your own velocity fields, stream lines, and vortex lines at any given simulation point. Please 12 | see the following to get started: `examples/listener_mode/listener_example.py 13 | `_. 14 | 15 | 16 | Trajectory format 17 | ----------------- 18 | 19 | The trajectory format is a single file that consists of consecutive 'frames' with data 20 | serialized in the :code:`msgpack` format. This makes doing post-processing in your language of 21 | choice reasonably straightforward, though most existing code is centered around :code:`python`, 22 | since that's what the configuration language and visualization are implemented in. Writing 23 | readers in other languages, especially those that have builtin :code:`map` and :code:`list` 24 | types, shouldn't be too much of a problem though. 25 | 26 | Each frame of data is simply a nested dictionary of key-value pairs, which we provide a 27 | convenience wrapper class for in :obj:`Python`. There is some example usage of this class in 28 | :ref:`getting-started`, and we suggest users work from there or directly from 29 | `examples/analysis_example.py 30 | `_. 31 | 32 | 33 | TrajectoryReader class 34 | ---------------------- 35 | 36 | .. autoclass:: skelly_sim.reader.TrajectoryReader 37 | :members: 38 | 39 | 40 | Listener class 41 | -------------- 42 | 43 | .. autoclass:: skelly_sim.reader.Listener 44 | :members: 45 | 46 | 47 | Request class 48 | ------------- 49 | 50 | .. autoclass:: skelly_sim.reader.Request 51 | :members: 52 | 53 | 54 | StreamlinesRequest class 55 | ------------------------ 56 | 57 | .. autoclass:: skelly_sim.reader.StreamlinesRequest 58 | :members: 59 | 60 | 61 | VelocityFieldRequest class 62 | -------------------------- 63 | 64 | .. autoclass:: skelly_sim.reader.VelocityFieldRequest 65 | :members: 66 | -------------------------------------------------------------------------------- /docs/source/visualization.rst: -------------------------------------------------------------------------------- 1 | .. _visualization: 2 | 3 | Visualization 4 | ============= 5 | 6 | There are currently two methods for visualization, and neither is anywhere near perfect. 7 | 8 | Visualization with Paraview 9 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 10 | 11 | Paraview provides a nice way to overlay text, view streamlines, and generally customize your 12 | visualization. Unfortunately the renderer is only OK and getting it to work with a custom 13 | python environment can be very finicky. 14 | 15 | 16 | Visualization with Blender 17 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 18 | 19 | Blender is not really designed with scientific workflows in mind, but it is very flexible, 20 | plays more nicely with user python environments, has multiple renderers, all of which are 21 | better than Paraview's, and is just generally way more developer friendly. It unfortunately 22 | doesn't have many of the nice builtin-facilities of Paraview (like text overlay and 23 | streamlines), and so is therefore very much a work in progress. There is an experimental 24 | blender script `skelly_blend.py` that hopefully will become the primary way to visualize the 25 | simulation in the future. For actually doing science, Paraview is still the preferred choice, 26 | but hopefully that will change in the future. 27 | -------------------------------------------------------------------------------- /examples/analysis_example.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib 3 | matplotlib.use('TKAgg') 4 | import matplotlib.pyplot as plt 5 | 6 | from skelly_sim.reader import TrajectoryReader, Listener, Request 7 | 8 | traj = TrajectoryReader('skelly_config.toml') 9 | shell_radius = traj.config_data['periphery']['radius'] 10 | body_radius = traj.config_data['bodies'][0]['radius'] 11 | 12 | body_pos = np.empty(shape=(len(traj), 3)) # COM body position in time 13 | plus_pos = np.empty(shape=(len(traj), 3)) # fiber plus end in time 14 | minus_pos = np.empty(shape=(len(traj), 3)) # fiber minus end in time 15 | 16 | for i in range(len(traj)): 17 | traj.load_frame(i) 18 | body_pos[i, :] = traj['bodies'][0]['position_'] 19 | minus_pos[i, :] = traj['fibers'][0]['x_'][0, :] 20 | plus_pos[i, :] = traj['fibers'][0]['x_'][-1, :] 21 | 22 | print("system keys: " + str(list(traj.keys()))) 23 | print("fiber keys: " + str(list(traj['fibers'][0].keys()))) 24 | print("body keys: " + str(list(traj['bodies'][0].keys()))) 25 | print("shell keys: " + str(list(traj['shell'].keys()))) 26 | 27 | ax1 = plt.subplot(1, 2, 2) 28 | 29 | ax1.plot(traj.times, body_pos[:, 2], traj.times, plus_pos[:,2], traj.times, minus_pos[:,2]) 30 | 31 | # Fire up SkellySim in "listener" mode 32 | listener = Listener(binary='skelly_sim_release') 33 | 34 | # All analysis requests are done via a "Request" object 35 | req = Request() 36 | 37 | # specify frame number to evaluate and evaluator (CPU, GPU, FMM) 38 | req.frame_no = 11 39 | req.evaluator = "GPU" 40 | 41 | # Request velocity field 42 | tmp = np.linspace(-shell_radius, shell_radius, 20) 43 | xm, ym, zm = np.meshgrid(tmp, tmp, tmp) 44 | xcube = np.array((xm.ravel(), ym.ravel(), zm.ravel())).T 45 | # Filter out points outside periphery and inside body 46 | relpoints = np.where((np.linalg.norm(xcube - body_pos[11,:], axis=1) > body_radius) & 47 | (np.linalg.norm(xcube, axis=1) < shell_radius)) 48 | req.velocity_field.x = xcube[relpoints] 49 | 50 | # Make our request to SkellySim! Might take a second... 51 | res = listener.request(req) 52 | 53 | x = req.velocity_field.x 54 | v = res['velocity_field'] 55 | 56 | ax2 = plt.subplot(1, 2, 2, projection="3d") 57 | ax2.quiver(x[:, 0], x[:, 1], x[:, 2], v[:, 0], v[:, 1], v[:, 2]) 58 | 59 | plt.show() 60 | -------------------------------------------------------------------------------- /examples/ellipsoid/gen_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import numpy as np 5 | from skelly_sim.skelly_config import ConfigEllipsoidal, Body, Fiber 6 | 7 | config_file = 'skelly_config.toml' 8 | if len(sys.argv) == 1: 9 | print( 10 | "Using default toml file for output: 'skelly_config.toml'. " 11 | "Provide an alternative filename argument to this script to use that instead." 12 | ) 13 | elif len(sys.argv) != 2: 14 | print("Supply output config path (blahblah.toml) as sole argument") 15 | sys.exit() 16 | else: 17 | config_file = sys.argv[1] 18 | 19 | np.random.seed(100) 20 | 21 | n_fibers = 2000 22 | 23 | # create a config object and set the system parameters 24 | config = ConfigEllipsoidal() 25 | config.params.dt_write = 0.1 26 | config.params.dt_initial = 8E-3 27 | config.params.dt_max = 8E-3 28 | 29 | # generate a list of fibers. They don't have positions though, so we need to fill those in 30 | config.fibers = [ 31 | Fiber(length=1.0, 32 | bending_rigidity=2.5E-3, 33 | parent_body=-1, 34 | force_scale=-0.05, 35 | minus_clamped=True, 36 | n_nodes=64) for i in range(n_fibers) 37 | ] 38 | 39 | config.periphery.n_nodes = 8000 40 | 41 | # move our fibers to the surface of the periphery and populate their position fields 42 | config.periphery.move_fibers_to_surface(config.fibers, ds_min=0.1) 43 | 44 | # output our config 45 | config.save(config_file) 46 | 47 | # uncomment the following to plot fibers on your ellipsoid 48 | # config.plot_fibers() 49 | -------------------------------------------------------------------------------- /examples/listener_mode/README.md: -------------------------------------------------------------------------------- 1 | Point source with constant torque and force inside of a sphere. Measure streamlines, vortex 2 | lines, and velocity field in x-y plane. 3 | 4 | ```bash 5 | ./gen_config.py 6 | skelly_precompute 7 | skelly_sim 8 | ./listener_example.py 9 | ``` 10 | -------------------------------------------------------------------------------- /examples/listener_mode/gen_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import numpy as np 5 | from skelly_sim.skelly_config import ConfigSpherical, Point, Body 6 | 7 | config_file = 'skelly_config.toml' 8 | if len(sys.argv) == 1: 9 | print( 10 | "Using default toml file for output: 'skelly_config.toml'. " 11 | "Provide an alternative filename argument to this script to use that instead." 12 | ) 13 | elif len(sys.argv) != 2: 14 | print("Supply output config path (blahblah.toml) as sole argument") 15 | sys.exit() 16 | else: 17 | config_file = sys.argv[1] 18 | 19 | np.random.seed(100) 20 | 21 | # create a config object and set the system parameters 22 | # config = ConfigSpherical() 23 | config = ConfigSpherical() 24 | config.params.eta = 1.0 25 | config.params.dt_initial = 1E-1 26 | config.params.dt_min = 1E-4 27 | config.params.dt_max = 1E-1 28 | config.params.dt_write = 1E-1 29 | config.params.t_final = 0.2 30 | config.params.gmres_tol = 1E-10 31 | config.params.seed = 130319 32 | config.params.pair_evaluator = "CPU" 33 | 34 | config.periphery.n_nodes = 2000 35 | config.periphery.radius = 3.0 36 | 37 | config.point_sources = [ 38 | Point(position=[0.0, 0.0, 0.0], force=[0.0, 0.0, 1.0], torque=[0.0, 0.0, 10.0]) 39 | ] 40 | 41 | # output our config 42 | config.save(config_file) 43 | -------------------------------------------------------------------------------- /examples/listener_mode/listener_example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import numpy as np 4 | from skelly_sim.reader import Listener, Request 5 | import matplotlib.pyplot as plt 6 | import matplotlib as mpl 7 | mpl.use('TKAgg') 8 | 9 | # Fire up SkellySim in "listener" mode 10 | listener = Listener() 11 | 12 | # All analysis requests are done via a "Request" object 13 | req = Request() 14 | 15 | # specify frame number to evaluate and evaluator (CPU, GPU, FMM) 16 | req.frame_no = 1 17 | req.evaluator = "GPU" 18 | 19 | # Request 3 streamlines from t=[-10, 10] 20 | req.streamlines.x0 = np.array([ 21 | [0.25, 0.0, 0.0], 22 | [0.5, 0.0, 0.0], 23 | [1.0, 0.0, 0.0], 24 | ]) 25 | req.streamlines.t_final = 10.0 26 | 27 | # Request single vortex line from t=[-10, 10] 28 | req.vortexlines.x0 = np.array([ 29 | [0.0, 0.0, 2.0], 30 | ]) 31 | req.vortexlines.t_final = 10.0 32 | 33 | # Request velocity field in z=0 plane 34 | tmp = np.linspace(-2, 2, 100) 35 | xm, ym, zm = np.meshgrid(tmp, tmp, 0.0) 36 | req.velocity_field.x = np.array((xm.ravel(), ym.ravel(), zm.ravel())).T 37 | 38 | # Make our request to SkellySim! Might take a second... 39 | res = listener.request(req) 40 | 41 | # Plot our streamlines 42 | ax1 = plt.subplot(1, 2, 1, projection='3d') 43 | for sl in res['streamlines']: 44 | x = sl['x'] # Points along streamline 45 | v = sl['val'] # Vector velocities at streamline evaluation points. Not used here, but available 46 | t = sl['time'] # time of evals. Not used here, but available 47 | 48 | ax1.plot3D(x[:,0], x[:,1], x[:,2]) 49 | 50 | 51 | # Streamplot of x,y components of velocity field in z=0 plane 52 | ax2 = plt.subplot(1, 2, 2) 53 | xm, ym = xm.squeeze(axis=2), ym.squeeze(axis=2) 54 | U = res['velocity_field'][:, 0].reshape(*xm.shape) 55 | V = res['velocity_field'][:, 1].reshape(*ym.shape) 56 | ax2.streamplot(xm, ym, U, V) 57 | 58 | # IT'S GO TIME 59 | plt.show() 60 | -------------------------------------------------------------------------------- /examples/oocyte/gen_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import toml 5 | import numpy as np 6 | from skelly_sim.skelly_config import ConfigRevolution, Body, Fiber 7 | from skelly_sim.shape_gallery import Envelope 8 | 9 | config_file = 'skelly_config.toml' 10 | if len(sys.argv) == 1: 11 | print( 12 | "Using default toml file for output: 'skelly_config.toml'. " 13 | "Provide an alternative filename argument to this script to use that instead." 14 | ) 15 | elif len(sys.argv) != 2: 16 | print("Supply output config path (blahblah.toml) as sole argument") 17 | sys.exit() 18 | else: 19 | config_file = sys.argv[1] 20 | 21 | # This is just a script that creates a toml file using some utility functions I've written in in skelly_config 22 | numpy_seed = 100 23 | n_fibers = 3000 24 | np.random.seed(numpy_seed) 25 | 26 | # config is the object that will actually get turned into a toml file at the end 27 | config = ConfigRevolution() 28 | 29 | # system wide parameters 30 | config.params.dt_write = 0.1 31 | config.params.dt_initial = 1E-2 32 | config.params.dt_max = 1E-2 33 | config.params.periphery_interaction_flag = False 34 | config.params.seed = 350 35 | config.params.viscosity = 1 36 | 37 | # Create a list of identical base fibers to work with 38 | config.fibers = [ 39 | Fiber(length=1.0, 40 | bending_rigidity=2.5E-3, 41 | force_scale=-0.05, 42 | minus_clamped=True, 43 | n_nodes=32) for i in range(n_fibers) 44 | ] 45 | 46 | # envelope is our surface of revolution. My algorithm tries to get the target nodes requested, 47 | # but will likely return an object with slightly more nodes. This is a required option 48 | config.periphery.envelope.n_nodes_target = 6000 49 | # lower/upper bound are required options. ideally your function should go to zero at the upper/lower bounds 50 | config.periphery.envelope.lower_bound = -3.75 51 | config.periphery.envelope.upper_bound = 3.75 52 | # required option. this is the function you're revolving around the 'x' axis. 'x' needs to be 53 | # the independent variable. Currently the function has to be a one-liner 54 | config.periphery.envelope.height = "0.5 * T * ((1 + 2*x/length)**p1) * ((1 - 2*x/length)**p2) * length" 55 | # All the parameters that go into our height function 56 | config.periphery.envelope.T = 0.72 57 | config.periphery.envelope.p1 = 0.4 58 | config.periphery.envelope.p2 = 0.2 59 | config.periphery.envelope.length = 7.5 60 | 61 | # minimum separation between fibers minus ends allowed on surface. distance is simple euclidean distance 62 | ds_min = 0.1 63 | config.periphery.move_fibers_to_surface(config.fibers, ds_min) 64 | 65 | # output our config 66 | config.save(config_file) 67 | 68 | # just uncomment this to show a quick visualization of fiber positions 69 | # config.plot_fibers() 70 | -------------------------------------------------------------------------------- /examples/skelly_sim_slurm_sbatch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | #SBATCH --partition=scc 3 | #SBATCH --nodes=1 4 | #SBATCH --ntasks-per-node=8 5 | #SBATCH --exclusive 6 | #SBATCH --job-name=skelly_sim 7 | #SBATCH --output=skelly_sim.log 8 | #SBATCH --constraint=rome 9 | 10 | module purge 11 | module use ~rblackwell/modules 12 | module load gcc/11 openmpi intel-oneapi-mkl trilinos pvfmm stkfmm fftw flexiblas skelly_sim 13 | module list 14 | ldd $(which skelly_sim) 15 | 16 | export OMP_NUM_THREADS=$((${SLURM_CPUS_ON_NODE}/${SLURM_NTASKS_PER_NODE})) 17 | export OMP_PROC_BIND=spread 18 | export OMP_PLACES=threads 19 | 20 | srun --cpu-bind=ldoms skelly_sim --config-file=skelly_config.toml 21 | -------------------------------------------------------------------------------- /examples/stokes_tests/body_const_force/calculate_velocity_error.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import numpy as np 4 | from skelly_sim.reader import TrajectoryReader 5 | 6 | traj = TrajectoryReader('skelly_config.toml') 7 | 8 | # need beginning/end positions to calculate average velocity 9 | traj.load_frame(0) 10 | z_initial = traj['bodies'][0]['position_'][2] 11 | traj.load_frame(len(traj) - 1) 12 | z_final = traj['bodies'][0]['position_'][2] 13 | 14 | dt = traj.times[-1] - traj.times[0] 15 | 16 | # We need the hydrodynamic radius, which is slightly different than the supplied 'attachment' 17 | # radius. let's just grab it from the precompute data. 18 | precompute_data = np.load(traj.config_data['bodies'][0]['precompute_file']) 19 | radius = np.linalg.norm(precompute_data["node_positions_ref"][0]) 20 | eta = traj.config_data['params']['eta'] 21 | force = traj.config_data['bodies'][0]['external_force'][2] 22 | 23 | v_theoretical = force / (6 * np.pi * eta * radius) 24 | v_measured = (z_final - z_initial) / dt 25 | 26 | print("Measured velocity: {}".format(v_measured)) 27 | print("Theoretical velocity: {}".format(v_theoretical)) 28 | print("Error |1 - v/v0|: {}".format(abs(1 - v_measured / v_theoretical))) 29 | -------------------------------------------------------------------------------- /examples/stokes_tests/body_const_force/gen_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import numpy as np 5 | from skelly_sim.skelly_config import Config, Body 6 | 7 | config_file = 'skelly_config.toml' 8 | if len(sys.argv) == 1: 9 | print("Using default toml file for output: 'skelly_config.toml'. " 10 | "Provide an alternative filename argument to this script to use that instead.") 11 | elif len(sys.argv) != 2: 12 | print("Supply output config path (blahblah.toml) as sole argument") 13 | sys.exit() 14 | else: 15 | config_file = sys.argv[1] 16 | 17 | np.random.seed(100) 18 | 19 | # create a config object and set the system parameters 20 | config = Config() 21 | config.params.eta = 0.9 22 | config.params.dt_initial = 1E-1 23 | config.params.dt_min = 1E-4 24 | config.params.dt_max = 1E-1 25 | config.params.dt_write = 1E-1 26 | config.params.t_final = 10.0 27 | config.params.gmres_tol = 1E-10 28 | config.params.seed = 130319 29 | config.params.pair_evaluator = "CPU" 30 | 31 | config.bodies = [ 32 | Body(n_nucleation_sites=0, 33 | position=[0.0, 0.0, 0.0], 34 | shape='sphere', 35 | radius=0.5, 36 | n_nodes=400, 37 | external_force=[0.0, 0.0, 1.5]) 38 | ] 39 | 40 | config.save(config_file) 41 | -------------------------------------------------------------------------------- /examples/stokes_tests/body_const_force_in_sphere/gen_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import numpy as np 5 | from skelly_sim.skelly_config import ConfigSpherical, Body, Fiber 6 | 7 | config_file = 'skelly_config.toml' 8 | if len(sys.argv) == 1: 9 | print( 10 | "Using default toml file for output: 'skelly_config.toml'. " 11 | "Provide an alternative filename argument to this script to use that instead." 12 | ) 13 | elif len(sys.argv) != 2: 14 | print("Supply output config path (blahblah.toml) as sole argument") 15 | sys.exit() 16 | else: 17 | config_file = sys.argv[1] 18 | 19 | np.random.seed(100) 20 | 21 | # create a config object and set the system parameters 22 | config = ConfigSpherical() 23 | config.params.eta = 0.19884428157961156 24 | config.params.dt_initial = 1E-2 25 | config.params.dt_min = 1E-4 26 | config.params.dt_max = 1E-2 27 | config.params.dt_write = 1E-2 28 | config.params.t_final = 40.0 29 | config.params.gmres_tol = 1E-8 30 | config.params.seed = 130319 31 | 32 | config.bodies = [ 33 | Body(n_nucleation_sites=0, 34 | position=[0.0, 0.0, 0.0], 35 | shape='sphere', 36 | radius=0.5, 37 | n_nodes=2000, 38 | external_force=[0.0, 0.0, 10.0]) 39 | ] 40 | 41 | config.periphery.n_nodes = 6000 42 | config.periphery.radius = 4.0 / 1.04 43 | 44 | # output our config 45 | config.save(config_file) 46 | 47 | # uncomment the following to plot fibers on your ellipsoid 48 | # config.plot_fibers() 49 | -------------------------------------------------------------------------------- /examples/stokes_tests/body_const_force_in_sphere/velocity_vs_z.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import numpy as np 4 | import matplotlib 5 | 6 | matplotlib.use('TKAgg') 7 | import matplotlib.pyplot as plt 8 | 9 | from skelly_sim.reader import TrajectoryReader 10 | 11 | traj = TrajectoryReader('skelly_config.toml') 12 | body_z = np.empty(shape=(len(traj))) # COM body position in time 13 | 14 | for i in range(len(traj)): 15 | traj.load_frame(i) 16 | body_z[i] = traj['bodies'][0]['position_'][2] 17 | 18 | v = np.diff(body_z) / np.diff(traj.times) 19 | 20 | # Compare to theoretical velocity of confined sphere 21 | body_precompute_data = np.load(traj.config_data['bodies'][0]['precompute_file']) 22 | shell_precompute_data = np.load(traj.config_data['periphery']['precompute_file']) 23 | 24 | r_body = np.linalg.norm(body_precompute_data["node_positions_ref"][0]) 25 | r_shell = np.linalg.norm(shell_precompute_data["nodes"][0]) 26 | 27 | lamb = r_body / r_shell 28 | 29 | f = traj.config_data['bodies'][0]['external_force'][-1] 30 | eta = traj.config_data['params']['eta'] 31 | gamma_theory = 6 * np.pi * r_body * 4 * eta * (1-lamb**5) / (4-9*lamb + 10*lamb**3 - 9*lamb**5 + 4*lamb**6) 32 | v_theory = f / gamma_theory 33 | gamma_compute = f / v[0] 34 | 35 | print(r_body, r_shell, lamb) 36 | print(v[0], v_theory, np.abs(1.0 - v[0]/v_theory)) 37 | print(gamma_compute, gamma_theory, np.abs(1.0 - gamma_compute/gamma_theory)) 38 | 39 | def mu_bar(z, a=r_body, R=r_shell): 40 | return 1 - 9 * a * (R*R / (R*R - z*z)) / 4 / R 41 | 42 | gamma_vs_z = 6 * np.pi * eta * r_body / np.array(list(map(mu_bar, body_z[1:]))) 43 | 44 | plt.plot(body_z[1:], v) 45 | plt.plot(body_z[1:], f / gamma_vs_z) 46 | plt.xlabel('Body z') 47 | plt.ylabel('Body velocity') 48 | plt.legend(['SkellySim', '1 - 9 * a (R^2 / (R^2 - r^2)) / 4 / R']) 49 | plt.title(f'Velocity of body in periphery a={r_body}, R={r_shell}') 50 | #plt.ylim([0, 1.5]) 51 | plt.show() 52 | -------------------------------------------------------------------------------- /examples/stokes_tests/clamped_buckling/README.md: -------------------------------------------------------------------------------- 1 | Test for a single clamped fiber with a constant force per unit length toward the clamping 2 | point. Values of `sigma < 76` should have decaying fiber oscillations, and values of `sigma > 3 | 76` should have growing oscillations. The python script, when run in either subdirectory, will 4 | plot the `x` position of the unclamped end of the fiber. 5 | 6 | Credit: Brato Chakrabarti for the [derivation](hopf-bifurcation.pdf) 7 | 8 | # Sigma = 72 9 | ![Plot of sigma=72](sigma72.png "Sigma = 72") 10 | 11 | # Sigma = 80 12 | ![Plot of sigma=80](sigma80.png "Sigma = 80") 13 | -------------------------------------------------------------------------------- /examples/stokes_tests/clamped_buckling/hopf-bifurcation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/examples/stokes_tests/clamped_buckling/hopf-bifurcation.pdf -------------------------------------------------------------------------------- /examples/stokes_tests/clamped_buckling/plot_deflection.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from skelly_sim.reader import TrajectoryReader 4 | 5 | import matplotlib 6 | matplotlib.use('TKAgg') 7 | import matplotlib.pyplot as plt 8 | 9 | def get_deflection(path: str): 10 | traj = TrajectoryReader(path) 11 | 12 | x = [] 13 | for i in range(len(traj)): 14 | traj.load_frame(i) 15 | x.append(traj['fibers'][0]['x_'][-1, 0]) 16 | 17 | return traj.times, x 18 | 19 | t72, x72 = get_deflection('sigma72/skelly_config.toml') 20 | t80, x80 = get_deflection('sigma80/skelly_config.toml') 21 | 22 | plt.plot(t72, x72, t80, x80) 23 | plt.legend(['\u03c3 = 72', '\u03c3 = 80']) 24 | plt.xlabel('Time (s)') 25 | plt.ylabel('Deflection (micrometers)') 26 | plt.show() 27 | -------------------------------------------------------------------------------- /examples/stokes_tests/clamped_buckling/sigma72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/examples/stokes_tests/clamped_buckling/sigma72.png -------------------------------------------------------------------------------- /examples/stokes_tests/clamped_buckling/sigma72/gen_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import numpy as np 4 | from skelly_sim.skelly_config import Config, Fiber, Point 5 | 6 | np.random.seed(100) 7 | 8 | # create a config object and set the system parameters 9 | config = Config() 10 | config.params.eta = 1.0 11 | config.params.dt_initial = 0.02 12 | config.params.dt_max = 0.02 13 | config.params.dt_write = 0.1 14 | config.params.t_final = 200.0 15 | config.params.pair_evaluator = "CPU" 16 | 17 | sigma = 72. 18 | 19 | length = 1.0 20 | bending_rigidity = 0.0025 21 | force_scale = -sigma * bending_rigidity / length**3 22 | n_nodes = 32 23 | 24 | config.fibers = [ 25 | Fiber( 26 | force_scale=force_scale, 27 | length=length, 28 | n_nodes=n_nodes, 29 | bending_rigidity=bending_rigidity, 30 | minus_clamped=True, 31 | ) 32 | ] 33 | 34 | # give a small kick to start from 35 | config.point_sources = [ 36 | Point( 37 | position=[0.0, 0.0, 10 * length], 38 | force=[10.0, 0.0, 0.0], 39 | time_to_live=1.0, 40 | ) 41 | ] 42 | 43 | # orient in z 44 | config.fibers[0].x = np.linspace([0, 0, 0], [0, 0, length], n_nodes).ravel().tolist() 45 | 46 | # output our config 47 | config.save() 48 | -------------------------------------------------------------------------------- /examples/stokes_tests/clamped_buckling/sigma80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/examples/stokes_tests/clamped_buckling/sigma80.png -------------------------------------------------------------------------------- /examples/stokes_tests/clamped_buckling/sigma80/gen_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import numpy as np 4 | from skelly_sim.skelly_config import Config, Fiber, Point 5 | 6 | # create a config object and set the system parameters 7 | config = Config() 8 | config.params.eta = 1.0 9 | config.params.dt_initial = 0.02 10 | config.params.dt_max = 0.02 11 | config.params.dt_write = 0.1 12 | config.params.t_final = 200.0 13 | config.params.pair_evaluator = "CPU" 14 | 15 | sigma = 80. 16 | 17 | length = 1.0 18 | bending_rigidity = 0.0025 19 | force_scale = -sigma * bending_rigidity / length**3 20 | n_nodes = 32 21 | 22 | config.fibers = [ 23 | Fiber( 24 | force_scale=force_scale, 25 | length=length, 26 | n_nodes=n_nodes, 27 | bending_rigidity=bending_rigidity, 28 | minus_clamped=True, 29 | ) 30 | ] 31 | 32 | # give a small kick to start from 33 | config.point_sources = [ 34 | Point( 35 | position=[0.0, 0.0, 10 * length], 36 | force=[10.0, 0.0, 0.0], 37 | time_to_live=1.0, 38 | ) 39 | ] 40 | 41 | # orient in z 42 | config.fibers[0].x = np.linspace([0, 0, 0], [0, 0, length], n_nodes).ravel().tolist() 43 | 44 | # output our config 45 | config.save() 46 | -------------------------------------------------------------------------------- /examples/stokes_tests/fiber_const_force/calc_velocity.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from skelly_sim.reader import TrajectoryReader 4 | import numpy as np 5 | 6 | traj = TrajectoryReader('skelly_config.toml') 7 | 8 | traj.load_frame(0) 9 | x0 = traj['fibers'][0]['x_'][0, :] 10 | traj.load_frame(-1) 11 | xf = traj['fibers'][0]['x_'][0, :] 12 | 13 | dt = traj.times[-1] - traj.times[0] 14 | v = (xf - x0) / dt 15 | 16 | fib = traj.config_data['fibers'][0] 17 | length = fib['length'] 18 | radius = fib['radius'] 19 | 20 | epsilon = radius / length 21 | 22 | gamma = fib["force_scale"] * length / v[-1] 23 | gamma_theory = -4 * np.pi * length * traj.config_data['params']['eta'] / (np.log(np.exp(1) * epsilon**2)) 24 | 25 | print("theoretical drag: {}".format(gamma_theory)) 26 | print("measured drag: {}".format(gamma)) 27 | print("relative error: {}".format(abs(1 - gamma/gamma_theory))) 28 | 29 | -------------------------------------------------------------------------------- /examples/stokes_tests/fiber_const_force/gen_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import numpy as np 5 | from skelly_sim.skelly_config import Config, Fiber 6 | 7 | config_file = 'skelly_config.toml' 8 | if len(sys.argv) == 1: 9 | print("Using default toml file for output: 'skelly_config.toml'. " 10 | "Provide an alternative filename argument to this script to use that instead.") 11 | elif len(sys.argv) != 2: 12 | print("Supply output config path (blahblah.toml) as sole argument") 13 | sys.exit() 14 | else: 15 | config_file = sys.argv[1] 16 | 17 | np.random.seed(100) 18 | 19 | # create a config object and set the system parameters 20 | config = Config() 21 | config.params.eta = 0.7 22 | config.params.dt_initial = 1E-4 23 | config.params.dt_min = 1E-4 24 | config.params.dt_max = 1E-4 25 | config.params.dt_write = 1E-3 26 | config.params.t_final = 1E-2 27 | config.params.gmres_tol = 1E-10 28 | config.params.seed = 130319 29 | config.params.pair_evaluator = "CPU" 30 | 31 | length = 0.75 32 | config.fibers = [Fiber( 33 | force_scale=0.31, 34 | length=length, 35 | n_nodes=8, 36 | bending_rigidity=0.0025 37 | )] 38 | config.fibers[0].fill_node_positions(np.array([0.0, 0.0, 0.0]), np.array([0.0, 0.0, 1.0])) 39 | 40 | # output our config 41 | config.save(config_file) 42 | -------------------------------------------------------------------------------- /examples/stokes_tests/fiber_const_force_in_sphere/README.md: -------------------------------------------------------------------------------- 1 | Not the most useful test, but interesting. Drag/velocity of a fiber with constant force/length 2 | inside of a spherical periphery should be independent of position, which SkellySim supports. 3 | 4 | ``` 5 | % ./calc_velocity.py 6 | theoretical drag: 0.9177395848298898 7 | measured drag: 0.9177395848290841 8 | relative error: 8.778533455711113e-13 9 | ``` 10 | -------------------------------------------------------------------------------- /examples/stokes_tests/fiber_const_force_in_sphere/calc_velocity.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from skelly_sim.reader import TrajectoryReader 4 | import numpy as np 5 | 6 | traj = TrajectoryReader('skelly_config.toml') 7 | 8 | traj.load_frame(0) 9 | x0 = traj['fibers'][0]['x_'][0, :] 10 | traj.load_frame(-1) 11 | xf = traj['fibers'][0]['x_'][0, :] 12 | 13 | dt = traj.times[-1] - traj.times[0] 14 | v = (xf - x0) / dt 15 | 16 | fib = traj.config_data['fibers'][0] 17 | length = fib['length'] 18 | radius = fib['radius'] 19 | 20 | epsilon = radius / length 21 | 22 | gamma = fib["force_scale"] * length / v[-1] 23 | gamma_theory = -4 * np.pi * length * traj.config_data['params']['eta'] / (np.log(np.exp(1) * epsilon**2)) 24 | 25 | print("theoretical drag: {}".format(gamma_theory)) 26 | print("measured drag: {}".format(gamma)) 27 | print("relative error: {}".format(abs(1 - gamma/gamma_theory))) 28 | 29 | -------------------------------------------------------------------------------- /examples/stokes_tests/fiber_const_force_in_sphere/gen_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import numpy as np 5 | from skelly_sim.skelly_config import ConfigSpherical, Fiber 6 | 7 | config_file = 'skelly_config.toml' 8 | if len(sys.argv) == 1: 9 | print("Using default toml file for output: 'skelly_config.toml'. " 10 | "Provide an alternative filename argument to this script to use that instead.") 11 | elif len(sys.argv) != 2: 12 | print("Supply output config path (blahblah.toml) as sole argument") 13 | sys.exit() 14 | else: 15 | config_file = sys.argv[1] 16 | 17 | np.random.seed(100) 18 | 19 | # create a config object and set the system parameters 20 | config = ConfigSpherical() 21 | config.params.eta = 1.0 22 | config.params.dt_initial = 1E-2 23 | config.params.dt_write = 1E-1 24 | config.params.t_final = 1.0 25 | config.params.gmres_tol = 1E-10 26 | config.params.seed = 130319 27 | config.params.adaptive_timestep_flag = False 28 | 29 | length = 0.75 30 | config.fibers = [Fiber( 31 | force_scale=0.31, 32 | length=length, 33 | n_nodes=8, 34 | bending_rigidity=0.0025 35 | )] 36 | config.fibers[0].fill_node_positions(np.array([1.0, 0.0, 0.0]), np.array([0.0, 0.0, 1.0])) 37 | 38 | config.periphery.n_nodes = 4000 39 | config.periphery.radius = 4.0 40 | 41 | # output our config 42 | config.save(config_file) 43 | -------------------------------------------------------------------------------- /examples/stokes_tests/ps_const_force_in_sphere/README.md: -------------------------------------------------------------------------------- 1 | Point source with constant force inside of a sphere. Measure velocity field and compare 2 | against known codes. 3 | -------------------------------------------------------------------------------- /examples/stokes_tests/ps_const_force_in_sphere/analysis_example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import numpy as np 4 | from skelly_sim.reader import Listener, Request 5 | import matplotlib as mpl 6 | import matplotlib.pyplot as plt 7 | mpl.use('TKAgg') 8 | 9 | # Fire up SkellySim in "listener" mode 10 | listener = Listener(binary='skelly_sim_release') 11 | 12 | # All analysis requests are done via a "Request" object 13 | req = Request() 14 | 15 | # specify frame number to evaluate and evaluator (CPU, GPU, FMM) 16 | req.frame_no = 0 17 | req.evaluator = "CPU" 18 | 19 | # Request velocity field in z=0 plane 20 | tmp = np.linspace(-2, 2, 1000) 21 | xm, ym, zm = np.meshgrid(tmp, tmp, 0.0) 22 | req.velocity_field.x = np.array((xm.ravel(), ym.ravel(), zm.ravel())).T 23 | 24 | # Make our request to SkellySim! Might take a second... 25 | res = listener.request(req) 26 | 27 | # Streamplot of x,y components of velocity field in z=0 plane 28 | ax1 = plt.subplot(1, 1, 1) 29 | xm, ym = xm.squeeze(axis=2), ym.squeeze(axis=2) 30 | U = res['velocity_field'][:, 0].reshape(*xm.shape) 31 | V = res['velocity_field'][:, 1].reshape(*ym.shape) 32 | ax1.streamplot(xm, ym, U, V) 33 | 34 | # IT'S GO TIME 35 | plt.show() 36 | -------------------------------------------------------------------------------- /examples/stokes_tests/ps_const_force_in_sphere/gen_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import numpy as np 5 | from skelly_sim.skelly_config import ConfigSpherical, Config, Point, Body 6 | 7 | config_file = 'skelly_config.toml' 8 | 9 | np.random.seed(100) 10 | 11 | # create a config object and set the system parameters 12 | # config = ConfigSpherical() 13 | config = ConfigSpherical() 14 | config.params.eta = 0.7 15 | config.params.dt_initial = 1E-1 16 | config.params.dt_min = 1E-4 17 | config.params.dt_max = 1E-1 18 | config.params.dt_write = 1E-1 19 | config.params.t_final = 0.2 20 | config.params.gmres_tol = 1E-10 21 | config.params.seed = 130319 22 | 23 | config.periphery.n_nodes = 6000 24 | config.periphery.radius = np.pi / 3 / 1.04 25 | 26 | config.point_sources = [ 27 | Point(position=((np.random.uniform(size=3) - 0.5)*0.6).tolist(), 28 | force=(np.random.uniform(size=3) - 0.5).tolist(), 29 | ) 30 | ] 31 | 32 | # output our config 33 | config.save(config_file) 34 | -------------------------------------------------------------------------------- /examples/stokes_tests/ps_const_torque_in_sphere/README.md: -------------------------------------------------------------------------------- 1 | Point source with constant torque inside of a sphere. Measure velocity field and compare 2 | against known codes. 3 | -------------------------------------------------------------------------------- /examples/stokes_tests/ps_const_torque_in_sphere/analysis_example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import numpy as np 4 | from skelly_sim.reader import Listener, Request 5 | import matplotlib as mpl 6 | import matplotlib.pyplot as plt 7 | 8 | # Fire up SkellySim in "listener" mode 9 | listener = Listener(binary='skelly_sim_release') 10 | 11 | # All analysis requests are done via a "Request" object 12 | req = Request() 13 | 14 | # specify frame number to evaluate and evaluator (CPU, GPU, FMM) 15 | req.frame_no = 0 16 | req.evaluator = "CPU" 17 | 18 | # Request velocity field in z=0 plane 19 | tmp = np.linspace(-2, 2, 1000) 20 | xm, ym, zm = np.meshgrid(tmp, tmp, 0.0) 21 | req.velocity_field.x = np.array((xm.ravel(), ym.ravel(), zm.ravel())).T 22 | 23 | # Make our request to SkellySim! Might take a second... 24 | res = listener.request(req) 25 | 26 | # Streamplot of x,y components of velocity field in z=0 plane 27 | ax1 = plt.subplot(1, 1, 1) 28 | xm, ym = xm.squeeze(axis=2), ym.squeeze(axis=2) 29 | U = res['velocity_field'][:, 0].reshape(*xm.shape) 30 | V = res['velocity_field'][:, 1].reshape(*ym.shape) 31 | ax1.streamplot(xm, ym, U, V) 32 | 33 | # IT'S GO TIME 34 | plt.show() 35 | -------------------------------------------------------------------------------- /examples/stokes_tests/ps_const_torque_in_sphere/gen_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import numpy as np 4 | from skelly_sim.skelly_config import ConfigSpherical, Config, Point, Body 5 | 6 | config_file = 'skelly_config.toml' 7 | np.random.seed(100) 8 | 9 | # create a config object and set the system parameters 10 | # config = ConfigSpherical() 11 | config = ConfigSpherical() 12 | config.params.eta = 0.7 13 | config.params.dt_initial = 1E-1 14 | config.params.dt_min = 1E-4 15 | config.params.dt_max = 1E-1 16 | config.params.dt_write = 1E-1 17 | config.params.t_final = 0.2 18 | config.params.gmres_tol = 1E-10 19 | config.params.seed = 130319 20 | 21 | config.periphery.n_nodes = 4000 22 | config.periphery.radius = np.pi / 3 / 1.04 23 | 24 | config.point_sources = [ 25 | Point(position=((np.random.uniform(size=3) - 0.5)*0.6).tolist(), 26 | torque=(np.random.uniform(size=3) - 0.5).tolist(), 27 | ) 28 | ] 29 | 30 | # output our config 31 | config.save(config_file) 32 | -------------------------------------------------------------------------------- /include/background_source.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BACKGROUND_SOURCE_HPP 2 | #define BACKGROUND_SOURCE_HPP 3 | 4 | #include 5 | 6 | class BackgroundSource { 7 | public: 8 | BackgroundSource() = default; 9 | BackgroundSource(const toml::value &background_table); 10 | Eigen::MatrixXd flow(CMatrixRef &r_trg, double eta); 11 | bool is_active() { return uniform_.norm() + scale_factor_.norm(); } 12 | 13 | private: 14 | Eigen::Vector3i components_ = {0, 1, 2}; 15 | Eigen::Vector3d scale_factor_ = {0.0, 0.0, 0.0}; 16 | Eigen::Vector3d uniform_ = {0.0, 0.0, 0.0}; 17 | }; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /include/body.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BODY_HPP 2 | #define BODY_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | class Periphery; 11 | class SphericalBody; 12 | class DeformableBody; 13 | class EllipsoidalBody; 14 | class FiberContainerFiniteDifference; 15 | 16 | /// Class for "small" bodies such as MTOCs 17 | class Body { 18 | public: 19 | enum EXTFORCE { Linear, Oscillatory }; ///< Type of external force [Linear, Oscillatory] 20 | static const std::string EXTFORCE_name[]; ///< String name of external force 21 | int n_nodes_; ///< Number of nodes representing the body surface 22 | 23 | Eigen::VectorXd RHS_; ///< Current 'right-hand-side' for matrix formulation of solver 24 | Eigen::MatrixXd node_positions_; ///< [ 3 x n_nodes ] node positions in lab frame 25 | Eigen::MatrixXd node_positions_ref_; ///< [ 3 x n_nodes ] node positions in reference 'body' frame 26 | Eigen::MatrixXd node_normals_; ///< [ 3 x n_nodes ] node normals in lab frame 27 | Eigen::MatrixXd node_normals_ref_; ///< [ 3 x n_nodes ] node normals in reference 'body' frame 28 | Eigen::VectorXd node_weights_; ///< [ n_nodes ] far field quadrature weights for nodes 29 | Eigen::VectorXd solution_vec_; ///< [ 3 * n_nodes + ] strength of interaction on nodes 30 | 31 | /// [ 3 x n_nucleation_sites ] nucleation site positions in reference 'body' frame 32 | Eigen::MatrixXd nucleation_sites_ref_; 33 | /// [ 3 x n_nucleation_sites ] nucleation site positions in lab frame 34 | Eigen::MatrixXd nucleation_sites_; 35 | 36 | Body(const toml::value &body_table, const Params ¶ms); 37 | Body() = default; ///< default constructor... 38 | 39 | virtual void update_RHS(CMatrixRef &v_on_body) = 0; 40 | virtual void update_cache_variables(double eta) = 0; 41 | virtual void update_preconditioner(double eta) = 0; 42 | virtual void load_precompute_data(const std::string &input_file) = 0; 43 | virtual void step(double dt, CVectorRef &body_solution) = 0; 44 | 45 | virtual int get_solution_size() const = 0; 46 | virtual Eigen::Vector3d get_position() const = 0; 47 | virtual Eigen::VectorXd matvec(CMatrixRef &v_bodies, CVectorRef &body_solution) const = 0; 48 | virtual Eigen::VectorXd apply_preconditioner(CVectorRef &x) const = 0; 49 | 50 | /// @brief Make a copy of this instance 51 | virtual std::shared_ptr clone() const = 0; 52 | 53 | /// @brief dummy method to be overriden by derived classes 54 | virtual bool check_collision(const Periphery &periphery, double threshold) const = 0; 55 | 56 | /// @brief dummy method to be overriden by derived classes 57 | virtual bool check_collision(const Body &body, double threshold) const = 0; 58 | 59 | /// @brief dummy method to be overriden by derived classes 60 | virtual bool check_collision(const SphericalBody &body, double threshold) const = 0; 61 | 62 | /// @brief dummy method to be overriden by derived classes 63 | virtual bool check_collision(const DeformableBody &body, double threshold) const = 0; 64 | 65 | /// @brief dummy method to be overriden by derived classes 66 | virtual bool check_collision(const EllipsoidalBody &body, double threshold) const = 0; 67 | 68 | /// For structures with fixed size Eigen::Vector types, this ensures alignment if the 69 | /// structure is allocated via `new` 70 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 71 | }; 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /include/body_deformable.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BODY_DEFORMABLE_HPP 2 | #define BODY_DEFORMABLE_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class Periphery; 12 | class SphericalBody; 13 | class EllipsoidalBody; 14 | class FiberContainerFiniteDifference; 15 | 16 | /// @brief Spherical Body... 17 | class DeformableBody : public Body { 18 | public: 19 | /// @brief Construct deformable body. @see Body 20 | /// @param[in] body_table Parsed TOML body table 21 | /// @param[in] params Initialized Params object 22 | DeformableBody(const toml::value &body_table, const Params ¶ms) : Body(body_table, params){}; 23 | DeformableBody() = default; 24 | 25 | /// Duplicate SphericalBody object 26 | std::shared_ptr clone() const override { return std::make_shared(*this); }; 27 | 28 | void min_copy(const std::shared_ptr &other); 29 | 30 | void update_RHS(CMatrixRef &v_on_body) override; 31 | void update_cache_variables(double eta) override; 32 | void update_preconditioner(double eta) override; 33 | void load_precompute_data(const std::string &input_file) override; 34 | void step(double dt, CVectorRef &body_solution) override; 35 | int get_solution_size() const override { return n_nodes_ * 4; }; 36 | Eigen::VectorXd matvec(CMatrixRef &v_bodies, CVectorRef &body_solution) const override; 37 | Eigen::VectorXd apply_preconditioner(CVectorRef &x) const override; 38 | Eigen::Vector3d get_position() const override; 39 | 40 | bool check_collision(const Periphery &periphery, double threshold) const override; 41 | bool check_collision(const Body &body, double threshold) const override; 42 | bool check_collision(const SphericalBody &body, double threshold) const override; 43 | bool check_collision(const DeformableBody &body, double threshold) const override; 44 | bool check_collision(const EllipsoidalBody &body, double threshold) const override; 45 | 46 | MSGPACK_DEFINE_MAP(node_positions_, node_normals_, solution_vec_); 47 | }; 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /include/eigen_matrix_plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef EIGEN_MATRIX_PLUGIN_H 2 | #define EIGEN_MATRIX_PLUGIN_H 3 | 4 | inline void msgpack_unpack(msgpack::object o) { 5 | if(o.type != msgpack::type::ARRAY) { throw msgpack::type_error(); } 6 | 7 | msgpack::object * p = o.via.array.ptr; 8 | 9 | std::string type; 10 | *p >> type; 11 | if (type != "__eigen__") { throw msgpack::type_error(); } 12 | 13 | size_t rows; 14 | size_t cols; 15 | 16 | ++p; 17 | *p >> rows; 18 | ++p; 19 | *p >> cols; 20 | this->resize(rows, cols); 21 | 22 | for (int i = 0; i < this->cols(); ++i) { 23 | for (int j = 0; j < this->rows(); ++j) { 24 | ++p; 25 | *p >> this->operator()(j, i); 26 | } 27 | } 28 | } 29 | 30 | template 31 | inline void msgpack_pack(Packer& pk) const { 32 | pk.pack_array(3 + this->rows()*this->cols()); 33 | pk.pack(std::string("__eigen__")); 34 | pk.pack(this->rows()); 35 | pk.pack(this->cols()); 36 | 37 | for (int i = 0; i < this->cols(); ++i) { 38 | for (int j = 0; j < this->rows(); ++j) { 39 | pk.pack(this->operator()(j, i)); 40 | } 41 | } 42 | } 43 | 44 | template 45 | inline void msgpack_object(MSGPACK_OBJECT* o, msgpack::zone* z) const { } 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /include/eigen_quaternion_plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef EIGEN_QUATERNION_PLUGIN_H 2 | #define EIGEN_QUATERNION_PLUGIN_H 3 | 4 | inline void msgpack_unpack(msgpack::object o) { 5 | if (o.type != msgpack::type::ARRAY) { 6 | throw msgpack::type_error(); 7 | } 8 | 9 | msgpack::object *p = o.via.array.ptr; 10 | 11 | std::string type; 12 | *p >> type; 13 | if (type != "__quat__") { 14 | throw msgpack::type_error(); 15 | } 16 | 17 | ++p; 18 | *p >> this->w(); 19 | ++p; 20 | *p >> this->x(); 21 | ++p; 22 | *p >> this->y(); 23 | ++p; 24 | *p >> this->z(); 25 | } 26 | 27 | template 28 | inline void msgpack_pack(Packer &pk) const { 29 | pk.pack_array(5); 30 | pk.pack(std::string("__quat__")); 31 | 32 | pk.pack(this->w()); 33 | pk.pack(this->x()); 34 | pk.pack(this->y()); 35 | pk.pack(this->z()); 36 | } 37 | 38 | template 39 | inline void msgpack_object(MSGPACK_OBJECT *o, msgpack::zone *z) const {} 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /include/io_maps.hpp: -------------------------------------------------------------------------------- 1 | #ifndef IO_MAPS_HPP 2 | #define IO_MAPS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | /// @brief Structure for importing frame of trajectory into the simulation 14 | /// 15 | /// We can't use output_map_t here, but rather a similar struct which uses copies of the member 16 | /// variables (rather than references) which are then used to update the System variables. 17 | typedef struct input_map_t { 18 | double time; ///< System::properties 19 | double dt; ///< System::properties 20 | std::unique_ptr fibers; ///< System::fc_ 21 | BodyContainer bodies; ///< System::bc_ 22 | Periphery shell; ///< System::shell_ 23 | std::vector> rng_state; ///< string representation of split/unsplit state in RNG 24 | MSGPACK_DEFINE_MAP(time, dt, rng_state, fibers, bodies, shell); ///< Helper routine to specify serialization 25 | } input_map_t; 26 | 27 | /// @brief Structure for trajectory output via msgpack 28 | /// 29 | /// This can be extended easily, so long as you update the corresponding input_map_t and potentially the System::write() 30 | /// function if you can't use a reference in the output_map for some reason. 31 | typedef struct output_map_t { 32 | double &time; ///< System::properties 33 | double &dt; ///< System::properties 34 | std::unique_ptr &fibers; ///< System::fc_ 35 | BodyContainer &bodies; ///< System::bc_ 36 | Periphery &shell; ///< System::shell_ 37 | std::vector> rng_state; ///< string representation of split/unsplit state in RNG 38 | MSGPACK_DEFINE_MAP(time, dt, rng_state, fibers, bodies, shell); ///< Helper routine to specify serialization 39 | } output_map_t; 40 | 41 | /// @brief Structure for trajectory header information via msgpack 42 | /// 43 | /// Contains all of the header information for the simulation 44 | typedef struct header_map_t { 45 | // Make sure that the trajectory version always comes first!!!!! 46 | int trajversion; ///< SKELLYSIM_TRAJECTORY_VERSION 47 | int number_mpi_ranks; ///< System::rank_ 48 | int fiber_type; ///< System::fc_->fiber_type_ 49 | std::string skellysim_version; ///< SKELLYSIM_VERSION 50 | std::string skellysim_commit; ///< SKELLYSIM_COMMIT 51 | std::string simdate; ///< Date of simulation from chrono and ctime 52 | std::string hostname; ///< Hostnames of the simulation 53 | MSGPACK_DEFINE_MAP(trajversion, number_mpi_ranks, fiber_type, skellysim_version, skellysim_commit, simdate, 54 | hostname); 55 | } header_map_t; 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /include/listener.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LISTENER_HPP 2 | #define LISTENER_HPP 3 | 4 | namespace listener { 5 | void run(); 6 | } 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /include/params.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PARAMS_HPP 2 | #define PARAMS_HPP 3 | 4 | #include 5 | 6 | /// Class containing input parameters for the simulated system 7 | class Params { 8 | public: 9 | double eta; 10 | double dt_initial; 11 | double beta_up; 12 | double beta_down; 13 | double gmres_tol; 14 | double t_final; 15 | double fiber_error_tol; 16 | double dt_min; 17 | double dt_max; 18 | double dt_write; 19 | double implicit_motor_activation_delay; 20 | bool periphery_interaction_flag; 21 | bool adaptive_timestep_flag; 22 | std::string pair_evaluator; 23 | std::string fiber_type; 24 | 25 | periphery_binding_t periphery_binding{ 26 | .polar_angle_start = 0.0, 27 | .polar_angle_end = M_PI, 28 | .threshold = 0.75, 29 | }; 30 | 31 | struct { 32 | int n_nodes = 0; 33 | double v_growth; 34 | double f_catastrophe; 35 | double v_grow_collision_scale; 36 | double f_catastrophe_collision_scale; 37 | double nucleation_rate; 38 | double min_length; 39 | double radius; 40 | double bending_rigidity; 41 | double min_separation; 42 | } dynamic_instability; 43 | unsigned long seed; 44 | 45 | fiber_periphery_interaction_t fiber_periphery_interaction{ 46 | .f_0 = 20.0, 47 | .l_0 = 0.05, 48 | }; 49 | 50 | struct { 51 | int body_stresslet_multipole_order = 8; 52 | int body_stresslet_max_points = 2000; 53 | int body_oseen_multipole_order = 8; 54 | int body_oseen_max_points = 2000; 55 | int fiber_stokeslet_multipole_order = 8; 56 | int fiber_stokeslet_max_points = 2000; 57 | int periphery_stresslet_multipole_order = 8; 58 | int periphery_stresslet_max_points = 2000; 59 | } stkfmm; 60 | 61 | std::string shell_precompute_file; 62 | 63 | Params() = default; 64 | Params(toml::value ¶m_table); 65 | 66 | void print(); 67 | }; 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /include/parse_util.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PARSE_UTIL_HPP 2 | #define PARSE_UTIL_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace parse_util { 10 | 11 | template 12 | inline T convert_array(const toml::array &src) { 13 | if constexpr (std::is_same_v) { 14 | double tmp[4]; 15 | for (size_t i = 0; i < src.size(); ++i) 16 | tmp[i] = src.at(i).as_floating(); 17 | 18 | return Eigen::Quaterniond(tmp); 19 | } 20 | else if constexpr (std::is_same_v) { 21 | T trg; 22 | for (size_t i = 0; i < 3; ++i) 23 | trg[i] = src.at(i).as_integer(); 24 | 25 | return trg; 26 | } 27 | else { 28 | T trg(src.size()); 29 | for (size_t i = 0; i < src.size(); ++i) 30 | trg[i] = src.at(i).as_floating(); 31 | 32 | return trg; 33 | } 34 | } 35 | 36 | } // namespace parse_util 37 | #endif 38 | -------------------------------------------------------------------------------- /include/point_source.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POINT_SOURCE_HPP 2 | #define POINT_SOURCE_HPP 3 | 4 | #include 5 | #include 6 | 7 | class PointSource { 8 | public: 9 | double time_to_live_ = 0.0; 10 | Eigen::Vector3d position_ = {0.0, 0.0, 0.0}; 11 | Eigen::Vector3d force_ = {0.0, 0.0, 0.0}; 12 | Eigen::Vector3d torque_ = {0.0, 0.0, 0.0}; 13 | 14 | PointSource(const toml::value &point_table); 15 | }; 16 | 17 | class PointSourceContainer { 18 | public: 19 | PointSourceContainer() = default; 20 | PointSourceContainer(const toml::array &point_tables); 21 | 22 | Eigen::MatrixXd flow(CMatrixRef &r_trg, double eta, double time); 23 | std::vector points; 24 | }; 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /include/rng.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RNG_HPP 2 | #define RNG_HPP 3 | 4 | #include 5 | 6 | namespace RNG { 7 | void init(unsigned long seed); 8 | void init(std::pair); 9 | 10 | std::pair dump_state(); 11 | 12 | double uniform(double low = 0.0, double high = 1.0); 13 | int uniform_int(int low, int high); 14 | double normal(double mu = 0.0, double sigma = 1.0); 15 | int poisson_int(double mu); 16 | 17 | double uniform_unsplit(double low = 0.0, double high = 1.0); 18 | int uniform_int_unsplit(int low, int high); 19 | double normal_unsplit(double mu = 0.0, double sigma = 1.0); 20 | int poisson_int_unsplit(double mu); 21 | } // namespace RNG 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /include/serialization.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERIALIZATION_HPP_ 2 | #define SERIALIZATION_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | /// Serialization and deserialization routines for msgpack 11 | /// 12 | /// FiberContainerBase NOT implemented (screws up everything) 13 | /// FiberContainerFiniteDifference implemented 14 | 15 | /// @brief Custom serializaiton and deserialization of the unique_ptr for FiberContainerBase 16 | namespace msgpack { 17 | 18 | MSGPACK_API_VERSION_NAMESPACE(MSGPACK_DEFAULT_API_NS) { 19 | 20 | namespace adaptor { 21 | 22 | template <> 23 | struct convert> { 24 | msgpack::object const &operator()(msgpack::object const &o, std::unique_ptr &v) const { 25 | // Sanity check on the contents of the unique pointer 26 | if (o.type != msgpack::type::ARRAY) { 27 | throw msgpack::type_error(); 28 | } 29 | 30 | FiberContainerBase::FIBERTYPE fiber_type; 31 | o.via.array.ptr[0].convert(fiber_type); 32 | 33 | switch (fiber_type) { 34 | case FiberContainerBase::FIBERTYPE::FiniteDifference: { 35 | FiberContainerFiniteDifference fc_finitediff; 36 | o.convert(fc_finitediff); 37 | v = std::make_unique(std::move(fc_finitediff)); 38 | break; 39 | } 40 | default: 41 | throw msgpack::type_error(); 42 | } // switch 43 | return o; 44 | } 45 | }; 46 | 47 | template <> 48 | struct pack> { 49 | template 50 | msgpack::packer &operator()(msgpack::packer &o, 51 | std::unique_ptr const &v) const { 52 | if (v->fiber_type_ == FiberContainerBase::FIBERTYPE::FiniteDifference) { 53 | o.pack(*static_cast(v.get())); 54 | } 55 | 56 | return o; 57 | } 58 | }; 59 | 60 | } // namespace adaptor 61 | } // MSGPACK_API_VERSION_NAMESPACE(MSGPACK_DEFAULT_API_NS) 62 | 63 | } // namespace msgpack 64 | 65 | #endif 66 | 67 | -------------------------------------------------------------------------------- /include/skelly_sim.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SKELLYSIM_HPP 2 | #define SKELLYSIM_HPP 3 | 4 | #ifndef SKELLYSIM_VERSION 5 | #define SKELLYSIM_VERSION "" 6 | #endif 7 | 8 | #ifndef SKELLYSIM_COMMIT 9 | #define SKELLYSIM_COMMIT "" 10 | #endif 11 | 12 | #include 13 | 14 | #include 15 | 16 | #include 17 | #define EIGEN_MATRIX_PLUGIN "eigen_matrix_plugin.h" 18 | #define EIGEN_QUATERNION_PLUGIN "eigen_quaternion_plugin.h" 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | typedef Eigen::Map VectorMap; 25 | typedef Eigen::Map CVectorMap; 26 | typedef Eigen::Map ArrayMap; 27 | typedef Eigen::Map CArrayMap; 28 | typedef Eigen::Map MatrixMap; 29 | typedef Eigen::Map CMatrixMap; 30 | typedef Eigen::Ref ArrayRef; 31 | typedef const Eigen::Ref CArrayRef; 32 | typedef Eigen::Ref VectorRef; 33 | typedef const Eigen::Ref CVectorRef; 34 | typedef Eigen::Ref MatrixRef; 35 | typedef const Eigen::Ref CMatrixRef; 36 | 37 | /// Struct of parameters for exponentially decaying fiber-periphery interaction 38 | typedef struct { 39 | double f_0; ///< strength of interaction 40 | double l_0; ///< characteristic length of interaction 41 | } fiber_periphery_interaction_t; 42 | 43 | typedef struct { 44 | bool active; ///< If periphery binding enabled 45 | double polar_angle_start; ///< Angle from z axis where binding starts 46 | double polar_angle_end; ///< Angle from z axis where binding ends 47 | double threshold; ///< Minimum distance from cortex where binding will begin 48 | } periphery_binding_t; 49 | 50 | template 51 | struct ActiveIterator { 52 | using iterator_category = std::forward_iterator_tag; 53 | using difference_type = std::ptrdiff_t; 54 | using value_type = T; 55 | using pointer = value_type *; 56 | using reference = value_type &; 57 | 58 | ActiveIterator(int index, std::vector &objs) : m_index(index), container_ref(objs) { 59 | while (m_index < container_ref.size() && !container_ref[m_index].active()) { 60 | m_index++; 61 | } 62 | } 63 | 64 | reference operator*() const { return container_ref[m_index]; } 65 | pointer operator->() { return &container_ref[m_index]; } 66 | 67 | // Prefix increment 68 | ActiveIterator &operator++() { 69 | do { 70 | m_index++; 71 | } while (m_index < container_ref.size() && !container_ref[m_index].active()); 72 | 73 | return *this; 74 | } 75 | 76 | // Postfix increment 77 | ActiveIterator operator++(int) { 78 | ActiveIterator tmp = *this; 79 | ++(*this); 80 | return tmp; 81 | } 82 | 83 | friend bool operator==(const ActiveIterator &a, const ActiveIterator &b) { return a.m_index == b.m_index; }; 84 | friend bool operator!=(const ActiveIterator &a, const ActiveIterator &b) { return a.m_index != b.m_index; }; 85 | 86 | private: 87 | int m_index; 88 | std::vector &container_ref; 89 | }; 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /include/solver.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SOLVER_HPP 2 | #define SOLVER_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | template 11 | class Solver { 12 | public: 13 | typedef typename matvec_T::scalar_type ST; 14 | typedef Tpetra::Vector 16 | SV; 17 | typedef typename matvec_T::MV MV; 18 | typedef Tpetra::Operator OP; 19 | 20 | Solver() { 21 | comm_ = Tpetra::getDefaultComm(); 22 | matvec_ = rcp(new matvec_T(comm_)); 23 | preconditioner_ = rcp(new precond_T(comm_)); 24 | map_ = matvec_->getDomainMap(); 25 | X_ = rcp(new SV(map_)); 26 | RHS_ = rcp(new SV(map_)); 27 | }; 28 | void set_RHS(); 29 | bool solve(); 30 | void apply_preconditioner(); 31 | CVectorMap get_solution() { return CVectorMap(X_->getData(0).getRawPtr(), X_->getLocalLength()); }; 32 | double get_residual() { 33 | Teuchos::RCP Y(new SV(map_)); 34 | matvec_->apply(*X_, *Y); 35 | CVectorMap RHS_map(RHS_->getData(0).getRawPtr(), RHS_->getLocalLength()); 36 | CVectorMap Y_map(Y->getData(0).getRawPtr(), Y->getLocalLength()); 37 | double residual = (RHS_map - Y_map).squaredNorm(); 38 | MPI_Allreduce(MPI_IN_PLACE, &residual, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD); 39 | return sqrt(residual); 40 | } 41 | 42 | private: 43 | Teuchos::RCP> comm_; 44 | Teuchos::RCP preconditioner_; 45 | Teuchos::RCP matvec_; 46 | Teuchos::RCP X_; 47 | Teuchos::RCP RHS_; 48 | Teuchos::RCP> map_; 49 | }; 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /include/solver_hydro.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class P_inv_hydro : public Tpetra::Operator<> { 11 | public: 12 | // Tpetra::Operator subclasses should always define these four typedefs. 13 | typedef Tpetra::Operator<>::scalar_type scalar_type; 14 | typedef Tpetra::Operator<>::local_ordinal_type local_ordinal_type; 15 | typedef Tpetra::Operator<>::global_ordinal_type global_ordinal_type; 16 | typedef Tpetra::Operator<>::node_type node_type; 17 | // The type of the input and output arguments of apply(). 18 | typedef Tpetra::MultiVector MV; 19 | // The Map specialization used by this class. 20 | typedef Tpetra::Map map_type; 21 | 22 | private: 23 | // This is an implementation detail; users don't need to see it. 24 | typedef Tpetra::Import import_type; 25 | 26 | public: 27 | // comm: The communicator over which to distribute those rows and columns. 28 | P_inv_hydro(const Teuchos::RCP> comm); 29 | virtual ~P_inv_hydro() {} 30 | 31 | Teuchos::RCP getDomainMap() const { return opMap_; }; 32 | Teuchos::RCP getRangeMap() const { return opMap_; }; 33 | 34 | void apply(const MV &X, MV &Y, Teuchos::ETransp mode = Teuchos::NO_TRANS, 35 | scalar_type alpha = Teuchos::ScalarTraits::one(), 36 | scalar_type beta = Teuchos::ScalarTraits::zero()) const; 37 | 38 | private: 39 | Teuchos::RCP opMap_; 40 | Teuchos::RCP> comm_; 41 | const int rank_; 42 | }; 43 | 44 | class A_fiber_hydro : public Tpetra::Operator<> { 45 | public: 46 | // Tpetra::Operator subclasses should always define these four typedefs. 47 | typedef Tpetra::Operator<>::scalar_type scalar_type; 48 | typedef Tpetra::Operator<>::local_ordinal_type local_ordinal_type; 49 | typedef Tpetra::Operator<>::global_ordinal_type global_ordinal_type; 50 | typedef Tpetra::Operator<>::node_type node_type; 51 | // The type of the input and output arguments of apply(). 52 | typedef Tpetra::MultiVector MV; 53 | // The Map specialization used by this class. 54 | typedef Tpetra::Map map_type; 55 | 56 | private: 57 | // This is an implementation detail; users don't need to see it. 58 | typedef Tpetra::Import import_type; 59 | 60 | public: 61 | // comm: The communicator over which to distribute those rows and columns. 62 | A_fiber_hydro(const Teuchos::RCP> comm); 63 | virtual ~A_fiber_hydro() {} 64 | Teuchos::RCP getDomainMap() const { return opMap_; }; 65 | Teuchos::RCP getRangeMap() const { return opMap_; }; 66 | 67 | void apply(const MV &X, MV &Y, Teuchos::ETransp mode = Teuchos::NO_TRANS, 68 | scalar_type alpha = Teuchos::ScalarTraits::one(), 69 | scalar_type beta = Teuchos::ScalarTraits::zero()) const; 70 | 71 | private: 72 | Teuchos::RCP opMap_; 73 | Teuchos::RCP> comm_; 74 | const int rank_; 75 | }; 76 | -------------------------------------------------------------------------------- /include/streamline.hpp: -------------------------------------------------------------------------------- 1 | #ifndef STREAMLINE_HPP 2 | #define STREAMLINE_HPP 3 | 4 | #include 5 | 6 | #include 7 | 8 | /// @brief Class representing a streamline 9 | class StreamLine { 10 | public: 11 | Eigen::MatrixXd x; ///< Coordinates of streamline 12 | Eigen::MatrixXd val; ///< Velocities of streamline 13 | Eigen::VectorXd time; ///< Evaluation times 14 | virtual void compute(); ///< Compute streamline 15 | 16 | StreamLine() = default; 17 | 18 | StreamLine(CVectorRef &x0_, double dt_init_, double t_final_, double abs_err_, double rel_err_, 19 | bool back_integrate_) 20 | : x(x0_), dt_init(dt_init_), t_final(t_final_), abs_err(abs_err_), rel_err(rel_err_), 21 | back_integrate(back_integrate_) { 22 | compute(); 23 | }; 24 | 25 | void write(std::ofstream &ofs) { 26 | msgpack::pack(ofs, *this); 27 | ofs.flush(); 28 | }; 29 | MSGPACK_DEFINE_MAP(x, val, time); 30 | 31 | protected: 32 | double dt_init; 33 | double t_final; 34 | double abs_err; 35 | double rel_err; 36 | bool back_integrate; 37 | }; 38 | 39 | class VortexLine : public StreamLine { 40 | public: 41 | VortexLine() = default; 42 | 43 | VortexLine(CVectorRef &x0_, double dt_init_, double t_final_, double abs_err_, double rel_err_, 44 | bool back_integrate_) { 45 | x = x0_; 46 | dt_init = dt_init_; 47 | t_final = t_final_; 48 | abs_err = abs_err_; 49 | rel_err = rel_err_; 50 | back_integrate = back_integrate_; 51 | compute(); 52 | }; 53 | 54 | virtual void compute() override; ///< Compute VortexLine 55 | MSGPACK_DEFINE_MAP(x, val, time); 56 | }; 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /include/system.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SYSTEM_HPP 2 | #define SYSTEM_HPP 3 | 4 | #include 5 | #include 6 | 7 | class Params; 8 | class BodyContainer; 9 | class FiberContainerBase; 10 | class Periphery; 11 | class PointSourceContainer; 12 | 13 | /// Namespace for System, which drives the simulation and handles communication (timestepping, data wrangling, etc) 14 | namespace System { 15 | 16 | /// @brief Time varying system properties that are extrinsic to the physical objects 17 | struct properties_t { 18 | double dt; ///< Current timestep size 19 | double time = 0.0; ///< Current system time 20 | }; 21 | 22 | void init(const std::string &input_file, bool resume_flag = false, bool listen_flag = false); 23 | Params *get_params(); 24 | BodyContainer *get_body_container(); 25 | FiberContainerBase *get_fiber_container(); 26 | Periphery *get_shell(); 27 | PointSourceContainer *get_point_source_container(); 28 | toml::value *get_param_table(); 29 | 30 | Eigen::MatrixXd calculate_body_fiber_link_conditions(CVectorRef &fibers_xt, CVectorRef &x_bodies); 31 | std::tuple get_local_solution_sizes(); 32 | Eigen::VectorXd apply_preconditioner(CVectorRef &x); 33 | Eigen::VectorXd apply_matvec(CVectorRef &x); 34 | void dynamic_instability(); 35 | void prep_state_for_solver(); 36 | bool solve(); 37 | bool step(); 38 | void run(); 39 | void write(); 40 | void write(std::ofstream &); 41 | void write_header(std::ofstream &); 42 | bool check_collision(); 43 | void backup(); 44 | void restore(); 45 | void set_evaluator(const std::string &evaluator); 46 | 47 | Eigen::VectorXd get_fiber_RHS(); 48 | Eigen::VectorXd get_shell_RHS(); 49 | Eigen::VectorXd get_body_RHS(); 50 | struct properties_t &get_properties(); 51 | Eigen::VectorXd &get_curr_solution(); 52 | std::tuple get_solution_maps(double *x); 53 | std::tuple get_solution_maps(const double *x); 54 | Eigen::MatrixXd velocity_at_targets(CMatrixRef &r_trg); 55 | 56 | }; // namespace System 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /include/trajectory_reader.hpp: -------------------------------------------------------------------------------- 1 | #ifndef TRAJECTORY_READER_HPP 2 | #define TRAJECTORY_READER_HPP 3 | 4 | #include 5 | 6 | #include 7 | 8 | class TrajectoryReader { 9 | public: 10 | TrajectoryReader(const std::string &input_file, bool resume_flag); 11 | std::size_t read_next_frame(); 12 | bool load_frame(std::size_t frameno); 13 | void unpack_current_frame(bool silence_output = false); 14 | std::size_t get_n_frames() const { return index.offsets.size(); } 15 | 16 | private: 17 | int fd_; ///< File descriptor 18 | std::size_t buflen_; ///< size of our file 19 | char *addr_; ///< mmap address 20 | std::size_t offset_; ///< current byte location in trajectory 21 | std::size_t header_offset_; ///< header offset size in trajectory 22 | msgpack::object_handle oh_; ///< handle to last read frame 23 | bool resume_flag_; ///< Reader being used to load resume data 24 | long int mtime; 25 | 26 | struct { 27 | long int mtime; 28 | std::vector offsets; ///< Vector of offsets for each frame 29 | std::vector times; ///< Vector of times for each frame 30 | MSGPACK_DEFINE_MAP(mtime, offsets, times); 31 | } index; 32 | 33 | void read_header(); 34 | void load_index(const std::string &traj_file); 35 | void build_index(const std::string &index_file); 36 | }; 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /include/utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_HPP 2 | #define UTILS_HPP 3 | 4 | #include 5 | 6 | namespace cnpy { 7 | struct NpyArray; 8 | using npz_t = std::map; 9 | } // namespace cnpy 10 | 11 | namespace utils { 12 | Eigen::MatrixXd barycentric_matrix(CArrayRef &x, CArrayRef &y); 13 | Eigen::MatrixXd finite_diff(CArrayRef &s, int M, int n_s); 14 | Eigen::VectorXd collect_into_global(CVectorRef &local_vec); 15 | 16 | Eigen::MatrixXd load_mat(cnpy::npz_t &npz, const char *var); 17 | Eigen::VectorXd load_vec(cnpy::npz_t &npz, const char *var); 18 | 19 | template 20 | bool allclose( 21 | const Eigen::DenseBase &a, const Eigen::DenseBase &b, 22 | const typename DerivedA::RealScalar &rtol = Eigen::NumTraits::dummy_precision(), 23 | const typename DerivedA::RealScalar &atol = Eigen::NumTraits::epsilon()) { 24 | return ((a.derived() - b.derived()).array().abs() <= (atol + rtol * b.derived().array().abs())).all(); 25 | } 26 | 27 | class LoggerRedirect { 28 | public: 29 | LoggerRedirect(std::ostream &in) : m_orig(in), m_old_buffer(in.rdbuf(ss.rdbuf())) {} 30 | 31 | ~LoggerRedirect() { m_orig.rdbuf(m_old_buffer); } 32 | 33 | void flush(spdlog::level::level_enum level, std::string logger = "status") { 34 | std::istringstream dumbtmp(ss.str()); 35 | for (std::string line; std::getline(dumbtmp, line);) 36 | spdlog::get(logger)->log(level, line); 37 | ss.str(""); 38 | ss.clear(); 39 | } 40 | 41 | private: 42 | LoggerRedirect(const LoggerRedirect &); 43 | LoggerRedirect &operator=(const LoggerRedirect &); 44 | 45 | std::stringstream ss; ///< temporary object to redirect to for lifetime of this object 46 | std::ostream &m_orig; ///< Actual original ostream object (usually cout) 47 | std::streambuf *m_old_buffer; ///< pointer to original streambuf pointer (usually cout's) 48 | }; 49 | 50 | }; // namespace utils 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /misc/logos/CMYK/Icon/EPS/SkellySim_Icon_CMYK_Black.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Icon/EPS/SkellySim_Icon_CMYK_Black.eps -------------------------------------------------------------------------------- /misc/logos/CMYK/Icon/EPS/SkellySim_Icon_CMYK_Full.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Icon/EPS/SkellySim_Icon_CMYK_Full.eps -------------------------------------------------------------------------------- /misc/logos/CMYK/Icon/EPS/SkellySim_Icon_CMYK_White.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Icon/EPS/SkellySim_Icon_CMYK_White.eps -------------------------------------------------------------------------------- /misc/logos/CMYK/Icon/JPG/SkellySim_Icon_CMYK_Black.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Icon/JPG/SkellySim_Icon_CMYK_Black.jpg -------------------------------------------------------------------------------- /misc/logos/CMYK/Icon/JPG/SkellySim_Icon_CMYK_Full.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Icon/JPG/SkellySim_Icon_CMYK_Full.jpg -------------------------------------------------------------------------------- /misc/logos/CMYK/Icon/PDF/SkellySim_Icon_CMYK_Black.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Icon/PDF/SkellySim_Icon_CMYK_Black.pdf -------------------------------------------------------------------------------- /misc/logos/CMYK/Icon/PDF/SkellySim_Icon_CMYK_Full.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Icon/PDF/SkellySim_Icon_CMYK_Full.pdf -------------------------------------------------------------------------------- /misc/logos/CMYK/Icon/PNG/SkellySim_Icon_CMYK_Black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Icon/PNG/SkellySim_Icon_CMYK_Black.png -------------------------------------------------------------------------------- /misc/logos/CMYK/Icon/PNG/SkellySim_Icon_CMYK_Full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Icon/PNG/SkellySim_Icon_CMYK_Full.png -------------------------------------------------------------------------------- /misc/logos/CMYK/Icon/PNG/SkellySim_Icon_CMYK_White.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Icon/PNG/SkellySim_Icon_CMYK_White.png -------------------------------------------------------------------------------- /misc/logos/CMYK/Icon/SVG/SkellySim_Icon_CMYK_Black.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/logos/CMYK/Icon/SVG/SkellySim_Icon_CMYK_Full.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/logos/CMYK/Icon/SVG/SkellySim_Icon_CMYK_White.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/logos/CMYK/Logo/EPS/SkellySim_Logo_CMYK_Black.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Logo/EPS/SkellySim_Logo_CMYK_Black.eps -------------------------------------------------------------------------------- /misc/logos/CMYK/Logo/EPS/SkellySim_Logo_CMYK_Full.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Logo/EPS/SkellySim_Logo_CMYK_Full.eps -------------------------------------------------------------------------------- /misc/logos/CMYK/Logo/EPS/SkellySim_Logo_CMYK_White.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Logo/EPS/SkellySim_Logo_CMYK_White.eps -------------------------------------------------------------------------------- /misc/logos/CMYK/Logo/JPG/SkellySim_Logo_CMYK_Black.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Logo/JPG/SkellySim_Logo_CMYK_Black.jpg -------------------------------------------------------------------------------- /misc/logos/CMYK/Logo/JPG/SkellySim_Logo_CMYK_Full.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Logo/JPG/SkellySim_Logo_CMYK_Full.jpg -------------------------------------------------------------------------------- /misc/logos/CMYK/Logo/PDF/SkellySim_Logo_CMYK_Black.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Logo/PDF/SkellySim_Logo_CMYK_Black.pdf -------------------------------------------------------------------------------- /misc/logos/CMYK/Logo/PDF/SkellySim_Logo_CMYK_Full.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Logo/PDF/SkellySim_Logo_CMYK_Full.pdf -------------------------------------------------------------------------------- /misc/logos/CMYK/Logo/PNG/SkellySim_Logo_CMYK_Black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Logo/PNG/SkellySim_Logo_CMYK_Black.png -------------------------------------------------------------------------------- /misc/logos/CMYK/Logo/PNG/SkellySim_Logo_CMYK_Full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Logo/PNG/SkellySim_Logo_CMYK_Full.png -------------------------------------------------------------------------------- /misc/logos/CMYK/Logo/PNG/SkellySim_Logo_CMYK_White.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/CMYK/Logo/PNG/SkellySim_Logo_CMYK_White.png -------------------------------------------------------------------------------- /misc/logos/CMYK/Logo/SVG/SkellySim_Logo_CMYK_Black.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/logos/CMYK/Logo/SVG/SkellySim_Logo_CMYK_Full.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/logos/CMYK/Logo/SVG/SkellySim_Logo_CMYK_White.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/logos/Favicon/SkellySim_Favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/Favicon/SkellySim_Favicon.ico -------------------------------------------------------------------------------- /misc/logos/RGB/Icon/EPS/SkellySim_Icon_RGB_Black.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Icon/EPS/SkellySim_Icon_RGB_Black.eps -------------------------------------------------------------------------------- /misc/logos/RGB/Icon/EPS/SkellySim_Icon_RGB_Full.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Icon/EPS/SkellySim_Icon_RGB_Full.eps -------------------------------------------------------------------------------- /misc/logos/RGB/Icon/EPS/SkellySim_Icon_RGB_White.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Icon/EPS/SkellySim_Icon_RGB_White.eps -------------------------------------------------------------------------------- /misc/logos/RGB/Icon/JPG/SkellySim_Icon_RGB_Black.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Icon/JPG/SkellySim_Icon_RGB_Black.jpg -------------------------------------------------------------------------------- /misc/logos/RGB/Icon/JPG/SkellySim_Icon_RGB_Full.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Icon/JPG/SkellySim_Icon_RGB_Full.jpg -------------------------------------------------------------------------------- /misc/logos/RGB/Icon/PDF/SkellySim_Icon_RGB_Black.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Icon/PDF/SkellySim_Icon_RGB_Black.pdf -------------------------------------------------------------------------------- /misc/logos/RGB/Icon/PDF/SkellySim_Icon_RGB_Full.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Icon/PDF/SkellySim_Icon_RGB_Full.pdf -------------------------------------------------------------------------------- /misc/logos/RGB/Icon/PNG/SkellySim_Icon_RGB_Black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Icon/PNG/SkellySim_Icon_RGB_Black.png -------------------------------------------------------------------------------- /misc/logos/RGB/Icon/PNG/SkellySim_Icon_RGB_Full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Icon/PNG/SkellySim_Icon_RGB_Full.png -------------------------------------------------------------------------------- /misc/logos/RGB/Icon/PNG/SkellySim_Icon_RGB_White.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Icon/PNG/SkellySim_Icon_RGB_White.png -------------------------------------------------------------------------------- /misc/logos/RGB/Icon/SVG/SkellySim_Icon_RGB_Black.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/logos/RGB/Icon/SVG/SkellySim_Icon_RGB_Full.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/logos/RGB/Icon/SVG/SkellySim_Icon_RGB_White.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/logos/RGB/Logo/EPS/SkellySim_Logo_RGB_Black.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Logo/EPS/SkellySim_Logo_RGB_Black.eps -------------------------------------------------------------------------------- /misc/logos/RGB/Logo/EPS/SkellySim_Logo_RGB_Full.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Logo/EPS/SkellySim_Logo_RGB_Full.eps -------------------------------------------------------------------------------- /misc/logos/RGB/Logo/EPS/SkellySim_Logo_RGB_White.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Logo/EPS/SkellySim_Logo_RGB_White.eps -------------------------------------------------------------------------------- /misc/logos/RGB/Logo/JPG/SkellySim_Logo_RGB_Black.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Logo/JPG/SkellySim_Logo_RGB_Black.jpg -------------------------------------------------------------------------------- /misc/logos/RGB/Logo/JPG/SkellySim_Logo_RGB_Full.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Logo/JPG/SkellySim_Logo_RGB_Full.jpg -------------------------------------------------------------------------------- /misc/logos/RGB/Logo/PDF/SkellySim_Logo_RGB_Black.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Logo/PDF/SkellySim_Logo_RGB_Black.pdf -------------------------------------------------------------------------------- /misc/logos/RGB/Logo/PDF/SkellySim_Logo_RGB_Full.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Logo/PDF/SkellySim_Logo_RGB_Full.pdf -------------------------------------------------------------------------------- /misc/logos/RGB/Logo/PNG/SkellySim_Logo_RGB_Black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Logo/PNG/SkellySim_Logo_RGB_Black.png -------------------------------------------------------------------------------- /misc/logos/RGB/Logo/PNG/SkellySim_Logo_RGB_Full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Logo/PNG/SkellySim_Logo_RGB_Full.png -------------------------------------------------------------------------------- /misc/logos/RGB/Logo/PNG/SkellySim_Logo_RGB_White.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/misc/logos/RGB/Logo/PNG/SkellySim_Logo_RGB_White.png -------------------------------------------------------------------------------- /misc/logos/RGB/Logo/SVG/SkellySim_Logo_RGB_Black.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/logos/RGB/Logo/SVG/SkellySim_Logo_RGB_Full.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/logos/RGB/Logo/SVG/SkellySim_Logo_RGB_White.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/module_template.in: -------------------------------------------------------------------------------- 1 | #%Module1.0##################################################################### 2 | # 3 | # Module: skelly_sim ${SKELLYSIM_VERSION} 4 | # 5 | 6 | set pkg skelly_sim 7 | set root ${CMAKE_INSTALL_PREFIX} 8 | set version ${SKELLYSIM_VERSION} 9 | 10 | proc ModulesHelp { } { 11 | puts stderr "Sets the environment for $pkg-$version" 12 | } 13 | 14 | module-whatis "Sets the environment for $pkg-$version" 15 | 16 | prepend-path PATH $root/bin 17 | setenv SKELLYSIM_BASE $root 18 | setenv SKELLYSIM_VERSION $version 19 | -------------------------------------------------------------------------------- /notes/model_changes.md: -------------------------------------------------------------------------------- 1 | # Numeric differences between this and Gokberk's version 2 | 3 | * Slenderness parameter 'epsilon' (r/L) 4 | * Value was fixed in original simulations (usually 1E-3) 5 | * Now it varies as it should (r/L) 6 | * Fix issue with stresslet improperly scaling inversely with viscosity 7 | Wasn't a problem if eta=1, but otherwise both SkellySim and its 8 | predecessor were wrong. Fortunately we always used eta=1. 9 | -------------------------------------------------------------------------------- /notes/model_overview.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/notes/model_overview.pdf -------------------------------------------------------------------------------- /notes/planning.md: -------------------------------------------------------------------------------- 1 | # Issues 2 | 1. Close Eval (for surfaces) 3 | 2. Close Eval (for fibers) ??? 4 | 3. Fiber Discretization 5 | 4. Fictitious ??? motion caused by ??? (timestepping scheme) 6 | 5. Boundary condition types (on fiber, on fluid) 7 | 6. Integration for different timestepping schemes 8 | 7. Do we have background flows? 9 | 8. Better scheme for bodies (stability) 10 | 9. Better for system for initial condition perturbation 11 | 10. Other formulations (Donev/Maxian/Ohm/Tangent-angle type formulation) 12 | 11. Stealth swimmers 13 | 12. Motors !?!?!&$@$ 14 | 13. ~Background flows~ 15 | 14. Rotate LU decomposition for bodies rather than re-decompose 16 | 17 | # Test problems: 18 | 1. PDE solver tests for exterior confinement // mobile bodies in confinement 19 | * easy for exterior; probably easy enough for exterior/interior 20 | * refinement for harder problems 21 | * robert has done approaching a periphery but not rotating 22 | 2. Single fiber refinement studies (correct accuracy in space / time) 23 | * free fibers 24 | * clamped fibers 25 | * pinned & growing fibers 26 | 3. Many fiber refinement studies 27 | * body with interacting fibers 28 | * ... lots of things we can imagine 29 | 4. Things with known solutions --- 30 | * single fiber under load (oscillation frequency /-> RFT) 31 | * ehssans oscillatory rheology computations 32 | * slow rotation with single MT on body (asymptotic calculation) 33 | 5. Stress tests 34 | * fibers aligned along extensional / compressional / shear flows 35 | * Reza's example 36 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "skelly_sim" 3 | version = "0.9.12" 4 | authors = [ 5 | { name="Robert Blackwell", email="rblackwell@flatironinstitute.org" } 6 | ] 7 | description = 'Simulate cytoskeletal systems with full hydrodynamics' 8 | readme = "README.md" 9 | requires-python = ">=3.7" 10 | classifiers = [ 11 | 'Development Status :: 4 - Beta', 12 | 'Intended Audience :: Scientists/Mathematicians', 13 | 'License :: Apache 2', 14 | 'Programming Language :: Python :: 3', 15 | ] 16 | license = { file="LICENSE" } 17 | dependencies = [ 18 | 'numba', 19 | 'numpy', 20 | 'scipy', 21 | 'msgpack', 22 | 'scikit-learn', 23 | 'toml', 24 | 'packaging', 25 | 'matplotlib', 26 | 'dataclass_utils', 27 | 'nptyping', 28 | 'function_generator@git+https://github.com/blackwer/function_generator@fda8b44b5edf15d1677ca7433139a43778d6659d', 29 | ] 30 | 31 | [project.optional-dependencies] 32 | tests = [ 33 | 'pytest', 34 | ] 35 | docs = [ 36 | 'sphinx-book-theme' 37 | ] 38 | 39 | [project.urls] 40 | homepage = "https//github.com/flatironinstitute/skelly_sim" 41 | documentation = "https://users.flatironinstitute.org/~rblackwell/py-skellysim" 42 | developer-documentation = "https://users.flatirninstitute.org/~rblackwell/skellysim" 43 | 44 | [project.scripts] 45 | skelly_precompute = "skelly_sim.precompute:main" 46 | 47 | [build-system] 48 | requires = ["flit_core >=3.2,<4"] 49 | build-backend = "flit_core.buildapi" 50 | 51 | [pytest] 52 | pythonpath = "src" 53 | -------------------------------------------------------------------------------- /scripts/skelly.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/scripts/skelly.blend -------------------------------------------------------------------------------- /scripts/skelly_sim.def: -------------------------------------------------------------------------------- 1 | BootStrap: library 2 | From: ubuntu:21.04 3 | 4 | %post 5 | export MKLROOT=/opt/intel/oneapi/mkl/latest 6 | export CXXFLAGS_="-I/usr/lib/x86_64-linux-gnu/openmpi/include -mavx2 -mfma" 7 | SKELLY_VERS="v0.9.9" 8 | 9 | apt -y update 10 | apt -y install curl gpg software-properties-common 11 | 12 | curl -s https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB | gpg --dearmor > /usr/share/keyrings/oneapi-archive-keyring.gpg 13 | echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" > /etc/apt/sources.list.d/oneAPI.list 14 | 15 | apt-add-repository multiverse 16 | apt-add-repository universe 17 | 18 | apt -y update 19 | apt -y install intel-oneapi-mkl-devel build-essential libopenmpi-dev cmake libeigen3-dev ninja-build git python3-pip 20 | 21 | pip3 install git+https://github.com/flatironinstitute/SkellySim.git@$SKELLY_VERS 22 | 23 | mkdir build 24 | cd build 25 | 26 | curl -L -o pvfmm.tar.gz https://github.com/dmalhotra/pvfmm/archive/refs/tags/v1.2.1.tar.gz 27 | tar -xf pvfmm.tar.gz 28 | mkdir pvfmm-build 29 | 30 | cd pvfmm-build 31 | cmake ../pvfmm-1.2.1 -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Release \ 32 | -DCMAKE_CXX_FLAGS="$CXXFLAGS_" -GNinja 33 | ninja 34 | ninja install 35 | cd .. 36 | 37 | curl -L -o stkfmm.tar.gz https://github.com/wenyan4work/STKFMM/archive/refs/tags/v1.0.0.tar.gz 38 | tar -xf stkfmm.tar.gz 39 | mkdir stkfmm-build 40 | 41 | cd stkfmm-build 42 | pvfmm_DIR=/usr/local/share/pvfmm cmake ../STKFMM-1.0.0 -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local \ 43 | -DCMAKE_CXX_FLAGS="$CXXFLAGS_" -GNinja 44 | ninja 45 | ninja install 46 | cd .. 47 | 48 | curl -L -o trilinos.tar.gz https://github.com/trilinos/Trilinos/archive/refs/tags/trilinos-release-13-2-0.tar.gz 49 | tar -xf trilinos.tar.gz 50 | mkdir trilinos-build 51 | 52 | cd trilinos-build 53 | cmake ../Trilinos-trilinos-release-13-2-0 -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local -DTrilinos_VERBOSE_CONFIGURE:BOOL=OFF -DBUILD_SHARED_LIBS:BOOL=ON -DCMAKE_CXX_STANDARD:STRING=14 -DTrilinos_ENABLE_ALL_OPTIONAL_PACKAGES:BOOL=OFF -DTrilinos_ENABLE_ALL_PACKAGES:BOOL=OFF -DTrilinos_ENABLE_OpenMP=ON -DTrilinos_ENABLE_Kokkos:BOOL=ON -DTrilinos_ENABLE_Belos:BOOL=ON -DTrilinos_ENABLE_Tpetra:BOOL=ON -DTrilinos_ENABLE_Teuchos:BOOL=ON -DBUILD_TESTING:BOOL=OFF -DTrilinos_ENABLE_Fortran:BOOL=OFF -D TPL_ENABLE_MPI=ON -DTPL_BLAS_LIBRARIES=/opt/intel/oneapi/mkl/latest/lib/intel64/libmkl_rt.so -DTPL_LAPACK_LIBRARIES=/opt/intel/oneapi/mkl/latest/lib/intel64/libmkl_rt.so -DCMAKE_CXX_FLAGS="$CXXFLAGS_" -GNinja 54 | ninja 55 | ninja install 56 | cd .. 57 | 58 | git clone https://github.com/flatironinstitute/skellysim 59 | cd skellysim 60 | git checkout ${SKELLY_VERS} 61 | git submodule update --init 62 | cd .. 63 | 64 | mkdir skellysim-build 65 | cd skellysim-build 66 | STKFMM_ROOT=/usr/local cmake ../skellysim -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local \ 67 | -DCMAKE_CXX_FLAGS="$CXXFLAGS_" -DBLAS_LIBRARIES=$MKLROOT/lib/intel64/libmkl_rt.so \ 68 | -DLAPACK_LIBRARIES=$MKLROOT/lib/intel64/libmkl_rt.so -GNinja 69 | ninja 70 | ninja install 71 | 72 | cd ../.. 73 | rm -rf build 74 | 75 | 76 | %environment 77 | export LC_ALL=C 78 | export PATH=/usr/local/bin:$PATH 79 | export MKL_THREADING_LAYER=GNU 80 | export pvfmm_DIR=$HOME/.cache/pvfmm 81 | export LD_LIBRARY_PATH=/opt/intel/oneapi/mkl/latest/lib/intel64:/usr/local/lib:$LD_LIBRARY_PATH 82 | 83 | %labels 84 | Author blackwer 85 | -------------------------------------------------------------------------------- /src/core/background_source.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | BackgroundSource::BackgroundSource(const toml::value &background_table) { 6 | if (background_table.contains("components")) 7 | components_ = parse_util::convert_array(background_table.at("components").as_array()); 8 | if (background_table.contains("scale_factor")) 9 | scale_factor_ = parse_util::convert_array<>(background_table.at("scale_factor").as_array()); 10 | if (background_table.contains("uniform")) 11 | uniform_ = parse_util::convert_array<>(background_table.at("uniform").as_array()); 12 | } 13 | 14 | Eigen::MatrixXd BackgroundSource::flow(const CMatrixRef &r_trg, double eta) { 15 | Eigen::MatrixXd vel = Eigen::MatrixXd::Zero(3, r_trg.cols()); 16 | 17 | for (int i = 0; i < r_trg.cols(); ++i) 18 | for (int j = 0; j < 3; ++j) 19 | vel(j, i) = uniform_[j] + r_trg(components_[j], i) * scale_factor_[j]; 20 | 21 | return vel; 22 | } 23 | -------------------------------------------------------------------------------- /src/core/body.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using Eigen::MatrixXd; 11 | using Eigen::VectorXd; 12 | 13 | /// @brief Construct body from relevant toml config and system params 14 | /// 15 | /// @param[in] body_table toml table from pre-parsed config 16 | /// @param[in] params Pre-constructed Params object 17 | /// surface). 18 | /// @return Body object that has been appropriately rotated. Other internal cache variables are _not_ updated. 19 | /// @see update_cache_variables 20 | Body::Body(const toml::value &body_table, const Params ¶ms) {} 21 | 22 | const std::string Body::EXTFORCE_name[] = {"Linear", "Oscillatory"}; 23 | 24 | -------------------------------------------------------------------------------- /src/core/body_deformable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | void DeformableBody::min_copy(const std::shared_ptr &other) {} 14 | void DeformableBody::update_RHS(CMatrixRef &v_on_body) {} 15 | void DeformableBody::update_cache_variables(double eta) {} 16 | void DeformableBody::update_preconditioner(double eta) {} 17 | void DeformableBody::load_precompute_data(const std::string &input_file) {} 18 | void DeformableBody::step(double dt, CVectorRef &body_solution) {} 19 | Eigen::VectorXd DeformableBody::matvec(CMatrixRef &v_bodies, CVectorRef &body_solution) const { 20 | return Eigen::VectorXd(); 21 | } 22 | Eigen::VectorXd DeformableBody::apply_preconditioner(CVectorRef &x) const { return Eigen::VectorXd(); } 23 | Eigen::Vector3d DeformableBody::get_position() const { return Eigen::Vector3d(); } 24 | 25 | bool DeformableBody::check_collision(const Periphery &periphery, double threshold) const { 26 | return periphery.check_collision(*this, threshold); 27 | } 28 | bool DeformableBody::check_collision(const Body &body, double threshold) const { 29 | return body.check_collision(*this, threshold); 30 | } 31 | bool DeformableBody::check_collision(const SphericalBody &body, double threshold) const { 32 | return body.check_collision(*this, threshold); 33 | } 34 | bool DeformableBody::check_collision(const DeformableBody &body, double threshold) const { 35 | spdlog::warn("check_collision not defined for DeformableBody->DeformableBody"); 36 | return false; 37 | } 38 | bool DeformableBody::check_collision(const EllipsoidalBody &body, double threshold) const { 39 | spdlog::warn("check_collision not defined for DeformableBody->EllipsoidalBody"); 40 | return false; 41 | } 42 | -------------------------------------------------------------------------------- /src/core/fiber_container_base.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | /// @brief Constructor 8 | FiberContainerBase::FiberContainerBase(toml::array &fiber_tables, Params ¶ms) { 9 | spdlog::debug("FiberContainerBase::FiberContainerBase"); 10 | 11 | MPI_Comm_size(MPI_COMM_WORLD, &world_size_); 12 | MPI_Comm_rank(MPI_COMM_WORLD, &world_rank_); 13 | 14 | set_evaluator(params.pair_evaluator); 15 | 16 | spdlog::debug("FiberContainerBase::FiberContainerBase return"); 17 | } 18 | 19 | /// @brief Set the evaluator 20 | void FiberContainerBase::set_evaluator(const std::string &evaluator) { 21 | auto ¶ms = *System::get_params(); 22 | if (evaluator == "FMM") { 23 | utils::LoggerRedirect redirect(std::cout); 24 | const int mult_order = params.stkfmm.fiber_stokeslet_multipole_order; 25 | const int max_pts = params.stkfmm.fiber_stokeslet_max_points; 26 | stokeslet_kernel_ = kernels::FMM(mult_order, max_pts, stkfmm::PAXIS::NONE, 27 | stkfmm::KERNEL::Stokes, kernels::stokes_vel_fmm); 28 | redirect.flush(spdlog::level::debug, "STKFMM"); 29 | } else if (evaluator == "CPU") 30 | stokeslet_kernel_ = kernels::stokeslet_direct_cpu; 31 | else if (evaluator == "GPU") 32 | stokeslet_kernel_ = kernels::stokeslet_direct_gpu; 33 | } 34 | 35 | /// @brief Get the global number of fibers in all MPI ranks 36 | int FiberContainerBase::get_global_fiber_count() const { 37 | const int local_fib_count = get_local_fiber_count(); 38 | int global_fib_count; 39 | 40 | MPI_Allreduce(&local_fib_count, &global_fib_count, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD); 41 | return global_fib_count; 42 | } 43 | 44 | /// @brief Get the global number of nodes for fibers in all MPI ranks 45 | int FiberContainerBase::get_global_node_count() const { 46 | const int local_node_count = get_local_node_count(); 47 | int global_node_count; 48 | 49 | MPI_Allreduce(&local_node_count, &global_node_count, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD); 50 | return global_node_count; 51 | } 52 | 53 | /// @brief Get the global solution size across fibers in all MPI ranks 54 | int FiberContainerBase::get_global_solution_size() const { 55 | const int local_solution_size = get_local_solution_size(); 56 | int global_solution_size; 57 | 58 | MPI_Allreduce(&local_solution_size, &global_solution_size, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD); 59 | return global_solution_size; 60 | } 61 | -------------------------------------------------------------------------------- /src/core/point_source.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | PointSource::PointSource(const toml::value &point_table) { 6 | if (point_table.contains("position")) 7 | position_ = parse_util::convert_array<>(point_table.at("position").as_array()); 8 | if (point_table.contains("force")) 9 | force_ = parse_util::convert_array<>(point_table.at("force").as_array()); 10 | if (point_table.contains("torque")) 11 | torque_ = parse_util::convert_array<>(point_table.at("torque").as_array()); 12 | if (point_table.contains("time_to_live")) 13 | time_to_live_ = point_table.at("time_to_live").as_floating(); 14 | } 15 | 16 | Eigen::MatrixXd PointSourceContainer::flow(const CMatrixRef &r_trg, double eta, double time) { 17 | Eigen::MatrixXd vel = Eigen::MatrixXd::Zero(3, r_trg.cols()); 18 | std::vector> forcers; 19 | std::vector> torquers; 20 | 21 | for (auto &point : points) { 22 | // Check if deactivated. ttl of 0.0 implies always alive 23 | if (point.time_to_live_ && time >= point.time_to_live_) 24 | continue; 25 | if (point.force_.any()) 26 | forcers.push_back(std::make_pair(point.position_, point.force_)); 27 | if (point.torque_.any()) 28 | torquers.push_back(std::make_pair(point.position_, point.torque_)); 29 | } 30 | 31 | if (forcers.size()) { 32 | const int n_forcers = forcers.size(); 33 | Eigen::MatrixXd positions(3, n_forcers); 34 | Eigen::MatrixXd forces(3, n_forcers); 35 | for (int i = 0; i < n_forcers; ++i) { 36 | positions.col(i) = forcers[i].first; 37 | forces.col(i) = forcers[i].second; 38 | } 39 | vel += kernels::oseen_tensor_contract_direct(positions, r_trg, forces, eta); 40 | } 41 | 42 | if (torquers.size()) { 43 | const int n_torquers = torquers.size(); 44 | Eigen::MatrixXd positions(3, n_torquers); 45 | Eigen::MatrixXd forces(3, n_torquers); 46 | for (int i = 0; i < n_torquers; ++i) { 47 | positions.col(i) = torquers[i].first; 48 | forces.col(i) = torquers[i].second; 49 | } 50 | vel += kernels::rotlet(positions, r_trg, forces, eta); 51 | } 52 | 53 | return vel; 54 | } 55 | 56 | PointSourceContainer::PointSourceContainer(const toml::array &point_tables) { 57 | const int n_points_tot = point_tables.size(); 58 | spdlog::info("Reading in {} points", n_points_tot); 59 | 60 | for (int i_point = 0; i_point < n_points_tot; ++i_point) { 61 | const toml::value &point_table = point_tables.at(i_point); 62 | points.emplace_back(PointSource(point_table)); 63 | 64 | auto &point = points.back(); 65 | auto position = point.position_; 66 | spdlog::info("Point {}: [ {}, {}, {} ]", i_point, position[0], position[1], position[2]); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/core/rng.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | 13 | namespace RNG { 14 | using engine_type = trng::yarn2; 15 | engine_type engine_distributed; 16 | engine_type engine_shared; 17 | 18 | void init(unsigned long seed) { 19 | int rank, size; 20 | MPI_Comm_rank(MPI_COMM_WORLD, &rank); 21 | MPI_Comm_size(MPI_COMM_WORLD, &size); 22 | engine_shared = engine_type(); 23 | engine_distributed = engine_type(); 24 | 25 | engine_distributed.seed(seed); 26 | engine_shared.seed(seed); 27 | 28 | engine_shared.split(2, 0); 29 | 30 | engine_distributed.split(2, 1); 31 | engine_distributed.split(size, rank); 32 | } 33 | 34 | void init(std::pair state) { 35 | std::stringstream shared_stream; 36 | std::stringstream distributed_stream; 37 | 38 | shared_stream.str(state.first); 39 | distributed_stream.str(state.second); 40 | 41 | shared_stream >> engine_shared; 42 | distributed_stream >> engine_distributed; 43 | } 44 | 45 | std::pair dump_state() { 46 | std::stringstream shared_stream; 47 | std::stringstream distributed_stream; 48 | 49 | shared_stream << engine_shared; 50 | distributed_stream << engine_distributed; 51 | return std::make_pair(shared_stream.str(), distributed_stream.str()); 52 | } 53 | 54 | double uniform(double low, double high) { return trng::uniform_dist(low, high)(engine_distributed); } 55 | int uniform_int(int low, int high) { return trng::uniform_int_dist(low, high)(engine_distributed); } 56 | double normal(double mu, double sigma) { return trng::normal_dist(mu, sigma)(engine_distributed); } 57 | int poisson_int(double mu) { return trng::poisson_dist(mu)(engine_distributed); } 58 | 59 | double uniform_unsplit(double low, double high) { return trng::uniform_dist(low, high)(engine_shared); } 60 | int uniform_int_unsplit(int low, int high) { return trng::uniform_int_dist(low, high)(engine_shared); } 61 | double normal_unsplit(double mu, double sigma) { return trng::normal_dist(mu, sigma)(engine_shared); } 62 | int poisson_int_unsplit(double mu) { return trng::poisson_dist(mu)(engine_shared); } 63 | } // namespace RNG 64 | -------------------------------------------------------------------------------- /src/skelly_sim.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | int main(int argc, char *argv[]) { 13 | int thread_level; 14 | MPI_Init_thread(&argc, &argv, MPI_THREAD_FUNNELED, &thread_level); 15 | 16 | { 17 | const char *testargv[] = {"--kokkos-disable-warnings"}; 18 | int testargc = 1; 19 | Kokkos::initialize(testargc, const_cast(testargv)); 20 | } 21 | 22 | std::string config_file = "skelly_config.toml"; 23 | bool resume_flag = false; 24 | bool overwrite_flag = false; 25 | bool listen_flag = false; 26 | Teuchos::CommandLineProcessor cmdp(false, true); 27 | cmdp.setOption("config-file", &config_file, "TOML input file."); 28 | cmdp.setOption("resume", "no-resume", &resume_flag, "Supply to resume simulation."); 29 | cmdp.setOption("overwrite", "no-overwrite", &overwrite_flag, "Supply to overwrite existing simulation."); 30 | cmdp.setOption("listen", "no-listen", &listen_flag, "Supply to run in listener mode"); 31 | 32 | if (cmdp.parse(argc, argv) != Teuchos::CommandLineProcessor::PARSE_SUCCESSFUL) { 33 | MPI_Finalize(); 34 | return EXIT_FAILURE; 35 | } 36 | 37 | try { 38 | if (resume_flag && overwrite_flag) 39 | throw std::runtime_error("Can't resume and overwrite simultaneously."); 40 | namespace fs = std::filesystem; 41 | if (listen_flag) { 42 | if (!fs::exists(fs::path{"skelly_sim.out"})) 43 | throw std::runtime_error("No trajectory detected for listener."); 44 | } else if (resume_flag) { 45 | if (!fs::exists(fs::path{"skelly_sim.out"})) 46 | throw std::runtime_error("--resume flag supplied without existing trajectory."); 47 | } else { 48 | if (!overwrite_flag && (fs::exists(fs::path{"skelly_sim.out"}) || fs::exists(fs::path{"skelly_sim.vf"}))) 49 | throw std::runtime_error("Existing trajectory detected. Supply --overwrite flag to overwrite."); 50 | } 51 | 52 | System::init(config_file, resume_flag, listen_flag); 53 | if (listen_flag) 54 | listener::run(); 55 | else 56 | System::run(); 57 | } catch (const std::runtime_error &e) { 58 | // Warning: Critical only catches things on rank 0, so this may or may not print, if 59 | // some random rank throws an error. This is the same reason we use MPI_Abort: all 60 | // ranks are not guaranteed to land here, so there's only so much grace that can be 61 | // easily implemented. 62 | spdlog::critical(std::string("Fatal exception caught: ") + e.what()); 63 | MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE); 64 | } 65 | 66 | MPI_Finalize(); 67 | return EXIT_SUCCESS; 68 | } 69 | -------------------------------------------------------------------------------- /src/skelly_sim/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/src/skelly_sim/__init__.py -------------------------------------------------------------------------------- /src/skelly_sim/paraview_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/src/skelly_sim/paraview_utils/__init__.py -------------------------------------------------------------------------------- /src/skelly_sim/paraview_utils/body_reader.py: -------------------------------------------------------------------------------- 1 | import vtk 2 | from trajectory_utility import load_frame 3 | import toml 4 | 5 | outInfo = self.GetOutputInformation(0) 6 | 7 | if outInfo.Has(vtk.vtkStreamingDemandDrivenPipeline.UPDATE_TIME_STEP()): 8 | time = outInfo.Get(vtk.vtkStreamingDemandDrivenPipeline.UPDATE_TIME_STEP()) 9 | else: 10 | time = 0 11 | 12 | timestep = len(self.times) - 1 13 | for i in range(len(self.times) - 1): 14 | if time < self.times[i+1] and time >= self.times[i]: 15 | timestep = i 16 | 17 | frame = load_frame(self.fhs, self.fpos, timestep) 18 | 19 | with open(toml_file) as f: 20 | skelly_config = toml.load(f) 21 | 22 | mb = vtk.vtkMultiBlockDataSet() 23 | offset = 0 24 | for i, body in enumerate(frame["bodies"]): 25 | position = body["position_"][3:] 26 | s = vtk.vtkSphereSource() 27 | s.SetRadius(skelly_config["bodies"][i]['radius']) 28 | s.SetCenter(position) 29 | s.SetThetaResolution(32) 30 | s.SetPhiResolution(32) 31 | s.Update() 32 | mb.SetBlock(offset, s.GetOutput()) 33 | offset += 1 34 | 35 | self.GetOutput().ShallowCopy(mb) 36 | -------------------------------------------------------------------------------- /src/skelly_sim/paraview_utils/body_reader_request.py: -------------------------------------------------------------------------------- 1 | import msgpack 2 | from pathlib import Path 3 | import vtk 4 | from trajectory_utility import get_frame_info 5 | 6 | outInfo = self.GetOutputInformation(0) 7 | 8 | print("Initializing bodies") 9 | self.fhs, self.fpos, self.times = get_frame_info(sorted(Path('.').glob('skelly_sim.out.*'))) 10 | outInfo.Set(vtk.vtkStreamingDemandDrivenPipeline.TIME_RANGE(), [self.times[0], self.times[-1]], 2) 11 | outInfo.Set(vtk.vtkStreamingDemandDrivenPipeline.TIME_STEPS(), self.times, len(self.times)) 12 | -------------------------------------------------------------------------------- /src/skelly_sim/paraview_utils/fiber_reader.py: -------------------------------------------------------------------------------- 1 | import vtk 2 | from trajectory_utility import load_frame 3 | 4 | outInfo = self.GetOutputInformation(0) 5 | 6 | if outInfo.Has(vtk.vtkStreamingDemandDrivenPipeline.UPDATE_TIME_STEP()): 7 | time = outInfo.Get(vtk.vtkStreamingDemandDrivenPipeline.UPDATE_TIME_STEP()) 8 | else: 9 | time = 0 10 | 11 | timestep = len(self.times) - 1 12 | for i in range(len(self.times) - 1): 13 | if time < self.times[i+1] and time >= self.times[i]: 14 | timestep = i 15 | break 16 | 17 | frame = load_frame(self.fhs, self.fpos, timestep) 18 | 19 | pts = vtk.vtkPoints() 20 | lines = vtk.vtkCellArray() 21 | offset = 0 22 | for fib in frame["fibers"]: 23 | n_nodes = fib["n_nodes_"] 24 | lines.InsertNextCell(n_nodes) 25 | pl = vtk.vtkPolyLine() 26 | pl.GetPointIds().SetNumberOfIds(n_nodes) 27 | 28 | for i in range(n_nodes): 29 | low = 3 + i * 3 30 | lines.InsertCellPoint(offset) 31 | pts.InsertPoint(offset, fib["x_"][low : low + 3]) 32 | offset += 1 33 | 34 | pd = self.GetPolyDataOutput() 35 | pd.SetPoints(pts) 36 | pd.SetLines(lines) 37 | -------------------------------------------------------------------------------- /src/skelly_sim/paraview_utils/fiber_reader_request.py: -------------------------------------------------------------------------------- 1 | import msgpack 2 | from pathlib import Path 3 | from trajectory_utility import get_frame_info 4 | 5 | outInfo = self.GetOutputInformation(0) 6 | print("Initializing fibers") 7 | self.fhs, self.fpos, self.times = get_frame_info(sorted(Path('.').glob('skelly_sim.out.*'))) 8 | outInfo.Set(vtk.vtkStreamingDemandDrivenPipeline.TIME_RANGE(), [self.times[0], self.times[-1]], 2) 9 | outInfo.Set(vtk.vtkStreamingDemandDrivenPipeline.TIME_STEPS(), self.times, len(self.times)) 10 | -------------------------------------------------------------------------------- /src/skelly_sim/paraview_utils/field_reader.py: -------------------------------------------------------------------------------- 1 | import vtk 2 | from trajectory_utility import load_field_frame 3 | import numpy as np 4 | 5 | outInfo = self.GetOutputInformation(0) 6 | 7 | if outInfo.Has(vtk.vtkStreamingDemandDrivenPipeline.UPDATE_TIME_STEP()): 8 | time = outInfo.Get(vtk.vtkStreamingDemandDrivenPipeline.UPDATE_TIME_STEP()) 9 | else: 10 | time = 0 11 | 12 | timestep = len(self.times) - 1 13 | for i in range(len(self.times) - 1): 14 | if time < self.times[i+1] and time >= self.times[i]: 15 | timestep = i 16 | 17 | frame = load_field_frame(self.fhs, self.fpos, timestep) 18 | 19 | pts = vtk.vtkPoints() 20 | npts = int(sum([data["x_grid"][2] for data in frame])) 21 | vertices = vtk.vtkPolyData() 22 | 23 | velocities = vtk.vtkDoubleArray() 24 | velocities.SetName("velocities") 25 | velocities.SetNumberOfComponents(3) 26 | velocities.SetNumberOfTuples(npts) 27 | 28 | magnitudes = vtk.vtkDoubleArray() 29 | magnitudes.SetName("magnitudes") 30 | magnitudes.SetNumberOfComponents(0) 31 | magnitudes.SetNumberOfValues(npts) 32 | 33 | offset = 0 34 | for data in frame: 35 | n_points_local = data["x_grid"][2] 36 | x_grid = data["x_grid"][3:] 37 | v_grid = data["v_grid"][3:] 38 | for i in range(n_points_local): 39 | pts.InsertPoint(offset, x_grid[3 * i : 3* (i + 1)]) 40 | # verts.InsertNextCell(1, [offset]) 41 | mag = np.linalg.norm(np.array(v_grid[3*i:3*(i+1)])) 42 | magnitudes.SetValue(offset, mag) 43 | velocities.SetTuple(offset, v_grid[3 * i : 3* (i + 1)]) 44 | offset += 1 45 | 46 | pd = self.GetPolyDataOutput() 47 | pd.SetPoints(pts) 48 | pd.GetPointData().AddArray(velocities) 49 | pd.GetPointData().AddArray(magnitudes) 50 | -------------------------------------------------------------------------------- /src/skelly_sim/paraview_utils/field_reader_request.py: -------------------------------------------------------------------------------- 1 | import msgpack 2 | from pathlib import Path 3 | from trajectory_utility import get_frame_info 4 | 5 | outInfo = self.GetOutputInformation(0) 6 | print("Initializing velocity field") 7 | self.fhs, self.fpos, self.times = get_frame_info(sorted(Path('.').glob('skelly_sim.vf.*'))) 8 | print(self.times) 9 | outInfo.Set(vtk.vtkStreamingDemandDrivenPipeline.TIME_RANGE(), [self.times[0], self.times[-1]], 2) 10 | outInfo.Set(vtk.vtkStreamingDemandDrivenPipeline.TIME_STEPS(), self.times, len(self.times)) 11 | print("Finished init") 12 | -------------------------------------------------------------------------------- /src/skelly_sim/paraview_utils/trajectory_utility.py: -------------------------------------------------------------------------------- 1 | import msgpack 2 | 3 | 4 | class DesyncError(Exception): 5 | pass 6 | 7 | 8 | def load_frame(fhs, fpos, index): 9 | data = [] 10 | for i in range(len(fhs)): 11 | fhs[i].seek(fpos[i][index]) 12 | data.append(msgpack.Unpacker(fhs[i], raw=False).unpack()) 13 | 14 | time = data[0]["time"] 15 | dt = data[0]["dt"] 16 | fibers = [] 17 | bodies = [] 18 | for el in data: 19 | if el["time"] != time or el["dt"] != dt: 20 | raise DesyncError 21 | fibers.extend(el["fibers"][0]) 22 | el.pop("fibers") 23 | 24 | data[0]["fibers"] = fibers 25 | data[0]["bodies"] = data[0]["bodies"][0] 26 | 27 | return data[0] 28 | 29 | 30 | def load_field_frame(fhs, fpos, index): 31 | data = [] 32 | for i in range(len(fhs)): 33 | fhs[i].seek(fpos[i][index]) 34 | data.append(msgpack.Unpacker(fhs[i], raw=False).unpack()) 35 | 36 | return data 37 | 38 | 39 | def get_frame_info(filenames): 40 | if not filenames: 41 | return [], [], [] 42 | 43 | unpackers = [] 44 | fhs = [] 45 | times = [] 46 | for filename in filenames: 47 | f = open(filename, "rb") 48 | fhs.append(f) 49 | unpackers.append(msgpack.Unpacker(f, raw=False)) 50 | 51 | fpos = [[] for i in range(len(filenames))] 52 | while True: 53 | try: 54 | for i in range(len(unpackers)): 55 | fpos[i].append(unpackers[i].tell()) 56 | if i == 0: 57 | n_keys = unpackers[i].read_map_header() 58 | for ikey in range(n_keys): 59 | key = unpackers[i].unpack() 60 | if key == 'time': 61 | times.append(unpackers[i].unpack()) 62 | else: 63 | unpackers[i].skip() 64 | else: 65 | unpackers[i].skip() 66 | 67 | except msgpack.exceptions.OutOfData: 68 | fpos[0].pop() 69 | break 70 | 71 | return fhs, fpos, times 72 | -------------------------------------------------------------------------------- /src/skelly_sim/periphery.py: -------------------------------------------------------------------------------- 1 | """ 2 | Small class to handle the periphery. 3 | """ 4 | import numpy as np 5 | import skelly_sim.kernels as kernels 6 | 7 | 8 | class Periphery(object): 9 | """ 10 | Small class to handle a single body. 11 | """ 12 | def __init__(self, location, orientation, reference_configuration, reference_normals, quadrature_weights): 13 | """ 14 | Constructor. Take arguments like ... 15 | """ 16 | # Location as np.array.shape = 3 17 | self.location = location 18 | # Orientation as Quaternion 19 | self.orientation = orientation 20 | # Number of blobs 21 | self.Nblobs = reference_configuration.size // 3 22 | # Reference configuration. Coordinates of blobs for quaternion [1, 0, 0, 0] 23 | # and location = np.array[0, 0, 0]) as a np.array.shape = (Nblobs, 3) 24 | # or np.array.shape = (Nblobs * 3) 25 | self.reference_configuration = np.reshape(reference_configuration, (self.Nblobs, 3)) 26 | self.reference_normals = np.reshape(reference_normals, (self.Nblobs, 3)) 27 | self.quadrature_weights = quadrature_weights.flatten() 28 | self.Nblobs = self.quadrature_weights.size 29 | # Name of body and type of body. A string or number 30 | self.name = None 31 | self.type = None 32 | self.rotation_matrix = None 33 | # Some default functions 34 | self.function_slip = np.zeros((self.Nblobs, 3)) 35 | self.ID = None 36 | # Vectors for singularity subtractions 37 | self.ex = None 38 | self.ey = None 39 | self.ez = None 40 | self.density = np.zeros(3 * self.Nblobs) 41 | self.density_new = np.zeros(3 * self.Nblobs) 42 | 43 | def get_r_vectors(self, location=None, orientation=None): 44 | """ 45 | Return the coordinates of the blobs. 46 | """ 47 | # Get location and orientation 48 | if location is None: 49 | location = self.location 50 | if orientation is None: 51 | orientation = self.orientation 52 | 53 | # Compute blobs coordinates 54 | rotation_matrix = orientation.rotation_matrix() 55 | r_vectors = np.array([np.dot(rotation_matrix, vec) for vec in self.reference_configuration]) 56 | r_vectors += location 57 | return r_vectors 58 | 59 | def get_normals(self, orientation=None): 60 | """ 61 | Return the normals of the periphery. 62 | """ 63 | # Get orientation 64 | if orientation is None: 65 | orientation = self.orientation 66 | 67 | # Compute blobs coordinates 68 | rotation_matrix = orientation.rotation_matrix() 69 | normals = np.array([np.dot(rotation_matrix, vec) for vec in self.reference_normals]) 70 | return normals 71 | 72 | def get_singularity_subtraction_vectors(self, eta=1): 73 | 74 | # Compute correction for singularity subtractions 75 | r_vectors_blobs = self.get_r_vectors() 76 | normals = self.get_normals() 77 | quadrature_weights = self.quadrature_weights 78 | Nperiphery = quadrature_weights.size 79 | e = np.zeros((Nperiphery, 3)) 80 | e[:, 0] = 1.0 81 | e *= quadrature_weights[:, None] 82 | self.ex = kernels.stresslet_kernel_times_normal_times_density_numba(r_vectors_blobs, normals, e, eta) 83 | e[:, :] = 0.0 84 | e[:, 1] = 1.0 85 | e *= quadrature_weights[:, None] 86 | self.ey = kernels.stresslet_kernel_times_normal_times_density_numba(r_vectors_blobs, normals, e, eta) 87 | e[:, :] = 0.0 88 | e[:, 2] = 1.0 89 | e *= quadrature_weights[:, None] 90 | self.ez = kernels.stresslet_kernel_times_normal_times_density_numba(r_vectors_blobs, normals, e, eta) 91 | 92 | return 93 | -------------------------------------------------------------------------------- /src/skelly_sim/quaternion.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Simple quaternion object for use with quaternion integrators. 3 | ''' 4 | import numpy as np 5 | 6 | 7 | class Quaternion(object): 8 | def __init__(self, entries): 9 | ''' Constructor, takes 4 entries = s, p1, p2, p3 as a numpy array. ''' 10 | self.entries = entries 11 | self.s = np.array(entries[0]) 12 | self.p = np.array(entries[1:4]) 13 | 14 | @classmethod 15 | def from_rotation(cls, phi): 16 | ''' Create a quaternion given an angle of rotation phi, 17 | which represents a rotation clockwise about the vector phi of magnitude 18 | phi. This will be used with phi = omega*dt or similar in the integrator.''' 19 | phi_norm = np.linalg.norm(phi) 20 | s = np.array([np.cos(phi_norm / 2.)]) 21 | if phi_norm != 0: 22 | p = np.sin(phi_norm / 2) * (phi / phi_norm) 23 | else: 24 | p = np.zeros(3) 25 | return cls(np.concatenate([s, p])) 26 | 27 | def __mul__(self, other): 28 | ''' 29 | Quaternion multiplication. In this case, other is the 30 | right quaternion. 31 | ''' 32 | s = (self.s * other.s - np.dot(self.p, other.p)) 33 | p = (self.s * other.p + other.s * self.p + np.cross(self.p, other.p)) 34 | return Quaternion(np.concatenate(([s], p))) 35 | 36 | def rotation_matrix(self): 37 | ''' 38 | Return the rotation matrix representing rotation 39 | by this quaternion. 40 | ''' 41 | # Cross product matrix for p, actually the negative. 42 | diag = self.s**2 - 0.5 43 | return 2.0 * np.array([[ 44 | self.p[0]**2 + diag, self.p[0] * self.p[1] - self.s * self.p[2], self.p[0] * self.p[2] + self.s * self.p[1] 45 | ], [ 46 | self.p[1] * self.p[0] + self.s * self.p[2], self.p[1]**2 + diag, self.p[1] * self.p[2] - self.s * self.p[0] 47 | ], [ 48 | self.p[2] * self.p[0] - self.s * self.p[1], self.p[2] * self.p[1] + self.s * self.p[0], self.p[2]**2 + diag 49 | ]]) 50 | 51 | def __str__(self): 52 | return '[ %f, %f, %f, %f ]' % (self.s, self.p[0], self.p[1], self.p[2]) 53 | 54 | def inverse(self): 55 | ''' Return the inverse quaternion.''' 56 | return Quaternion([self.s, -1. * self.p[0], -1. * self.p[1], -1. * self.p[2]]) 57 | 58 | def rotation_angle(self): 59 | ''' Return 3 dimensional rotation angle that the quaternion represents. ''' 60 | phi_norm = 2. * np.arccos(self.s) 61 | return phi_norm * self.p / (np.linalg.norm(self.p)) 62 | 63 | def random_orientation(self): 64 | '''Give this quaternion object a random orientation''' 65 | theta = np.random.normal(0., 1., 4) 66 | theta = theta / np.linalg.norm(theta) 67 | self.entries = theta 68 | self.s = theta[0] 69 | self.p = theta[1:4] 70 | -------------------------------------------------------------------------------- /src/skelly_sim/testing.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import os 3 | import pytest 4 | 5 | from pathlib import Path 6 | from subprocess import run 7 | 8 | @contextlib.contextmanager 9 | def working_directory(path): 10 | """Changes working directory and returns to previous on exit.""" 11 | prev_cwd = Path.cwd() 12 | os.chdir(path) 13 | try: 14 | yield 15 | finally: 16 | os.chdir(prev_cwd) 17 | 18 | @pytest.fixture(scope="session") 19 | def sim_path(tmp_path_factory): 20 | path = tmp_path_factory.mktemp("sims") 21 | return path 22 | 23 | def run_sim(path: Path=Path('.')): 24 | print("Running simulation") 25 | with working_directory(path): 26 | res = run(['skelly_sim', '--overwrite'], shell=False, capture_output=False) 27 | return not res.returncode 28 | 29 | def run_precompute(path: Path=Path('.')): 30 | print("Generating precompute data") 31 | with working_directory(path): 32 | res = run(['skelly_precompute'], shell=False, capture_output=True) 33 | return not res.returncode 34 | -------------------------------------------------------------------------------- /tests/combined/bodies/test_compare_quadrature.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from pathlib import Path 4 | from skelly_sim.reader import TrajectoryReader 5 | from skelly_sim.skelly_config import Config, Body 6 | from skelly_sim.testing import working_directory, run_sim, run_precompute, sim_path 7 | 8 | np.random.seed(100) 9 | n_nodes = 800 10 | config_file = 'skelly_config.toml' 11 | 12 | def gen_config(path: Path=('.')): 13 | print("Generating config") 14 | # create a config object and set the system parameters 15 | config = Config() 16 | config.params.eta = 0.9 17 | config.params.dt_initial = 1E-1 18 | config.params.dt_min = 1E-4 19 | config.params.dt_max = 1E-1 20 | config.params.dt_write = 1E-1 21 | config.params.t_final = 0.2 22 | config.params.gmres_tol = 1E-10 23 | config.params.seed = 130319 24 | config.params.pair_evaluator = "CPU" 25 | 26 | config.bodies = [ 27 | Body(n_nucleation_sites=0, 28 | position=[0.0, 0.0, 0.0], 29 | shape='ellipsoid', 30 | axis_length=[0.5, 0.5, 0.5], 31 | n_nodes=n_nodes, 32 | external_force=[0.0, 0.0, 1.5], 33 | precompute_file='ellipsoidal_precompute.npz'), 34 | Body(n_nucleation_sites=0, 35 | position=[5.0, 0.0, 0.0], 36 | shape='sphere', 37 | radius=0.5, 38 | n_nodes=n_nodes, 39 | external_force=[0.0, 0.0, 1.5], 40 | precompute_file='sphere_precompute.npz'), 41 | ] 42 | 43 | config.save(path / config_file) 44 | return True 45 | 46 | def compare_quadrature(path: Path=Path('.')): 47 | print("Comparing quadrature between a sphere and a spherical ellipsoid (also sphere)") 48 | with working_directory(path): 49 | # Get the precompute data to compare the quadrature, etc 50 | precompute_ellipsoid = np.load("ellipsoidal_precompute.npz") 51 | precompute_sphere = np.load("sphere_precompute.npz") 52 | 53 | # Compare node weights 54 | weights_compare = np.allclose(precompute_ellipsoid['node_weights'], precompute_sphere['node_weights'], atol=1e-12) 55 | 56 | # Compare node normals 57 | normals_compare = np.allclose(precompute_ellipsoid['node_normals_ref'], precompute_sphere['node_normals_ref'], atol=1e-12) 58 | 59 | # Compare node positions 60 | positions_compare = np.allclose(precompute_ellipsoid['node_positions_ref'], precompute_sphere['node_positions_ref'], atol=1e-12) 61 | 62 | return (weights_compare and normals_compare and positions_compare) 63 | 64 | def test_gen_config(sim_path): 65 | assert gen_config(sim_path) 66 | 67 | def test_run_precompute(sim_path): 68 | assert run_precompute(sim_path) 69 | 70 | def test_quadrature(sim_path): 71 | assert compare_quadrature(sim_path) 72 | 73 | -------------------------------------------------------------------------------- /tests/combined/bodies/test_ellipsoid_assphere_constforce.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from pathlib import Path 4 | from skelly_sim.reader import TrajectoryReader 5 | from skelly_sim.skelly_config import Config, Body 6 | from skelly_sim.testing import working_directory, run_sim, run_precompute, sim_path 7 | 8 | np.random.seed(100) 9 | #n_nodes = 2000 10 | n_nodes = 800 11 | config_file = 'skelly_config.toml' 12 | 13 | def gen_config(path: Path=('.')): 14 | print("Generating config") 15 | # create a config object and set the system parameters 16 | config = Config() 17 | config.params.eta = 0.9 18 | config.params.dt_initial = 1E-1 19 | config.params.dt_min = 1E-4 20 | config.params.dt_max = 1E-1 21 | config.params.dt_write = 1E-1 22 | config.params.t_final = 1.0 23 | config.params.gmres_tol = 1E-10 24 | config.params.seed = 130319 25 | config.params.pair_evaluator = "CPU" 26 | 27 | config.bodies = [ 28 | Body(n_nucleation_sites=0, 29 | position=[0.0, 0.0, 0.0], 30 | shape='ellipsoid', 31 | axis_length=[0.5, 0.5, 0.5], 32 | n_nodes=n_nodes, 33 | external_force=[0.0, 0.0, 1.5]) 34 | ] 35 | 36 | config.save(path / config_file) 37 | return True 38 | 39 | def velocity(path: Path=Path('.')): 40 | print("Running analysis") 41 | with working_directory(path): 42 | traj = TrajectoryReader(config_file) 43 | 44 | # need beginning/end positions to calculate average velocity 45 | traj.load_frame(0) 46 | z_initial = traj['bodies'][0]['position_'][2] 47 | v_initial = traj['bodies'][0]['solution_vec_'][-4] 48 | traj.load_frame(len(traj) - 1) 49 | z_final = traj['bodies'][0]['position_'][2] 50 | v_final = traj['bodies'][0]['solution_vec_'][-4] 51 | 52 | dt = traj.times[-1] - traj.times[0] 53 | 54 | # We need the hydrodynamic radius, which is slightly different than the supplied 'attachment' 55 | # radius. let's just grab it from the precompute data. 56 | precompute_data = np.load(traj.config_data['bodies'][0]['precompute_file']) 57 | 58 | # Find the radius from all of the node_positions_ref, and look for the max (should be around 0.4) 59 | radii = np.zeros(len(precompute_data['node_positions_ref'])) 60 | for idx in range(len(radii)): 61 | radii[idx] = np.linalg.norm(precompute_data['node_positions_ref'][idx,:]) 62 | radius = np.mean(radii) 63 | print("Effective radius XY: {}".format(radius)) 64 | eta = traj.config_data['params']['eta'] 65 | force = traj.config_data['bodies'][0]['external_force'][2] 66 | 67 | v_theoretical = force / (6 * np.pi * eta * radius) 68 | v_measured = (z_final - z_initial) / dt 69 | error = abs(1 - v_measured / v_theoretical) 70 | 71 | print("Measured velocity: {}".format(v_measured)) 72 | print("v_initial: {}".format(v_initial)) 73 | print("v_final: {}".format(v_final)) 74 | print("Theoretical velocity: {}".format(v_theoretical)) 75 | print("Error |1 - v/v0|: {}".format(error)) 76 | 77 | return error 78 | 79 | 80 | def test_gen_config(sim_path): 81 | assert gen_config(sim_path) 82 | 83 | def test_run_precompute(sim_path): 84 | assert run_precompute(sim_path) 85 | 86 | def test_run_sim(sim_path): 87 | assert run_sim(sim_path) 88 | 89 | def test_velocity(sim_path): 90 | assert velocity(sim_path) < 1E-6 91 | -------------------------------------------------------------------------------- /tests/combined/bodies/test_ellipsoid_prolate_z_torque.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from pathlib import Path 4 | from skelly_sim.reader import TrajectoryReader 5 | from skelly_sim.skelly_config import Config, Body 6 | from skelly_sim.testing import working_directory, run_sim, run_precompute, sim_path 7 | 8 | np.random.seed(100) 9 | n_nodes = 1000 10 | config_file = 'skelly_config.toml' 11 | 12 | # This test is based off of Kim and Karilla, pg. 64 equations for a prolate spheriod under torque 13 | # Note that the torque is about the main symmetry axis (rotating in this case around z) 14 | 15 | def gen_config(path: Path=('.')): 16 | print("Generating config") 17 | # create a config object and set the system parameters 18 | config = Config() 19 | config.params.eta = 0.9 20 | config.params.dt_initial = 1E-1 21 | config.params.dt_min = 1E-4 22 | config.params.dt_max = 1E-1 23 | config.params.dt_write = 1E-1 24 | config.params.t_final = 1.5 25 | config.params.gmres_tol = 1E-10 26 | config.params.seed = 130319 27 | config.params.pair_evaluator = "CPU" 28 | 29 | # Generate an axis length for a,b,c to get a 3:1 ratio, accounting for the shifts in radii 30 | body_a = 1.1 31 | body_b = 1.1 32 | body_c = 3.2 33 | 34 | config.bodies = [ 35 | Body(n_nucleation_sites=0, 36 | position=[0.0, 0.0, 0.0], 37 | shape='ellipsoid', 38 | axis_length=[body_a, body_b, body_c], 39 | n_nodes=n_nodes, 40 | external_torque=[0.0, 0.0, 0.6]) 41 | ] 42 | 43 | config.save(path / config_file) 44 | return True 45 | 46 | def angular_velocity(path: Path=Path('.')): 47 | print("Running analysis") 48 | with working_directory(path): 49 | traj = TrajectoryReader(config_file) 50 | 51 | # need beginning/end positions to calculate average velocity 52 | traj.load_frame(0) 53 | omega_initial = traj['bodies'][0]['solution_vec_'][-3:] 54 | traj.load_frame(len(traj) - 1) 55 | omega_final = traj['bodies'][0]['solution_vec_'][-3:] 56 | 57 | # Use the same radii as above, minus the correction factors 58 | a = 3.0 59 | c = 1.0 60 | eccentricity = np.sqrt(a**2 - c**2)/a 61 | print("Eccentricity: {}".format(eccentricity)) 62 | eta = traj.config_data['params']['eta'] 63 | torque = np.linalg.norm(traj.config_data['bodies'][0]['external_torque']) 64 | 65 | # Calculate 66 | oneme2 = (1.0 - np.power(eccentricity,2)) 67 | Le = np.log((1.0+eccentricity)/(1.0-eccentricity)) 68 | Xc = (4./3.)*np.power(eccentricity,3)*(oneme2) / (2*eccentricity - oneme2*Le) 69 | 70 | w_theoretical = torque / (8. * np.pi * eta * a**3 * Xc) 71 | w_initial = omega_initial[2] 72 | 73 | error = abs(1 - w_initial / w_theoretical) 74 | 75 | print("Measured angular velocity: {}".format(w_initial)) 76 | print("Theoretical angular velocity {}".format(w_theoretical)) 77 | print("Error |1 - w/w0|: {}".format(error)) 78 | 79 | return error 80 | 81 | 82 | def test_gen_config(sim_path): 83 | assert gen_config(sim_path) 84 | 85 | def test_run_precompute(sim_path): 86 | assert run_precompute(sim_path) 87 | 88 | def test_run_sim(sim_path): 89 | assert run_sim(sim_path) 90 | 91 | def test_angular_velocity(sim_path): 92 | assert angular_velocity(sim_path) < 1E-5 93 | -------------------------------------------------------------------------------- /tests/combined/regression_tests/fdfiber_compression_data.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/tests/combined/regression_tests/fdfiber_compression_data.npy -------------------------------------------------------------------------------- /tests/combined/regression_tests/fdfiber_compression_finalpositions.npz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/tests/combined/regression_tests/fdfiber_compression_finalpositions.npz -------------------------------------------------------------------------------- /tests/combined/test_body_const_force.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from pathlib import Path 4 | from skelly_sim.reader import TrajectoryReader 5 | from skelly_sim.skelly_config import Config, Body 6 | from skelly_sim.testing import working_directory, run_sim, run_precompute, sim_path 7 | 8 | np.random.seed(100) 9 | n_nodes = 800 10 | config_file = 'skelly_config.toml' 11 | 12 | def gen_config(path: Path=('.')): 13 | print("Generating config") 14 | # create a config object and set the system parameters 15 | config = Config() 16 | config.params.eta = 0.9 17 | config.params.dt_initial = 1E-1 18 | config.params.dt_min = 1E-4 19 | config.params.dt_max = 1E-1 20 | config.params.dt_write = 1E-1 21 | config.params.t_final = 1.0 22 | config.params.gmres_tol = 1E-10 23 | config.params.seed = 130319 24 | config.params.pair_evaluator = "CPU" 25 | 26 | config.bodies = [ 27 | Body(n_nucleation_sites=0, 28 | position=[0.0, 0.0, 0.0], 29 | shape='sphere', 30 | radius=0.5, 31 | n_nodes=n_nodes, 32 | external_force=[0.0, 0.0, 1.5]) 33 | ] 34 | 35 | config.save(path / config_file) 36 | return True 37 | 38 | def velocity(path: Path=Path('.')): 39 | print("Running analysis") 40 | with working_directory(path): 41 | traj = TrajectoryReader(config_file) 42 | 43 | # need beginning/end positions to calculate average velocity 44 | traj.load_frame(0) 45 | z_initial = traj['bodies'][0]['position_'][2] 46 | traj.load_frame(len(traj) - 1) 47 | z_final = traj['bodies'][0]['position_'][2] 48 | 49 | dt = traj.times[-1] - traj.times[0] 50 | 51 | # We need the hydrodynamic radius, which is slightly different than the supplied 'attachment' 52 | # radius. let's just grab it from the precompute data. 53 | precompute_data = np.load(traj.config_data['bodies'][0]['precompute_file']) 54 | radius = np.linalg.norm(precompute_data["node_positions_ref"][0]) 55 | eta = traj.config_data['params']['eta'] 56 | force = traj.config_data['bodies'][0]['external_force'][2] 57 | 58 | print("Effective radius: {}".format(radius)) 59 | 60 | v_theoretical = force / (6 * np.pi * eta * radius) 61 | v_measured = (z_final - z_initial) / dt 62 | error = abs(1 - v_measured / v_theoretical) 63 | 64 | print("Measured velocity: {}".format(v_measured)) 65 | print("Theoretical velocity: {}".format(v_theoretical)) 66 | print("Error |1 - v/v0|: {}".format(error)) 67 | 68 | return error 69 | 70 | 71 | def test_gen_config(sim_path): 72 | assert gen_config(sim_path) 73 | 74 | def test_run_precompute(sim_path): 75 | assert run_precompute(sim_path) 76 | 77 | def test_run_sim(sim_path): 78 | assert run_sim(sim_path) 79 | 80 | def test_velocity(sim_path): 81 | assert velocity(sim_path) < 1E-6 82 | -------------------------------------------------------------------------------- /tests/combined/test_body_const_torque.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from pathlib import Path 4 | from skelly_sim.reader import TrajectoryReader 5 | from skelly_sim.skelly_config import Config, Body 6 | from skelly_sim.testing import working_directory, run_sim, run_precompute, sim_path 7 | 8 | np.random.seed(100) 9 | n_nodes = 800 10 | config_file = 'skelly_config.toml' 11 | 12 | def gen_config(path): 13 | print("Generating config") 14 | # create a config object and set the system parameters 15 | config = Config() 16 | config.params.eta = 0.9 17 | config.params.dt_initial = 1E-1 18 | config.params.dt_min = 1E-4 19 | config.params.dt_max = 1E-1 20 | config.params.dt_write = 1E-1 21 | config.params.t_final = 1.0 22 | config.params.gmres_tol = 1E-10 23 | config.params.seed = 130319 24 | config.params.pair_evaluator = "CPU" 25 | 26 | config.bodies = [ 27 | Body(n_nucleation_sites=0, 28 | position=[0.0, 0.0, 0.0], 29 | shape='sphere', 30 | radius=0.5, 31 | n_nodes=n_nodes, 32 | external_force=[0.0, 0.0, 0.0], 33 | external_torque=[0.0, 0.0, 1.2] 34 | ) 35 | ] 36 | 37 | config.save(path / config_file) 38 | return True 39 | 40 | def angular_velocity(path: Path=Path('.')): 41 | print("Running analysis") 42 | with working_directory(path): 43 | traj = TrajectoryReader(config_file) 44 | 45 | # need beginning/end positions to calculate average velocity 46 | traj.load_frame(0) 47 | Theta_initial = traj['bodies'][0]['orientation_'] 48 | traj.load_frame(len(traj) - 1) 49 | Theta_final = traj['bodies'][0]['orientation_'] 50 | 51 | precompute_data = np.load(traj.config_data['bodies'][0]['precompute_file']) 52 | radius = np.linalg.norm(precompute_data["node_positions_ref"][0]) 53 | eta = traj.config_data['params']['eta'] 54 | torque = np.linalg.norm(traj.config_data['bodies'][0]['external_torque']) 55 | dt = traj.times[-1] - traj.times[0] 56 | 57 | w_theoretical = torque / (8 * np.pi * eta * radius**3) 58 | w_measured = 2 * np.arccos(np.dot(Theta_initial, Theta_final)) / dt 59 | 60 | return np.abs(1 - w_measured / w_theoretical) 61 | 62 | 63 | def test_gen_config(sim_path): 64 | assert gen_config(sim_path) 65 | 66 | def test_run_precompute(sim_path): 67 | assert run_precompute(sim_path) 68 | 69 | def test_run_sim(sim_path): 70 | assert run_sim(sim_path) 71 | 72 | def test_angular_velocity(sim_path): 73 | assert angular_velocity(sim_path) < 1E-6 74 | -------------------------------------------------------------------------------- /tests/combined/test_body_oscillatory_force.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from pathlib import Path 4 | from skelly_sim.reader import TrajectoryReader 5 | from skelly_sim.skelly_config import ConfigSpherical, Body 6 | from skelly_sim.testing import working_directory, run_sim, run_precompute, sim_path 7 | 8 | np.random.seed(100) 9 | config_file = 'skelly_config.toml' 10 | 11 | def gen_config(path: Path=('.')): 12 | print("Generating config") 13 | # create a config object and set the system parameters 14 | config = ConfigSpherical() 15 | config.params.dt_initial = 5E-2 16 | config.params.dt_min = 1E-4 17 | config.params.dt_max = 5E-2 18 | config.params.dt_write = 5E-2 19 | config.params.t_final = 10.0 20 | config.params.gmres_tol = 1E-10 21 | config.params.seed = 130319 22 | config.params.pair_evaluator = "CPU" 23 | 24 | config.bodies = [ 25 | Body(n_nucleation_sites=0, 26 | position=[0.0, 0.0, 0.0], 27 | shape='sphere', 28 | radius=0.5, 29 | n_nodes=400, 30 | external_force_type='Oscillatory', 31 | external_force=[0.0, 0.0, 1.0], 32 | external_oscillation_force_amplitude=2.0, 33 | external_oscillation_force_frequency=0.1, 34 | external_oscillation_force_phase=0.0) 35 | ] 36 | 37 | config.periphery.n_nodes = 500 38 | config.periphery.radius = 4.0/1.04 39 | 40 | config.save(path / config_file) 41 | return True 42 | 43 | def z_final_position(path: Path=Path('.')): 44 | print("Running analysis") 45 | with working_directory(path): 46 | traj = TrajectoryReader(config_file) 47 | 48 | # Get start/end positions 49 | traj.load_frame(0) 50 | z_initial = traj['bodies'][0]['position_'][2] 51 | traj.load_frame(len(traj) - 1) 52 | z_final = traj['bodies'][0]['position_'][2] 53 | 54 | # Since this is an oscillatory force, check where the sphere returned to 55 | z_final_theoretical = 6.215481243240294E-05 56 | error = abs(1 - z_final / z_final_theoretical) 57 | print(f"Measured final position: {z_final}") 58 | print(f"Theoretical final position: {z_final_theoretical}") 59 | print(f"Error: {z_final}") 60 | 61 | return error 62 | 63 | 64 | def test_gen_config(sim_path): 65 | assert gen_config(sim_path) 66 | 67 | def test_run_precompute(sim_path): 68 | assert run_precompute(sim_path) 69 | 70 | def test_run_sim(sim_path): 71 | assert run_sim(sim_path) 72 | 73 | def test_velocity(sim_path): 74 | assert z_final_position(sim_path) < 1E-6 75 | -------------------------------------------------------------------------------- /tests/combined/test_clamped_buckling_sigma72.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.signal import find_peaks 3 | 4 | from pathlib import Path 5 | 6 | from skelly_sim.reader import TrajectoryReader 7 | from skelly_sim.skelly_config import Config, Fiber, Point 8 | from skelly_sim.testing import working_directory, sim_path, run_sim 9 | 10 | np.random.seed(100) 11 | config_file = 'skelly_config.toml' 12 | 13 | def gen_config(path: Path): 14 | print("Generating config") 15 | # create a config object and set the system parameters 16 | config = Config() 17 | config.params.eta = 1.0 18 | config.params.dt_initial = 0.02 19 | config.params.dt_min = 0.01 20 | config.params.dt_max = 0.1 21 | config.params.dt_write = 0.1 22 | config.params.t_final = 50.0 23 | config.params.gmres_tol = 1E-10 24 | config.params.seed = 130319 25 | config.params.pair_evaluator = "CPU" 26 | config.params.adaptive_timestep_flag = True 27 | 28 | sigma = 72.0 29 | length = 1.0 30 | bending_rigidity = 0.0025 31 | force_scale = -sigma * bending_rigidity / length**3 32 | n_nodes = 32 33 | 34 | config.fibers = [Fiber( 35 | force_scale=force_scale, 36 | length=length, 37 | n_nodes=n_nodes, 38 | bending_rigidity=bending_rigidity, 39 | minus_clamped=True 40 | )] 41 | 42 | # Give a small starting kick 43 | config.point_sources = [ 44 | Point( 45 | position=[0.0, 0.0, 10*length], 46 | force=[10.0, 0.0, 0.0], 47 | time_to_live=1.0, 48 | ) 49 | ] 50 | 51 | # Orient in Z 52 | config.fibers[0].x = np.linspace([0, 0, 0], [0, 0, length], n_nodes).ravel().tolist() 53 | 54 | config.save(path / config_file) 55 | 56 | return True 57 | 58 | def is_decaying(path: Path=Path('.')): 59 | with working_directory(path): 60 | traj = TrajectoryReader('skelly_config.toml') 61 | 62 | x = [] 63 | for i in range(len(traj)): 64 | traj.load_frame(i) 65 | x.append(traj['fibers'][0]['x_'][-1, 0]) 66 | 67 | x = np.array(x) 68 | mpeaks, _ = find_peaks(x, height=0) 69 | 70 | # Check the peaks, ignoring the first one from the original 'kick' to the system 71 | x_peak1 = x[mpeaks[1]] 72 | x_peak2 = x[mpeaks[2]] 73 | print(f"peak 1 x: {x_peak1}") 74 | print(f"peak 2 x: {x_peak2}") 75 | 76 | # Just return if the solution is decaying at this timescale 77 | return x_peak2 < x_peak1 78 | 79 | def compare_previous_peaks(path: Path=Path('.')): 80 | print(f"Comparing deflection to previous results") 81 | with working_directory(path): 82 | traj = TrajectoryReader('skelly_config.toml') 83 | 84 | x = [] 85 | for i in range(len(traj)): 86 | traj.load_frame(i) 87 | x.append(traj['fibers'][0]['x_'][-1, 0]) 88 | 89 | x = np.array(x) 90 | mpeaks, _ = find_peaks(x, height=0) 91 | 92 | # Check the peaks, ignoring the first one from the original 'kick' to the system, compared to previous results 93 | x_peak1 = x[mpeaks[1]] 94 | x_peak2 = x[mpeaks[2]] 95 | 96 | x_peak1_test = 0.08844356 97 | x_peak2_test = 0.05563314 98 | 99 | rel_error_1 = abs(1 - x_peak1/x_peak1_test) 100 | rel_error_2 = abs(1 - x_peak2/x_peak2_test) 101 | rel_error = np.sqrt(np.power(rel_error_1, 2) + np.power(rel_error_2, 2)) 102 | 103 | print(f"peak 1 x: {x_peak1}") 104 | print(f"peak 2 x: {x_peak2}") 105 | print(f"relative error peak 1: {rel_error_1}") 106 | print(f"relative error peak 2: {rel_error_2}") 107 | print(f"relative error: {rel_error}") 108 | 109 | return rel_error 110 | 111 | def test_gen_config(sim_path): 112 | assert gen_config(sim_path) 113 | 114 | def test_run_sim(sim_path): 115 | assert run_sim(sim_path) 116 | 117 | def test_is_decaying(sim_path): 118 | assert is_decaying(sim_path) 119 | 120 | def test_compare_previous_peaks(sim_path): 121 | assert compare_previous_peaks(sim_path) < 1E-6 122 | 123 | -------------------------------------------------------------------------------- /tests/combined/test_clamped_buckling_sigma80.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.signal import find_peaks 3 | 4 | from pathlib import Path 5 | 6 | from skelly_sim.reader import TrajectoryReader 7 | from skelly_sim.skelly_config import Config, Fiber, Point 8 | from skelly_sim.testing import working_directory, sim_path, run_sim 9 | 10 | np.random.seed(100) 11 | config_file = 'skelly_config.toml' 12 | 13 | def gen_config(path: Path): 14 | print("Generating config") 15 | # create a config object and set the system parameters 16 | config = Config() 17 | config.params.eta = 1.0 18 | config.params.dt_initial = 0.02 19 | config.params.dt_min = 0.01 20 | config.params.dt_max = 0.1 21 | config.params.dt_write = 0.1 22 | config.params.t_final = 50.0 23 | config.params.gmres_tol = 1E-10 24 | config.params.seed = 130319 25 | config.params.pair_evaluator = "CPU" 26 | config.params.adaptive_timestep_flag = True 27 | 28 | sigma = 80.0 29 | length = 1.0 30 | bending_rigidity = 0.0025 31 | force_scale = -sigma * bending_rigidity / length**3 32 | n_nodes = 32 33 | 34 | config.fibers = [Fiber( 35 | force_scale=force_scale, 36 | length=length, 37 | n_nodes=n_nodes, 38 | bending_rigidity=bending_rigidity, 39 | minus_clamped=True 40 | )] 41 | 42 | # Give a small starting kick 43 | config.point_sources = [ 44 | Point( 45 | position=[0.0, 0.0, 10*length], 46 | force=[10.0, 0.0, 0.0], 47 | time_to_live=1.0, 48 | ) 49 | ] 50 | 51 | # Orient in Z 52 | config.fibers[0].x = np.linspace([0, 0, 0], [0, 0, length], n_nodes).ravel().tolist() 53 | 54 | config.save(path / config_file) 55 | 56 | return True 57 | 58 | def is_decaying(path: Path=Path('.')): 59 | with working_directory(path): 60 | traj = TrajectoryReader('skelly_config.toml') 61 | 62 | x = [] 63 | for i in range(len(traj)): 64 | traj.load_frame(i) 65 | x.append(traj['fibers'][0]['x_'][-1, 0]) 66 | 67 | x = np.array(x) 68 | mpeaks, _ = find_peaks(x, height=0) 69 | 70 | # Check the peaks, ignoring the first one from the original 'kick' to the system 71 | x_peak1 = x[mpeaks[1]] 72 | x_peak2 = x[mpeaks[2]] 73 | print(f"peak 1 x: {x_peak1}") 74 | print(f"peak 2 x: {x_peak2}") 75 | 76 | # Just return if the solution is decaying at this timescale 77 | return x_peak2 < x_peak1 78 | 79 | def compare_previous_peaks(path: Path=Path('.')): 80 | print(f"Comparing deflection to previous results") 81 | with working_directory(path): 82 | traj = TrajectoryReader('skelly_config.toml') 83 | 84 | x = [] 85 | for i in range(len(traj)): 86 | traj.load_frame(i) 87 | x.append(traj['fibers'][0]['x_'][-1, 0]) 88 | 89 | x = np.array(x) 90 | mpeaks, _ = find_peaks(x, height=0) 91 | 92 | # Check the peaks, ignoring the first one from the original 'kick' to the system, compared to previous results 93 | x_peak1 = x[mpeaks[1]] 94 | x_peak2 = x[mpeaks[2]] 95 | 96 | x_peak1_test = 0.09575812 97 | x_peak2_test = 0.13564472 98 | 99 | rel_error_1 = abs(1 - x_peak1/x_peak1_test) 100 | rel_error_2 = abs(1 - x_peak2/x_peak2_test) 101 | rel_error = np.sqrt(np.power(rel_error_1, 2) + np.power(rel_error_2, 2)) 102 | 103 | print(f"peak 1 x: {x_peak1}") 104 | print(f"peak 2 x: {x_peak2}") 105 | print(f"relative error peak 1: {rel_error_1}") 106 | print(f"relative error peak 2: {rel_error_2}") 107 | print(f"relative error: {rel_error}") 108 | 109 | return rel_error 110 | 111 | def test_gen_config(sim_path): 112 | assert gen_config(sim_path) 113 | 114 | def test_run_sim(sim_path): 115 | assert run_sim(sim_path) 116 | 117 | def test_is_decaying(sim_path): 118 | assert not is_decaying(sim_path) 119 | 120 | def test_compare_previous_peaks(sim_path): 121 | assert compare_previous_peaks(sim_path) < 1E-6 122 | 123 | -------------------------------------------------------------------------------- /tests/combined/test_fiber_const_force.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from pathlib import Path 4 | 5 | from skelly_sim.reader import TrajectoryReader 6 | from skelly_sim.skelly_config import Config, Fiber 7 | from skelly_sim.testing import working_directory, sim_path, run_sim 8 | 9 | np.random.seed(100) 10 | config_file = 'skelly_config.toml' 11 | 12 | def gen_config(path: Path): 13 | print("Generating config") 14 | # create a config object and set the system parameters 15 | config = Config() 16 | config.params.eta = 0.7 17 | config.params.dt_initial = 1E-4 18 | config.params.dt_min = 1E-4 19 | config.params.dt_max = 1E-4 20 | config.params.dt_write = 1E-3 21 | config.params.t_final = 1E-2 22 | config.params.gmres_tol = 1E-10 23 | config.params.seed = 130319 24 | config.params.pair_evaluator = "CPU" 25 | 26 | length = 0.75 27 | config.fibers = [Fiber( 28 | force_scale=0.31, 29 | length=length, 30 | n_nodes=8, 31 | bending_rigidity=0.0025 32 | )] 33 | config.fibers[0].fill_node_positions(np.array([0.0, 0.0, 0.0]), np.array([0.0, 0.0, 1.0])) 34 | 35 | config.save(path / config_file) 36 | 37 | return True 38 | 39 | 40 | def velocity(path: Path=Path('.')): 41 | print("Comparing velocity to analytic result") 42 | with working_directory(path): 43 | traj = TrajectoryReader('skelly_config.toml') 44 | 45 | traj.load_frame(0) 46 | x0 = traj['fibers'][0]['x_'][0, :] 47 | traj.load_frame(-1) 48 | xf = traj['fibers'][0]['x_'][0, :] 49 | 50 | dt = traj.times[-1] - traj.times[0] 51 | v = (xf - x0) / dt 52 | 53 | fib = traj.config_data['fibers'][0] 54 | length = fib['length'] 55 | radius = fib['radius'] 56 | 57 | epsilon = radius / length 58 | 59 | gamma = fib["force_scale"] * length / v[-1] 60 | gamma_theory = -4 * np.pi * length * traj.config_data['params']['eta'] / (np.log(np.exp(1) * epsilon**2)) 61 | rel_error = abs(1 - gamma/gamma_theory) 62 | 63 | print("theoretical drag: {}".format(gamma_theory)) 64 | print("measured drag: {}".format(gamma)) 65 | print("relative error: {}".format(rel_error)) 66 | 67 | return rel_error 68 | 69 | 70 | def test_gen_config(sim_path): 71 | assert gen_config(sim_path) 72 | 73 | def test_run_sim(sim_path): 74 | assert run_sim(sim_path) 75 | 76 | def test_velocity(sim_path): 77 | assert velocity(sim_path) < 1E-6 78 | -------------------------------------------------------------------------------- /tests/combined/test_fiber_dualfilament.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from pathlib import Path 4 | 5 | from skelly_sim.reader import TrajectoryReader 6 | from skelly_sim.skelly_config import Config, Fiber, perturbed_fiber_positions 7 | from skelly_sim.testing import working_directory, sim_path, run_sim 8 | 9 | np.random.seed(100) 10 | config_file = 'skelly_config.toml' 11 | 12 | def gen_config(path: Path): 13 | print("Generating config") 14 | # create a config object and set the system parameters 15 | config = Config() 16 | config.params.eta = 1.0 17 | config.params.dt_initial = 1E-1 18 | config.params.dt_write = 1.0 19 | config.params.t_final = 1E1 20 | config.params.gmres_tol = 1E-10 21 | config.params.seed = 130319 22 | config.params.pair_evaluator = "CPU" 23 | config.params.adaptive_timestep_flag = False 24 | 25 | sigma = 0.0225 26 | length = 2.0 27 | bending_rigidity = 0.0025 28 | n_nodes = 64 29 | 30 | n_fibers = 2 31 | 32 | config.fibers = [Fiber( 33 | force_scale=-sigma, 34 | length=length, 35 | n_nodes=n_nodes, 36 | bending_rigidity=bending_rigidity, 37 | minus_clamped=True 38 | ) for i in range(n_fibers)] 39 | 40 | # Disrupt the first fiber only 41 | x = perturbed_fiber_positions(0.01, length, np.array([0.0, 0.0, 0.0]), np.array([0.0, 0.0, 1.0]), n_nodes, np.array([1.0, 0.0, 0.0])) 42 | config.fibers[0].x = x.ravel().tolist() 43 | config.fibers[1].fill_node_positions(np.array([1.0, 0.0, 0.0]), np.array([0.0, 0.0, 1.0])) 44 | 45 | config.save(path / config_file) 46 | 47 | return True 48 | 49 | 50 | def deflection(path: Path=Path('.')): 51 | print(f"Comparing deflection to previous results") 52 | with working_directory(path): 53 | traj = TrajectoryReader('skelly_config.toml') 54 | 55 | traj.load_frame(-1) 56 | x0 = traj['fibers'][0]['x_'][-1,0] 57 | x1 = traj['fibers'][1]['x_'][-1,0] 58 | 59 | # The deflection of the fiber(s) from previous results with these parameter choices 60 | x0_test = -0.004765810967995735 61 | x1_test = 1.0048647877439878 62 | 63 | rel_error_0 = abs(1 - x0/x0_test) 64 | rel_error_1 = abs(1 - x1/x1_test) 65 | rel_error = np.sqrt(np.power(rel_error_0, 2) + np.power(rel_error_1, 2)) 66 | 67 | print(f"x0: {x0}") 68 | print(f"x1: {x1}") 69 | print(f"relative error x0 (driver): {rel_error_0}") 70 | print(f"relative error x1 (response): {rel_error_1}") 71 | print(f"relative error: {rel_error}") 72 | 73 | return rel_error 74 | 75 | 76 | def test_gen_config(sim_path): 77 | assert gen_config(sim_path) 78 | 79 | def test_run_sim(sim_path): 80 | assert run_sim(sim_path) 81 | 82 | def test_deflection(sim_path): 83 | assert deflection(sim_path) < 1E-6 84 | -------------------------------------------------------------------------------- /tests/combined/test_fiber_uniform_background.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from pathlib import Path 4 | 5 | from skelly_sim.reader import TrajectoryReader 6 | from skelly_sim.skelly_config import Config, Fiber 7 | from skelly_sim.testing import working_directory, sim_path, run_sim 8 | 9 | np.random.seed(100) 10 | config_file = 'skelly_config.toml' 11 | 12 | def gen_config(path: Path): 13 | print("Generating config") 14 | # create a config object and set the system parameters 15 | config = Config() 16 | config.params.eta = 0.7 17 | config.params.dt_initial = 1E-4 18 | config.params.dt_min = 1E-4 19 | config.params.dt_max = 1E-4 20 | config.params.dt_write = 1E-3 21 | config.params.t_final = 1E-2 22 | config.params.gmres_tol = 1E-10 23 | config.params.seed = 130319 24 | config.params.pair_evaluator = "CPU" 25 | 26 | length = 0.75 27 | config.fibers = [Fiber( 28 | length=length, 29 | n_nodes=8, 30 | bending_rigidity=0.0025 31 | )] 32 | config.fibers[0].fill_node_positions(np.array([0.0, 0.0, 0.0]), np.array([0.0, 0.0, 1.0])) 33 | 34 | config.background.uniform = [1.0, 2.0, 3.0] 35 | 36 | config.save(path / config_file) 37 | 38 | return True 39 | 40 | 41 | def velocity(path: Path=Path('.')): 42 | print("Comparing velocity to analytic result") 43 | with working_directory(path): 44 | traj = TrajectoryReader('skelly_config.toml') 45 | 46 | traj.load_frame(0) 47 | x0 = traj['fibers'][0]['x_'][0, :] 48 | traj.load_frame(-1) 49 | xf = traj['fibers'][0]['x_'][0, :] 50 | 51 | dt = traj.times[-1] - traj.times[0] 52 | v = np.linalg.norm((xf - x0) / dt) 53 | 54 | velocity_meas = v 55 | velocity_theory = np.linalg.norm(np.array(traj.config_data['background']['uniform'])) 56 | rel_error = 1 - velocity_meas/velocity_theory 57 | 58 | return rel_error 59 | 60 | 61 | def test_gen_config(sim_path): 62 | assert gen_config(sim_path) 63 | 64 | def test_run_sim(sim_path): 65 | assert run_sim(sim_path) 66 | 67 | def test_velocity(sim_path): 68 | assert velocity(sim_path) < 1E-13 69 | -------------------------------------------------------------------------------- /tests/core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | file(GLOB test_src "*.cpp") 3 | 4 | foreach(srcfile ${test_src}) 5 | string(REPLACE ".cpp" "" executable ${srcfile}) 6 | get_filename_component(executable ${executable} NAME) 7 | add_executable(${executable} ${srcfile}) 8 | target_link_libraries(${executable} PUBLIC skelly z MPI::MPI_CXX trng4_static 9 | ${Kokkos_LIBRARIES} ${Tpetra_LIBRARIES} ${Teuchos_LIBRARIES} ${Belos_LIBRARIES}) 10 | endforeach() 11 | 12 | foreach(kernel stokeslet;stresslet) 13 | foreach(driver single;openmp;gpu;fmm) 14 | add_test(NAME ${kernel}_${driver} COMMAND kernel_test --kernel=${kernel} --driver=${driver}) 15 | endforeach() 16 | endforeach() 17 | 18 | ############################################################################### 19 | # Fetch google test 20 | ############################################################################### 21 | set(INSTALL_GTEST OFF) 22 | include(FetchContent) 23 | FetchContent_Declare( 24 | googletest 25 | GIT_REPOSITORY https://github.com/google/googletest.git 26 | GIT_TAG release-1.12.1 27 | ) 28 | 29 | # For Windows: Prevent overriding the parent project's compiler/linker settings 30 | # https://chromium.googlesource.com/external/github.com/pwnall/googletest/+/HEAD/googletest/README.md 31 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 32 | FetchContent_MakeAvailable(googletest) 33 | 34 | # Create the test files directory 35 | set(TEST_FILES_DIR ${CMAKE_SOURCE_DIR}/tests/core/test_files) 36 | 37 | add_subdirectory(unit_tests) 38 | -------------------------------------------------------------------------------- /tests/core/dynamic_instability_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | int main(int argc, char *argv[]) { 19 | int thread_level; 20 | MPI_Init_thread(&argc, &argv, MPI_THREAD_FUNNELED, &thread_level); 21 | int rank; 22 | MPI_Comm_rank(MPI_COMM_WORLD, &rank); 23 | 24 | std::string config_file = "skelly_config.toml"; 25 | Teuchos::CommandLineProcessor cmdp(false, true); 26 | cmdp.setOption("config-file", &config_file, "TOML input file."); 27 | if (cmdp.parse(argc, argv) != Teuchos::CommandLineProcessor::PARSE_SUCCESSFUL) { 28 | MPI_Finalize(); 29 | return EXIT_FAILURE; 30 | } 31 | 32 | try { 33 | System::init(config_file, false, false); 34 | auto params = System::get_params(); 35 | auto &properties = System::get_properties(); 36 | double t_final = params->t_final; 37 | 38 | // Only used with finite difference fibers (for now) 39 | auto &fc = *System::get_fiber_container(); 40 | if (fc.fiber_type_ != FiberContainerBase::FIBERTYPE::FiniteDifference) { 41 | throw std::runtime_error("dynamic_instability_test only compatible with FiniteDifferenceFiber(s) for now"); 42 | } 43 | FiberContainerFiniteDifference *fc_fd = static_cast(&fc); 44 | 45 | std::vector times; 46 | std::vector n_fibers; 47 | std::vector lengths; 48 | 49 | double t = 0.0; 50 | while (t < t_final) { 51 | times.push_back(t); 52 | n_fibers.push_back(fc_fd->get_global_fiber_count()); 53 | double length = 54 | std::accumulate(fc_fd->fibers_.begin(), fc_fd->fibers_.end(), 0.0, 55 | [](const double &a, const FiberFiniteDifference &b) { return a + b.length_; }); 56 | double length_tot; 57 | MPI_Reduce(&length, &length_tot, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); 58 | 59 | lengths.push_back(length_tot / n_fibers.back()); 60 | System::dynamic_instability(); 61 | t += properties.dt; 62 | } 63 | 64 | if (rank == 0) { 65 | cnpy::npz_save("di_traj.npz", "t", times.data(), {times.size()}, "w"); 66 | cnpy::npz_save("di_traj.npz", "n_fibers", n_fibers.data(), {n_fibers.size()}, "a"); 67 | cnpy::npz_save("di_traj.npz", "lengths", lengths.data(), {lengths.size()}, "a"); 68 | } 69 | } catch (const std::runtime_error &e) { 70 | // Warning: Critical only catches things on rank 0, so this may or may not print, if 71 | // some random rank throws an error. This is the same reason we use MPI_Abort: all 72 | // ranks are not guaranteed to land here, so there's only so much grace that can be 73 | // easily implemented. 74 | spdlog::critical(std::string("Fatal exception caught: ") + e.what()); 75 | MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE); 76 | } 77 | 78 | MPI_Finalize(); 79 | return EXIT_SUCCESS; 80 | } 81 | -------------------------------------------------------------------------------- /tests/core/kernel_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | using Eigen::MatrixXd; 9 | 10 | int main(int argc, char *argv[]) { 11 | int thread_level; 12 | MPI_Init_thread(&argc, &argv, MPI_THREAD_FUNNELED, &thread_level); 13 | 14 | std::string kernel, driver; 15 | 16 | Teuchos::CommandLineProcessor cmdp(false, true); 17 | cmdp.setOption("kernel", &kernel, "kernel to evaluate [stokeslet, stresslet]", true); 18 | cmdp.setOption("driver", &driver, "driver to evaluate [single, openmp, gpu, fmm]", true); 19 | 20 | if (cmdp.parse(argc, argv) != Teuchos::CommandLineProcessor::PARSE_SUCCESSFUL) { 21 | MPI_Finalize(); 22 | return EXIT_FAILURE; 23 | } 24 | 25 | constexpr int n_src = 1229; 26 | constexpr int n_trg = 743; 27 | constexpr double eta = 1.3; 28 | 29 | const int mult_order = 16; 30 | const int max_pts = 50; 31 | 32 | MatrixXd r_src = MatrixXd::Random(3, n_src); 33 | MatrixXd r_trg = MatrixXd::Random(3, n_trg); 34 | MatrixXd nullmat; 35 | double err; 36 | if (kernel == "stokeslet") { 37 | MatrixXd f_src = MatrixXd::Random(3, n_src); 38 | 39 | omp_set_num_threads(1); 40 | MatrixXd ref = kernels::stokeslet_direct_cpu(r_src, nullmat, r_trg, f_src, nullmat, eta); 41 | MatrixXd other; 42 | 43 | if (driver == "single") { 44 | omp_set_num_threads(1); 45 | other = kernels::stokeslet_direct_cpu(r_src, nullmat, r_trg, f_src, nullmat, eta); 46 | } else if (driver == "openmp") { 47 | omp_set_num_threads(4); 48 | other = kernels::stokeslet_direct_cpu(r_src, nullmat, r_trg, f_src, nullmat, eta); 49 | } else if (driver == "gpu") { 50 | other = kernels::stokeslet_direct_gpu(r_src, nullmat, r_trg, f_src, nullmat, eta); 51 | } else if (driver == "fmm") { 52 | auto stokeslet_kernel_fmm = kernels::FMM(mult_order, max_pts, stkfmm::PAXIS::NONE, 53 | stkfmm::KERNEL::Stokes, kernels::stokes_vel_fmm); 54 | other = stokeslet_kernel_fmm(r_src, nullmat, r_trg, f_src, nullmat, eta); 55 | } else { 56 | std::cerr << "Invalid driver supplied \"" + driver << "\"" << std::endl; 57 | return EXIT_FAILURE; 58 | } 59 | 60 | err = sqrt((ref - other).squaredNorm()); 61 | } else if (kernel == "stresslet") { 62 | MatrixXd f_src = MatrixXd::Random(9, n_src); 63 | 64 | omp_set_num_threads(1); 65 | MatrixXd ref = kernels::stresslet_direct_cpu(nullmat, r_src, r_trg, nullmat, f_src, eta); 66 | MatrixXd other; 67 | 68 | if (driver == "single") { 69 | omp_set_num_threads(1); 70 | other = kernels::stresslet_direct_cpu(nullmat, r_src, r_trg, nullmat, f_src, eta); 71 | } else if (driver == "openmp") { 72 | omp_set_num_threads(4); 73 | other = kernels::stresslet_direct_cpu(nullmat, r_src, r_trg, nullmat, f_src, eta); 74 | } else if (driver == "gpu") { 75 | other = kernels::stresslet_direct_gpu(nullmat, r_src, r_trg, nullmat, f_src, eta); 76 | } else if (driver == "fmm") { 77 | auto stresslet_kernel_fmm = kernels::FMM(mult_order, max_pts, stkfmm::PAXIS::NONE, 78 | stkfmm::KERNEL::PVel, kernels::stokes_pvel_fmm); 79 | other = stresslet_kernel_fmm(nullmat, r_src, r_trg, nullmat, f_src, eta); 80 | } else { 81 | std::cerr << "Invalid driver supplied \"" + driver << "\"" << std::endl; 82 | return EXIT_FAILURE; 83 | } 84 | 85 | err = sqrt((ref - other).squaredNorm()); 86 | } else { 87 | std::cerr << "Invalid kernel supplied \"" + kernel << "\"" << std::endl; 88 | return EXIT_FAILURE; 89 | } 90 | 91 | std::cout << err << std::endl; 92 | return err > 5E-9; 93 | } 94 | -------------------------------------------------------------------------------- /tests/core/solver_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | int main(int argc, char *argv[]) { 18 | int thread_level; 19 | MPI_Init_thread(&argc, &argv, MPI_THREAD_FUNNELED, &thread_level); 20 | int rank; 21 | MPI_Comm_rank(MPI_COMM_WORLD, &rank); 22 | 23 | std::string config_file = "skelly_config.toml"; 24 | Teuchos::CommandLineProcessor cmdp(false, true); 25 | cmdp.setOption("config-file", &config_file, "TOML input file."); 26 | if (cmdp.parse(argc, argv) != Teuchos::CommandLineProcessor::PARSE_SUCCESSFUL) { 27 | MPI_Finalize(); 28 | return EXIT_FAILURE; 29 | } 30 | 31 | try { 32 | System::init(config_file, false, false); 33 | auto params = System::get_params(); 34 | auto psc = System::get_point_source_container(); 35 | 36 | PointSourceContainer psc_old = *psc; 37 | 38 | System::solve(); 39 | 40 | psc->points.clear(); 41 | 42 | double radius = System::get_shell()->node_pos_.col(0).norm(); 43 | int n_nodes = System::get_shell()->n_nodes_global_; 44 | 45 | Eigen::MatrixXd r_trg = Eigen::MatrixXd::Zero(3, 100); 46 | r_trg.row(2).setLinSpaced(-radius, radius); 47 | 48 | Eigen::MatrixXd velocity_system = System::velocity_at_targets(r_trg); 49 | 50 | std::ofstream outstream("N" + std::to_string(n_nodes) + "_Rp" + std::to_string(radius) + "_Rs" + 51 | std::to_string(psc_old.points[0].position_[2]) + ".dat"); 52 | Eigen::MatrixXd velocity_points = psc_old.flow(r_trg, params->eta, 0.0); 53 | for (int i = 0; i < r_trg.cols(); ++i) 54 | outstream << r_trg(2, i) << " " 55 | << (1.0 + velocity_system.col(i).array() / velocity_points.col(i).array()).abs()[2] << " " 56 | << velocity_points.col(i).transpose()[2] << std::endl; 57 | 58 | } catch (const std::runtime_error &e) { 59 | // Warning: Critical only catches things on rank 0, so this may or may not print, if 60 | // some random rank throws an error. This is the same reason we use MPI_Abort: all 61 | // ranks are not guaranteed to land here, so there's only so much grace that can be 62 | // easily implemented. 63 | spdlog::critical(std::string("Fatal exception caught: ") + e.what()); 64 | MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE); 65 | } 66 | 67 | MPI_Finalize(); 68 | return EXIT_SUCCESS; 69 | } 70 | -------------------------------------------------------------------------------- /tests/core/test_files/body_precompute_body_ellipsoid_n1.npz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatironinstitute/SkellySim/6d54834c9488d8a357b2ebbcd9d5c6fe540cbd53/tests/core/test_files/body_precompute_body_ellipsoid_n1.npz -------------------------------------------------------------------------------- /tests/core/test_files/ellipsoidal_body_n1.toml: -------------------------------------------------------------------------------- 1 | [[bodies]] 2 | n_nucleation_sites = 0 3 | position = [ 0.0, 0.0, 0.0,] 4 | orientation = [ 0.0, 0.0, 0.0, 1.0,] 5 | shape = "ellipsoid" 6 | radius = 1.0 7 | n_nodes = 400 8 | axis_length = [ 0.4, 0.4, 0.5,] 9 | precompute_file = "body_precompute_body_ellipsoid_n1.npz" 10 | external_force_type = "Oscillatory" 11 | external_force = [ 0.0, 0.0, 1.0,] 12 | external_torque = [ 0.0, 0.0, 0.0,] 13 | external_oscillation_force_amplitude = 2.0 14 | external_oscillation_force_frequency = 1.0 15 | external_oscillation_force_phase = 0.0 16 | 17 | [params] 18 | eta = 1.0 19 | dt_initial = 0.05 20 | dt_min = 0.0001 21 | dt_max = 0.05 22 | dt_write = 0.05 23 | t_final = 5.0 24 | gmres_tol = 1e-8 25 | fiber_error_tol = 0.1 26 | seed = 130319 27 | implicit_motor_activation_delay = 0.0 28 | periphery_interaction_flag = false 29 | adaptive_timestep_flag = true 30 | pair_evaluator = "FMM" 31 | fiber_type = "FiniteDifference" 32 | 33 | [background] 34 | components = [ 0, 0, 0,] 35 | scale_factor = [ 0.0, 0.0, 0.0,] 36 | uniform = [ 0.0, 0.0, 0.0,] 37 | 38 | [periphery] 39 | n_nodes = 2000 40 | precompute_file = "periphery_precompute.npz" 41 | shape = "sphere" 42 | radius = 3.846153846153846 43 | 44 | [params.dynamic_instability] 45 | n_nodes = 0 46 | v_growth = 0.0 47 | f_catastrophe = 0.0 48 | v_grow_collision_scale = 0.5 49 | f_catastrophe_collision_scale = 2.0 50 | nucleation_rate = 0.0 51 | radius = 0.025 52 | min_length = 0.5 53 | bending_rigidity = 0.0025 54 | min_separation = 0.1 55 | 56 | [params.periphery_binding] 57 | active = false 58 | polar_angle_start = 0.0 59 | polar_angle_end = 1.5707963267948966 60 | threshold = 0.75 61 | -------------------------------------------------------------------------------- /tests/core/test_files/fiber_container_fdf_n1.toml: -------------------------------------------------------------------------------- 1 | [[fibers]] 2 | n_nodes = 32 3 | parent_body = -1 4 | parent_site = -1 5 | force_scale = 0.0 6 | bending_rigidity = 20.0 7 | radius = 0.01 8 | length = 20.0 9 | minus_clamped = false 10 | x = [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.6451612903225806, 0.0, 0.0, 1.2903225806451613, 0.0, 0.0, 1.935483870967742, 0.0, 0.0, 2.5806451612903225, 0.0, 0.0, 3.225806451612903, 0.0, 0.0, 3.870967741935484, 0.0, 0.0, 4.516129032258064, 0.0, 0.0, 5.161290322580645, 0.0, 0.0, 5.806451612903226, 0.0, 0.0, 6.451612903225806, 0.0, 0.0, 7.096774193548388, 0.0, 0.0, 7.741935483870968, 0.0, 0.0, 8.387096774193548, 0.0, 0.0, 9.032258064516128, 0.0, 0.0, 9.67741935483871, 0.0, 0.0, 10.32258064516129, 0.0, 0.0, 10.96774193548387, 0.0, 0.0, 11.612903225806452, 0.0, 0.0, 12.258064516129032, 0.0, 0.0, 12.903225806451612, 0.0, 0.0, 13.548387096774192, 0.0, 0.0, 14.193548387096776, 0.0, 0.0, 14.838709677419356, 0.0, 0.0, 15.483870967741936, 0.0, 0.0, 16.129032258064516, 0.0, 0.0, 16.774193548387096, 0.0, 0.0, 17.419354838709676, 0.0, 0.0, 18.064516129032256, 0.0, 0.0, 18.709677419354836, 0.0, 0.0, 19.35483870967742, 0.0, 0.0, 20.0,] 11 | 12 | [params] 13 | eta = 1.0 14 | dt_initial = 0.01 15 | dt_min = 1e-5 16 | dt_max = 1.0 17 | dt_write = 0.01 18 | t_final = 10.0 19 | gmres_tol = 1e-8 20 | fiber_error_tol = 0.1 21 | seed = 130319 22 | implicit_motor_activation_delay = 0.0 23 | periphery_interaction_flag = false 24 | adaptive_timestep_flag = true 25 | pair_evaluator = "FMM" 26 | 27 | [background] 28 | components = [ 0, 0, 0,] 29 | scale_factor = [ 0.0, 0.0, 0.0,] 30 | uniform = [ 0.0, 0.0, 0.0,] 31 | 32 | [periphery] 33 | n_nodes = 6000 34 | precompute_file = "periphery_precompute.npz" 35 | shape = "sphere" 36 | radius = 6.0 37 | 38 | [params.dynamic_instability] 39 | n_nodes = 0 40 | v_growth = 0.0 41 | f_catastrophe = 0.0 42 | v_grow_collision_scale = 0.5 43 | f_catastrophe_collision_scale = 2.0 44 | nucleation_rate = 0.0 45 | radius = 0.025 46 | min_length = 0.5 47 | bending_rigidity = 0.0025 48 | min_separation = 0.1 49 | 50 | [params.periphery_binding] 51 | active = false 52 | polar_angle_start = 0.0 53 | polar_angle_end = 1.5707963267948966 54 | threshold = 0.75 55 | -------------------------------------------------------------------------------- /tests/core/unit_tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(TEST_LIST 3 | unit_test_fiber_finite_difference 4 | unit_test_fibercontainer_finite_difference 5 | unit_test_serialization 6 | unit_test_body_ellipsoidal 7 | unit_test_skelly_chebyshev 8 | unit_test_fiber_chebyshev_penalty_autodiff 9 | ) 10 | 11 | # Setup the MPI testing environment for the MPI tests 12 | # Taken from hoomd-blue v4.0.1 13 | MACRO(ADD_TO_MPI_TESTS _KEY _VALUE) 14 | SET("NProc_${_KEY}" "${_VALUE}") 15 | SET(MPI_TEST_LIST ${MPI_TEST_LIST} ${_KEY}) 16 | ENDMACRO(ADD_TO_MPI_TESTS) 17 | 18 | ADD_TO_MPI_TESTS(unit_test_fibercontainer_finite_difference_mpi 2) 19 | ADD_TO_MPI_TESTS(unit_test_serialization_mpi 2) 20 | 21 | # Set up the test executables 22 | foreach (CUR_TEST ${TEST_LIST} ${MPI_TEST_LIST}) 23 | # Add the executable 24 | add_executable(${CUR_TEST} EXCLUDE_FROM_ALL ${CUR_TEST}.cpp) 25 | 26 | target_compile_definitions(${CUR_TEST} PRIVATE TEST_FILES_DIR="${TEST_FILES_DIR}") 27 | 28 | add_dependencies(test_all ${CUR_TEST}) 29 | 30 | target_link_libraries(${CUR_TEST} PRIVATE 31 | skelly 32 | GTest::gtest 33 | GTest::gtest_main 34 | GTest::gmock 35 | GTest::gmock_main) 36 | target_include_directories(${CUR_TEST} PRIVATE 37 | "${googletest_SOURCE_DIR}/googlemock/include" 38 | "${googletest_SOURCE_DIR}/googletest/include") 39 | endforeach (CUR_TEST) 40 | 41 | # Add the non-mpi tests first (1 processor) 42 | foreach (CUR_TEST ${TEST_LIST}) 43 | add_test(NAME ${CUR_TEST} COMMAND ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} 1 ${MPIEXEC_POSTFLAGS} $) 44 | endforeach (CUR_TEST) 45 | 46 | # Add the mpi tests (variable processors) 47 | foreach (CUR_TEST ${MPI_TEST_LIST}) 48 | set(MPI_TEST_NAME mpi-${CUR_TEST}) 49 | 50 | add_test(NAME ${MPI_TEST_NAME} COMMAND 51 | ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} 52 | ${NProc_${CUR_TEST}} ${MPIEXEC_POSTFLAGS} 53 | $) 54 | endforeach (CUR_TEST) 55 | -------------------------------------------------------------------------------- /tests/core/unit_tests/mpi_environment.hpp: -------------------------------------------------------------------------------- 1 | #ifndef TESTS_MPI_ENVIRONMENT_HPP_ 2 | #define TESTS_MPI_ENVIRONMENT_HPP_ 3 | 4 | /// \file mpi_environment.hpp 5 | /// \brief Test MPI environment controller 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | // External includes 12 | #include 13 | #include 14 | #include 15 | 16 | /// \class MPIEnvironment 17 | /// \brief MPI environment interface with google test 18 | 19 | class MPIEnvironment : public ::testing::Environment { 20 | public: 21 | virtual void SetUp() { 22 | char **argv; 23 | int argc = 0; 24 | int mpiError = MPI_Init(&argc, &argv); 25 | ASSERT_FALSE(mpiError); 26 | 27 | // Load the MPI configuration for spdlog for our application 28 | int mrank; 29 | int msize; 30 | MPI_Comm_rank(MPI_COMM_WORLD, &mrank); 31 | MPI_Comm_size(MPI_COMM_WORLD, &msize); 32 | spdlog::logger sink = 33 | mrank == 0 ? spdlog::logger("SkellySim", std::make_shared()) 34 | : spdlog::logger("SkellySim", std::make_shared()); 35 | spdlog::set_default_logger(std::make_shared(sink)); 36 | spdlog::stderr_color_mt("STKFMM"); 37 | spdlog::stderr_color_mt("Belos"); 38 | spdlog::stderr_color_mt("SkellySim global"); 39 | spdlog::cfg::load_env_levels(); 40 | } 41 | 42 | virtual void TearDown() { 43 | int mpiError = MPI_Finalize(); 44 | ASSERT_FALSE(mpiError); 45 | } 46 | 47 | virtual ~MPIEnvironment() {} 48 | }; // MPIEnvironment 49 | 50 | #endif // TESTS_MPI_ENVIRONMENT_HPP_ 51 | -------------------------------------------------------------------------------- /tests/core/unit_tests/unit_test_body_ellipsoidal.cpp: -------------------------------------------------------------------------------- 1 | /// \file unit_test_fiber_finite_difference.cpp 2 | /// \brief Unit tests for FiberFiniteDifference (single MPI rank) 3 | 4 | // C++ includes 5 | #include 6 | 7 | // skelly includes 8 | #include 9 | #include 10 | 11 | // test files 12 | #include "./mpi_environment.hpp" 13 | 14 | int main(int argc, char **argv) { 15 | ::testing::InitGoogleTest(&argc, argv); 16 | ::testing::AddGlobalTestEnvironment(new MPIEnvironment); 17 | return RUN_ALL_TESTS(); 18 | } 19 | 20 | TEST(EllipsoidalBody, ConstructorTest) { 21 | 22 | // Get the ellipsoidal bodies read in 23 | const std::string toml_file = std::string(TEST_FILES_DIR) + "/ellipsoidal_body_n1.toml"; 24 | auto param_table = toml::parse(toml_file); 25 | auto params = Params(param_table.at("params")); 26 | params.print(); 27 | 28 | // Construct the body container for this, should handle the abstraction itself 29 | // FIXME XXX Currently we have issues loading in the quadrature points on the body from a separate directory 30 | BodyContainer bc = BodyContainer(); 31 | } 32 | -------------------------------------------------------------------------------- /tests/core/unit_tests/unit_test_fiber_finite_difference.cpp: -------------------------------------------------------------------------------- 1 | /// \file unit_test_fiber_finite_difference.cpp 2 | /// \brief Unit tests for FiberFiniteDifference (single MPI rank) 3 | 4 | // C++ includes 5 | #include 6 | 7 | // skelly includes 8 | #include 9 | 10 | // test files 11 | #include "./mpi_environment.hpp" 12 | 13 | int main(int argc, char **argv) { 14 | ::testing::InitGoogleTest(&argc, argv); 15 | ::testing::AddGlobalTestEnvironment(new MPIEnvironment); 16 | return RUN_ALL_TESTS(); 17 | } 18 | 19 | TEST(FiberFiniteDifference, ConstructorTest) { 20 | double flength = 1.0; 21 | double bending_rigidity = 0.0025; 22 | double radius = 0.0125; 23 | double eta = 1.0; 24 | int n_nodes = 32; 25 | FiberFiniteDifference fiber(n_nodes, radius, flength, bending_rigidity, eta); 26 | 27 | // Do some simple assertions to test if we have the right number of columns, etc 28 | ASSERT_EQ(3, fiber.x_.rows()); 29 | ASSERT_EQ(n_nodes, fiber.x_.cols()); 30 | } 31 | --------------------------------------------------------------------------------