diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ec3738..4ff3bdb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,8 +25,9 @@ jobs: - name: Linux (CUDA 12) steps: - name: Check out - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: + fetch-depth: 0 submodules: recursive - name: Show dependency file @@ -87,7 +88,8 @@ jobs: micromamba activate libmobility # install conda-build micromamba install -c conda-forge conda-build - conda build conda + export CUDA_VERSION=12 + conda build devtools/conda-build shell: bash -el {0} - name: Install via pip diff --git a/.github/workflows/conda.yaml b/.github/workflows/conda.yaml new file mode 100644 index 0000000..f2263ef --- /dev/null +++ b/.github/workflows/conda.yaml @@ -0,0 +1,44 @@ +name: Build and upload conda packages + +on: + release: + types: ['released', 'prereleased'] + # lets add on PR for testing + # pull_request: + # types: ['opened', 'edited', 'reopened', 'synchronize'] + + workflow_dispatch: # Un comment line if you also want to trigger action manually + +jobs: + conda_deployment_with_new_tag: + name: Conda deployment of package with Python ${{ matrix.python-version }} + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12"] + cuda-version: ["12.1", "12.6"] + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + - name: Conda environment creation and activation + uses: conda-incubator/setup-miniconda@v2 + with: + python-version: ${{ matrix.python-version }} + environment-file: devtools/conda-envs/build_env.yaml # Path to the build conda environment + auto-update-conda: false + auto-activate-base: false + show-channel-urls: true + - name: Build and upload the conda packages + uses: uibcdf/action-build-and-upload-conda-packages@v1.3.0 + # Export cuda-version as an environment variable + env: + CUDA_VERSION: ${{ matrix.cuda-version }} + with: + meta_yaml_dir: devtools/conda-build + python-version: ${{ matrix.python-version }} # Values previously defined in `matrix` + user: stochasticHydroTools + label: auto + overwrite: true + token: ${{ secrets.ANACONDA_TOKEN }} diff --git a/README.md b/README.md index 96d5515..2be978e 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ This repository is organized into the following directories: - **include/**: Includes the essential C++ base classes and utility files needed to construct the modules. -- **conda/**: Contains a meta.yaml file to build a conda package for libMobility using conda-build. +- **devtools/**: Contains dev specific scripts and files, like a meta.yaml file to build a conda package for libMobility using conda-build. - **docs/**: Contains the source files for the documentation. @@ -70,12 +70,23 @@ The solvers constructor will check the provided configuration and throw an error ## How to use this repo + +### Installation + +#### Installing a pre-built binary + +You can install libMobility latest release through our conda channel: + +```shell +$ conda install -c conda-forge -c stochasticHydroTools libmobility +``` + +#### Compiling from source + Be sure to clone this repository recursively (using ```git clone --recurse```). After compilation (see below) you will have all the tools mentioned above available for each solver. -## Compilation - We recommend working with a [conda](https://docs.conda.io/en/latest/) environment. The file environment.yml contains the necessary dependencies to compile and use the library. You can create the environment with: @@ -108,7 +119,7 @@ The following variables are available to customize the compilation process: * DOUBLEPRECISION : If this variable is defined libMobility is compiled in double precision (single by default). -## Python Usage +### Python Usage Importing libMobility will make available any module under "solvers". @@ -119,12 +130,13 @@ Calling ``` will provide more in depth information about the solver. -## C++ Usage +### C++ Usage In order to use a module called SolverName, the header solvers/SolverName/mobility.h must be included. If the module has been compiled correctly the definitions required for the functions in mobility.h will be available at solvers/SolverName/mobility.so. An example is available in cpp/example.cpp. -## Adding a new solver + +### Adding a new solver Solvers must be added following the ```libmobility::Mobility``` directives (see include/MobilityInterface). This C++ base class joins every solver under a common interface. When the C++ interface is prepared, the python bindings can be added automagically by using ```pythonify.h``` (a tool under MobilityInterface). diff --git a/conda/README.md b/devtools/conda-build/README.md similarity index 100% rename from conda/README.md rename to devtools/conda-build/README.md diff --git a/conda/build.sh b/devtools/conda-build/build.sh similarity index 100% rename from conda/build.sh rename to devtools/conda-build/build.sh diff --git a/conda/meta.yaml b/devtools/conda-build/meta.yaml similarity index 64% rename from conda/meta.yaml rename to devtools/conda-build/meta.yaml index 3c10324..a32be2d 100644 --- a/conda/meta.yaml +++ b/devtools/conda-build/meta.yaml @@ -1,25 +1,29 @@ package: name: libmobility - version: 0.1.0 + version: {{ GIT_DESCRIBE_TAG }} source: - path: ../ # Path to your project's source code + git_url: ../../ + +build: + number: 0 + string: cuda{{ CUDA_VERSION }}py{{ CONDA_PY }}h{{ PKG_HASH }}_{{ PKG_BUILDNUM }} requirements: build: - cmake >=3.22 - - cuda-version 12.* + - cuda-version {{ CUDA_VERSION }} - gxx - cuda-libraries-dev - cuda-nvcc - make - mkl-devel - pybind11 - - python 3.11.* + - python run: - - python 3.11.* - - cuda-version 12.* + - python + - cuda-version >={{ CUDA_VERSION }} - numpy - cuda-libraries - mkl @@ -33,7 +37,7 @@ test: source_files: - tests commands: - - pytest -vs -k SelfMobility tests/test_*py + - pytest -vs -k "SelfMobility and not fluct" tests/test_*py about: home: https://github.com/stochasticHydroTools/libmobility diff --git a/devtools/conda-envs/build_env.yaml b/devtools/conda-envs/build_env.yaml new file mode 100644 index 0000000..297f3a2 --- /dev/null +++ b/devtools/conda-envs/build_env.yaml @@ -0,0 +1,7 @@ +channels: # write here the list of channels to look for your library dependencies + - conda-forge + - default + +dependencies: # Keep this block with only these two packages + - anaconda-client + - conda-build diff --git a/docs/source/installation.rst b/docs/source/installation.rst index b06ea61..6d115a8 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -1,11 +1,32 @@ Installation ============ -We recommend working with a `conda `_ environment. The file ``environment.yml`` contains the necessary dependencies to compile and use the library. +We recommend working with a `conda `_ environment. + +You can install libMobility's latest release through our conda channel: + +.. code-block:: shell + + $ conda install -c conda-forge -c stochasticHydroTools libmobility + + + +Compilation from source +~~~~~~~~~~~~~~~~~~~~~~~ + +If you want to compile the library from source, you can clone the repository with: + +.. code-block:: shell + + $ git clone --recursive https://github.com/stochasticHydroTools/libMobility + +The ``--recursive`` flag is necessary to clone the submodules of the repository. Getting dependencies -------------------- +The file ``environment.yml`` contains the necessary dependencies to compile and use the library. + You can create the environment with: .. code-block:: shell @@ -20,9 +41,27 @@ Then, activate the environment with: .. hint:: At the moment we offer several installation methods: via pip, conda or building from source. We recommend using pip or conda first, resorting to building from source only if the other methods do not work for you. +Building from source +-------------------- + +CMake is used for compilation under the hood. After installing the dependencies you can compile and install everything with: + +.. code-block:: shell + + $ mkdir build && cd build + $ cmake -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX .. + $ make all install + +It is advisable to install the library in the conda environment, so that the python bindings are available. The environment variable ``$CONDA_PREFIX`` is set to the root of the conda environment. + +After compilation, the python bindings will be available in the conda environment under the name ``libMobility``. See :ref:`usage` for more information. + +The following variables are available to customize the compilation process: + +- ``DOUBLEPRECISION``: If this variable is defined, libMobility is compiled in double precision (single by default). -Installing via pip ------------------- +Alternative: Installing via pip +------------------------------- After installing the dependencies, you can install the library with pip. Go to the root of the repository and run: @@ -30,11 +69,11 @@ After installing the dependencies, you can install the library with pip. Go to t $ pip install . + +Alternative: Building a conda package +------------------------------------- -Building a conda package ------------------------- - -Building the conda package only requires the conda-build package. You can install it with: +Building the conda package only requires the conda-build package (dependencies will be fetched automatically). You can install it with: .. code-block:: shell @@ -44,7 +83,7 @@ You can build a conda package with the following command from the root of the re .. code-block:: shell - $ conda build conda + $ conda build devtools/conda-build This will build and test the package, which you can install in any environment with: @@ -55,22 +94,3 @@ This will build and test the package, which you can install in any environment w Conda will automatically install all the dependencies needed to run the library. -Building from source --------------------- - - -CMake is used for compilation, you can compile and install everything with: - -.. code-block:: shell - - $ mkdir build && cd build - $ cmake -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX .. - $ make all install - -It is advisable to install the library in the conda environment, so that the python bindings are available. The environment variable ``$CONDA_PREFIX`` is set to the root of the conda environment. - -After compilation, the python bindings will be available in the conda environment under the name ``libMobility``. See :ref:`usage` for more information. - -The following variables are available to customize the compilation process: - -- ``DOUBLEPRECISION``: If this variable is defined, libMobility is compiled in double precision (single by default). diff --git a/setup.py b/setup.py index da965e7..e0e3341 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,16 @@ import os import sys +try: + version = ( + subprocess.check_output(["git", "describe", "--abbrev=0", "--tags"]) + .strip() + .decode("utf-8") + ) +except: + print("Failed to retrieve the current version, defaulting to 0") + version = "0" + class CMakeExtension(Extension): def __init__(self, name, sourcedir=""): @@ -50,7 +60,7 @@ def build_extension(self, ext): setup( name="libMobility", - version="0.1.0", + version=version, packages=find_packages(), ext_modules=[CMakeExtension("libMobility")], cmdclass=dict(build_ext=CMakeBuild), diff --git a/solvers/DPStokes/extra/poly_fits.h b/solvers/DPStokes/extra/poly_fits.h index 9bfe59a..878fdb1 100644 --- a/solvers/DPStokes/extra/poly_fits.h +++ b/solvers/DPStokes/extra/poly_fits.h @@ -1,7 +1,25 @@ +/* Ryker Fish 2024. Helpers to compute values of c^{-1} from [1]. + +The ES kernel has a function c(beta) that provides a relationship between the hydrodynamic radius and the kernel +parameters alpha and beta. There is no closed form for c, but we can approximate c and c^{-1} with a polynomial fit. + +We have rH = h*w*c(beta), where rH is the hydrodynamic radius, h is the grid spacing, and w is the width of the kernel. +Thus, we can write c^{-1}(rH/(h*w)) = beta. + +References: +[1] Computing hydrodynamic interactions in confined doubly periodic geometries in linear time. +A. Hashemi et al. J. Chem. Phys. 158, 154101 (2023) https://doi.org/10.1063/5.0141371 + */ + #include namespace dpstokes_polys{ + /* Evaluates a polynomial at x with coefficients in descending order, so the highest order coefficients + are at the start of polyCoeffs. + e.g. for a polynomial of order n, this computes + polyCoeffs[n+1] + polyCoeffs[n]*x + polyCoeffs[n-2]*x^2 + ... + polyCoeffs[0]*x^n + */ double polyEval(std::vector polyCoeffs, double x){ int order = polyCoeffs.size() - 1; @@ -15,6 +33,7 @@ namespace dpstokes_polys{ return accumulator; } + // Coefficients for the polynomial fit of c^{-1} from [1] std::vector cbetam_inv = { 4131643418.193291, -10471683395.26777, diff --git a/solvers/DPStokes/mobility.h b/solvers/DPStokes/mobility.h index 39348cb..e065012 100644 --- a/solvers/DPStokes/mobility.h +++ b/solvers/DPStokes/mobility.h @@ -1,4 +1,8 @@ /*Raul P. Pelaez 2022. libMobility interface for UAMMD's DPStokes module + +References: +[1] Computing hydrodynamic interactions in confined doubly periodic geometries in linear time. +A. Hashemi et al. J. Chem. Phys. 158, 154101 (2023) https://doi.org/10.1063/5.0141371 */ #ifndef MOBILITY_SELFMOBILITY_H #define MOBILITY_SELFMOBILITY_H @@ -40,6 +44,9 @@ class DPStokes: public libmobility::Mobility{ void setParametersDPStokes(DPStokesParameters i_dppar){ this->dppar = i_dppar; + + if(this->dppar.Lx != this->dppar.Ly) throw std::runtime_error("[DPStokes] Only square periodic boxes (Lx = Ly) are currently supported.\n"); + dpstokes = std::make_shared(); } @@ -67,21 +74,13 @@ class DPStokes: public libmobility::Mobility{ this->dppar.alpha = this->dppar.w*0.5; this->dppar.tolerance = 1e-6; - real N_real = this->dppar.Lx/h; // actual N given L and h - int N_up = ceil(N_real); - int N_down = floor(N_real); - int N; - // either N_up or N_down will be a multiple of 2. pick the even one for a more FFT friendly grid. - if(N_up % 2 == 0){ - N = N_up; - }else{ - N = N_down; - } + int N = floor(this->dppar.Lx/h); + N += N % 2; this->dppar.nx = N; this->dppar.ny = N; - // note: only set up for square boxes + // note: this part is only configured for square boxes if(this->dppar.allowChangingBoxSize){ // adjust box size to suit h this->dppar.Lx = N*h; this->dppar.Ly = N*h; @@ -104,14 +103,9 @@ class DPStokes: public libmobility::Mobility{ // sets chebyshev node spacing at its coarsest (in the middle) to be h real nz_actual = M_PI/(asin(h/H)) + 1; - // pick nearby N such that 2(Nz-1) is FFT friendly - N_up = ceil(nz_actual); - N_down = floor(nz_actual); - if(N_up % 2 == 1){ - this->dppar.nz = N_up; - } else { - this->dppar.nz = N_down; - } + // pick nearby N such that 2(Nz-1) has two factors of 2 and is FFT friendly + this->dppar.nz = floor(nz_actual); + this->dppar.nz += (int)ceil(nz_actual) % 2; dpstokes->initialize(dppar, this->numberParticles); Mobility::initialize(ipar); diff --git a/tests/ref/pair_mobility_bw_ref_noimg.mat b/tests/ref/pair_mobility_bw_ref_noimg.npz similarity index 99% rename from tests/ref/pair_mobility_bw_ref_noimg.mat rename to tests/ref/pair_mobility_bw_ref_noimg.npz index 97b988b..f7757c1 100644 Binary files a/tests/ref/pair_mobility_bw_ref_noimg.mat and b/tests/ref/pair_mobility_bw_ref_noimg.npz differ diff --git a/tests/ref/pair_mobility_bw_torque.mat b/tests/ref/pair_mobility_bw_torque.npz similarity index 98% rename from tests/ref/pair_mobility_bw_torque.mat rename to tests/ref/pair_mobility_bw_torque.npz index 630ed82..896be62 100644 Binary files a/tests/ref/pair_mobility_bw_torque.mat and b/tests/ref/pair_mobility_bw_torque.npz differ diff --git a/tests/ref/pair_mobility_bw_w4.mat b/tests/ref/pair_mobility_bw_w4.npz similarity index 96% rename from tests/ref/pair_mobility_bw_w4.mat rename to tests/ref/pair_mobility_bw_w4.npz index b1dde98..e8099fc 100644 Binary files a/tests/ref/pair_mobility_bw_w4.mat and b/tests/ref/pair_mobility_bw_w4.npz differ diff --git a/tests/ref/pair_mobility_nbody_freespace.mat b/tests/ref/pair_mobility_nbody_freespace.npz similarity index 81% rename from tests/ref/pair_mobility_nbody_freespace.mat rename to tests/ref/pair_mobility_nbody_freespace.npz index e01c99a..ff716be 100644 Binary files a/tests/ref/pair_mobility_nbody_freespace.mat and b/tests/ref/pair_mobility_nbody_freespace.npz differ diff --git a/tests/ref/pair_mobility_sc_torque.mat b/tests/ref/pair_mobility_sc_torque.npz similarity index 98% rename from tests/ref/pair_mobility_sc_torque.mat rename to tests/ref/pair_mobility_sc_torque.npz index 24ecf55..43c2c4e 100644 Binary files a/tests/ref/pair_mobility_sc_torque.mat and b/tests/ref/pair_mobility_sc_torque.npz differ diff --git a/tests/ref/pair_mobility_sc_w4.mat b/tests/ref/pair_mobility_sc_w4.npz similarity index 96% rename from tests/ref/pair_mobility_sc_w4.mat rename to tests/ref/pair_mobility_sc_w4.npz index 1797e01..82a8a6a 100644 Binary files a/tests/ref/pair_mobility_sc_w4.mat and b/tests/ref/pair_mobility_sc_w4.npz differ diff --git a/tests/ref/readme.txt b/tests/ref/readme.txt index cb80a74..1edcfb6 100644 --- a/tests/ref/readme.txt +++ b/tests/ref/readme.txt @@ -9,7 +9,7 @@ sc- slit channel -------------- Files -------------- SELF: self_mobility_bw_ref: from the DPStokes repository. Periodized NBody using kernels with wall corrections in double precision. -self_mobility_bw_ref_noimg.mat: from the DPStokes repository. NBody kernels with wall corrections in double precision, but without using periodized images. +self_mobility_bw_ref_noimg: from the DPStokes repository. NBody kernels with wall corrections in double precision, but without using periodized images. self_mobility_bw_w4, self_mobility_sc_w4: generated from this repository in double precision with default parameters. self_mobility_*_torques: generated from this repository in double precision with default parameters. @@ -17,4 +17,4 @@ PAIR: pair_mobility_bw_w4, pair_mobility_sc_w4: generated from this repository in double precision with default parameters. pair_mobility_bw_ref_noimg: from the DPStokes repository. NBody kernels with wall corrections in double precision, but without using periodized images. pair_mobility_nbody_freespace: no-wall RPY pair mobility. Generated using mobility matrix numba code from the RigidMultiblob repository. -pair_mobility_*_torques: generated from this repository in double precision with default parameters. \ No newline at end of file +pair_mobility_*_torques: generated from this repository in double precision with default parameters. diff --git a/tests/ref/self_mobility_bw_ref.mat b/tests/ref/self_mobility_bw_ref.npz similarity index 95% rename from tests/ref/self_mobility_bw_ref.mat rename to tests/ref/self_mobility_bw_ref.npz index 90d68cf..d3c57bd 100644 Binary files a/tests/ref/self_mobility_bw_ref.mat and b/tests/ref/self_mobility_bw_ref.npz differ diff --git a/tests/ref/self_mobility_bw_ref_noimg.mat b/tests/ref/self_mobility_bw_ref_noimg.npz similarity index 92% rename from tests/ref/self_mobility_bw_ref_noimg.mat rename to tests/ref/self_mobility_bw_ref_noimg.npz index b7e4d7f..2a65791 100644 Binary files a/tests/ref/self_mobility_bw_ref_noimg.mat and b/tests/ref/self_mobility_bw_ref_noimg.npz differ diff --git a/tests/ref/self_mobility_bw_torque.mat b/tests/ref/self_mobility_bw_torque.npz similarity index 91% rename from tests/ref/self_mobility_bw_torque.mat rename to tests/ref/self_mobility_bw_torque.npz index 5f9a6bf..5f1d155 100644 Binary files a/tests/ref/self_mobility_bw_torque.mat and b/tests/ref/self_mobility_bw_torque.npz differ diff --git a/tests/ref/self_mobility_bw_w4.mat b/tests/ref/self_mobility_bw_w4.npz similarity index 83% rename from tests/ref/self_mobility_bw_w4.mat rename to tests/ref/self_mobility_bw_w4.npz index 63744a7..665494c 100644 Binary files a/tests/ref/self_mobility_bw_w4.mat and b/tests/ref/self_mobility_bw_w4.npz differ diff --git a/tests/ref/self_mobility_sc_torque.mat b/tests/ref/self_mobility_sc_torque.npz similarity index 91% rename from tests/ref/self_mobility_sc_torque.mat rename to tests/ref/self_mobility_sc_torque.npz index acc4bd3..9d6abff 100644 Binary files a/tests/ref/self_mobility_sc_torque.mat and b/tests/ref/self_mobility_sc_torque.npz differ diff --git a/tests/ref/self_mobility_sc_w4.mat b/tests/ref/self_mobility_sc_w4.npz similarity index 84% rename from tests/ref/self_mobility_sc_w4.mat rename to tests/ref/self_mobility_sc_w4.npz index 378db78..c0ea116 100644 Binary files a/tests/ref/self_mobility_sc_w4.mat and b/tests/ref/self_mobility_sc_w4.npz differ diff --git a/tests/test_fluctuation_dissipation.py b/tests/test_fluctuation_dissipation.py index efcc207..032b314 100644 --- a/tests/test_fluctuation_dissipation.py +++ b/tests/test_fluctuation_dissipation.py @@ -69,8 +69,7 @@ def fluctuation_dissipation_KS(M, fluctuation_method, needsTorques): ], ) @pytest.mark.parametrize("hydrodynamicRadius", [0.95, 1.12]) -# @pytest.mark.parametrize("numberParticles", [1,2,10]) -@pytest.mark.parametrize("numberParticles", [10]) +@pytest.mark.parametrize("numberParticles", [1,2,10]) def test_fluctuation_dissipation_linear_displacements( Solver, periodicity, hydrodynamicRadius, numberParticles ): diff --git a/tests/test_initialization.py b/tests/test_initialization.py index f888c4f..c732603 100644 --- a/tests/test_initialization.py +++ b/tests/test_initialization.py @@ -24,7 +24,7 @@ def test_periodic_initialization(Solver, periodicity): def test_pse_torques_unsupported(): hydrodynamicRadius = 1.0 solver = PSE("periodic", "periodic", "periodic") - parameters = sane_parameters[Solver.__name__] + parameters = sane_parameters[PSE.__name__] solver.setParameters(**parameters) numberParticles = 1 with pytest.raises(RuntimeError): @@ -41,6 +41,13 @@ def test_invalid_throws(Solver): with pytest.raises(RuntimeError): solver = Solver("periodicasdas", "periodic", "open") +def test_dpstokes_invalid_box(): + with pytest.raises(RuntimeError): + Lx, Ly = 10, 20 + solver = DPStokes("periodic", "periodic", "single_wall") + params = {"dt": 1, "Lx": Lx, "Ly": Ly, "zmin": 0, "zmax": 19.2} + solver.setParameters(**params) + @pytest.mark.parametrize(("NBatch", "NperBatch"), [ (1, 12), diff --git a/tests/test_mobility_matrix.py b/tests/test_mobility_matrix.py index 2cdffef..d08d40e 100644 --- a/tests/test_mobility_matrix.py +++ b/tests/test_mobility_matrix.py @@ -41,6 +41,8 @@ def test_mobility_matrix_linear( assert M.shape == (3 * numberParticles, 3 * numberParticles) assert M.dtype == precision sym = M - M.T + atol = 5e-5 + rtol = 1e-7 assert np.allclose( sym, 0.0, rtol=0, atol=5e-5 ), f"Mobility matrix is not symmetric within 5e-5, max diff: {np.max(np.abs(sym))}" @@ -132,7 +134,7 @@ def test_self_mobility_linear_nbody(algorithm): forces = np.ones(3, dtype=precision) result, _ = solver.Mdot(forces) m0 = 1.0 / (6 * np.pi * viscosity * hydrodynamicRadius) - assert np.allclose(result, m0 * forces, rtol=0, atol=1e-7) + assert np.allclose(result, m0 * forces, rtol=1e-7, atol=1e-7) @pytest.mark.parametrize("algorithm", ["naive", "block", "fast", "advise"]) def test_self_mobility_angular_nbody(algorithm): @@ -211,8 +213,8 @@ def test_pair_mobility_angular_nbody(algorithm): parameters = {"algorithm": algorithm} solver.setParameters(**parameters) - ref_file = "./ref/pair_mobility_nbody_freespace.mat" - ref = scipy.io.loadmat(ref_file) + ref_file = "./ref/pair_mobility_nbody_freespace.npz" + ref = np.load(ref_file) refM = np.array(ref['M']).astype(precision) r_vecs = np.array(ref['r_vecs']).astype(precision) a = np.array(ref['a']).astype(precision).flatten() @@ -233,4 +235,4 @@ def test_pair_mobility_angular_nbody(algorithm): solver.setPositions(positions) M = compute_M(solver, 2, True) - assert np.allclose(refM[i], M, atol=1e-6) \ No newline at end of file + assert np.allclose(refM[i], M, atol=1e-6) diff --git a/tests/test_wall_mobility.py b/tests/test_wall_mobility.py index 3dcbca5..a18eac2 100644 --- a/tests/test_wall_mobility.py +++ b/tests/test_wall_mobility.py @@ -1,12 +1,13 @@ import pytest import numpy as np -import scipy.interpolate -import scipy.io from libMobility import DPStokes, NBody from utils import compute_M -self_mobility_params = { +# NOTE: Some of the following tests will only pass if compiled with double precision. +# This is because the reference data was generated in double precision. + +wall_params = { "DPStokes": {"dt": 1, "Lx": 76.8, "Ly": 76.8, "zmin": 0, "zmax": 19.2}, "NBody": {"algorithm": "advise"}, } @@ -14,23 +15,21 @@ @pytest.mark.parametrize( ("Solver", "periodicity", "tol", "start_height", "ref_file"), [ - (DPStokes, ("periodic", "periodic", "single_wall"), 5e-3, 4, "self_mobility_bw_ref.mat"), - (DPStokes, ("periodic", "periodic", "single_wall"), 1e-5, 0, "self_mobility_bw_w4.mat"), - (DPStokes, ("periodic", "periodic", "two_walls"), 1e-5, 0, "self_mobility_sc_w4.mat"), - (NBody, ("open", "open", "single_wall"), 1e-6, 1, "self_mobility_bw_ref_noimg.mat") + (DPStokes, ("periodic", "periodic", "single_wall"), 5e-3, 4, "self_mobility_bw_ref.npz"), + (DPStokes, ("periodic", "periodic", "single_wall"), 1e-6, 0, "self_mobility_bw_w4.npz"), + (DPStokes, ("periodic", "periodic", "two_walls"), 1e-6, 0, "self_mobility_sc_w4.npz"), + (NBody, ("open", "open", "single_wall"), 1e-6, 1, "self_mobility_bw_ref_noimg.npz") ], ) def test_self_mobility_linear(Solver, periodicity, tol, start_height, ref_file): - zmax = 19.2 xymax = 76.8 - params = self_mobility_params[Solver.__name__] - + params = wall_params[Solver.__name__] needsTorque = False ref_dir = "./ref/" - ref = scipy.io.loadmat(ref_dir + ref_file) + ref = np.load(ref_dir + ref_file) refM = ref['M'] - refHeights = ref['heights'][0] + refHeights = ref['heights'].flatten() hydrodynamicRadius = 1.0 eta = 1/4/np.sqrt(np.pi) @@ -66,37 +65,29 @@ def test_self_mobility_linear(Solver, periodicity, tol, start_height, ref_file): M /= normMat allM[i] = M - # uncomment to save datafile for test plots - # scipy.io.savemat('./temp/test_data/test_' + ref_file, {'M': allM, 'heights': refHeights}) - diags = [np.diag(matrix) for matrix in allM] ref_diags = [np.diag(matrix)[0:3] for matrix in refM] # only take diagonal elements from forces - diff = np.abs([(diag - ref_diag) for diag, ref_diag in zip(diags, ref_diags)]) - - avgErr = np.mean(diff) - - assert avgErr < tol, f"Self mobility does not match reference. Average error: {avgErr}" + for diag, ref_diag in zip(diags, ref_diags): + assert np.allclose(diag, ref_diag, rtol=tol, atol=tol), f"Self mobility does not match reference" @pytest.mark.parametrize( ("Solver", "periodicity", "tol", "ref_file"), [ - (DPStokes, ("periodic", "periodic", "single_wall"), 5e-5, "pair_mobility_bw_w4.mat"), - (DPStokes, ("periodic", "periodic", "two_walls"), 5e-5, "pair_mobility_sc_w4.mat"), - (NBody, ("open", "open", "single_wall"), 1e-4, "pair_mobility_bw_ref_noimg.mat"), + (DPStokes, ("periodic", "periodic", "single_wall"), 1e-6, "pair_mobility_bw_w4.npz"), + (DPStokes, ("periodic", "periodic", "two_walls"), 1e-6, "pair_mobility_sc_w4.npz"), + (NBody, ("open", "open", "single_wall"), 1e-4, "pair_mobility_bw_ref_noimg.npz"), ], ) def test_pair_mobility_linear(Solver, periodicity, ref_file, tol): - zmax = 19.2 xymax = 76.8 - params = self_mobility_params[Solver.__name__] - + params = wall_params[Solver.__name__] needsTorque = False ref_dir = "./ref/" - ref = scipy.io.loadmat(ref_dir + ref_file) + ref = np.load(ref_dir + ref_file) refM = ref['M'] - refHeights = ref['heights'].flatten() + refHeights = ref['heights'] nHeights = len(refHeights) radH = 1.0 # hydrodynamic radius @@ -132,9 +123,6 @@ def test_pair_mobility_linear(Solver, periodicity, ref_file, tol): M /= normMat allM[i][k] = M - # uncomment to save datafile for test plots - # scipy.io.savemat('./temp/test_data/test_' + ref_file, {'M': allM, 'heights': refHeights}) - for i in range(0, nSeps): for k in range(0, nHeights): diff = abs(allM[i,k] - refM[i,k][0:6,0:6]) @@ -143,15 +131,15 @@ def test_pair_mobility_linear(Solver, periodicity, ref_file, tol): @pytest.mark.parametrize( ("Solver", "periodicity", "ref_file"), [ - (DPStokes, ("periodic", "periodic", "single_wall"), "self_mobility_bw_torque.mat"), - (DPStokes, ("periodic", "periodic", "two_walls"), "self_mobility_sc_torque.mat"), - (NBody, ("open", "open", "single_wall"), "self_mobility_bw_ref_noimg.mat") + (DPStokes, ("periodic", "periodic", "single_wall"), "self_mobility_bw_torque.npz"), + (DPStokes, ("periodic", "periodic", "two_walls"), "self_mobility_sc_torque.npz"), + (NBody, ("open", "open", "single_wall"), "self_mobility_bw_ref_noimg.npz") ], ) def test_self_mobility_angular(Solver, periodicity, ref_file): zmax = 19.2 xymax = 76.8 - params = self_mobility_params[Solver.__name__] + params = wall_params[Solver.__name__] hydrodynamicRadius = 1.0 eta = 1/4/np.sqrt(np.pi) @@ -160,9 +148,9 @@ def test_self_mobility_angular(Solver, periodicity, ref_file): tol = 1e-6 ref_dir = "./ref/" - ref = scipy.io.loadmat(ref_dir + ref_file) + ref = np.load(ref_dir + ref_file) refM = ref['M'] - refHeights = ref['heights'][0] + refHeights = ref['heights'].flatten() precision = np.float32 if Solver.precision == "float" else np.float64 @@ -187,6 +175,7 @@ def test_self_mobility_angular(Solver, periodicity, ref_file): allM = np.zeros((nHeights, 6*numberParticles, 6*numberParticles), dtype=precision) for i in range(0,nHeights): + # breakpoint() positions = np.array([[xymax/2, xymax/2, refHeights[i]]], dtype=precision) solver.setPositions(positions) @@ -194,9 +183,6 @@ def test_self_mobility_angular(Solver, periodicity, ref_file): M /= normMat allM[i] = M - # uncomment to save datafile for test plots - # scipy.io.savemat('./temp/test_data/test_' + ref_file, {'M': allM, 'heights': refHeights}) - for i in range(0, nHeights): diff = abs(allM[i] - refM[i]) assert np.all(diff < tol) @@ -204,15 +190,15 @@ def test_self_mobility_angular(Solver, periodicity, ref_file): @pytest.mark.parametrize( ("Solver", "periodicity", "ref_file"), [ - (DPStokes, ("periodic", "periodic", "single_wall"), "pair_mobility_bw_torque.mat"), - (DPStokes, ("periodic", "periodic", "two_walls"), "pair_mobility_sc_torque.mat"), - (NBody, ("open", "open", "single_wall"), "pair_mobility_bw_ref_noimg.mat") + (DPStokes, ("periodic", "periodic", "single_wall"), "pair_mobility_bw_torque.npz"), + (DPStokes, ("periodic", "periodic", "two_walls"), "pair_mobility_sc_torque.npz"), + (NBody, ("open", "open", "single_wall"), "pair_mobility_bw_ref_noimg.npz") ], ) def test_pair_mobility_angular(Solver, periodicity, ref_file): zmax = 19.2 xymax = 76.8 - params = self_mobility_params[Solver.__name__] + params = wall_params[Solver.__name__] hydrodynamicRadius = 1.0 eta = 1/4/np.sqrt(np.pi) needsTorque = True @@ -220,7 +206,7 @@ def test_pair_mobility_angular(Solver, periodicity, ref_file): tol = 1e-6 ref_dir = "./ref/" - ref = scipy.io.loadmat(ref_dir + ref_file) + ref = np.load(ref_dir + ref_file) refM = ref['M'] refHeights = ref['heights'].flatten() @@ -260,33 +246,9 @@ def test_pair_mobility_angular(Solver, periodicity, ref_file): M /= normMat allM[i,k] = M - # uncomment to save datafile for test plots - scipy.io.savemat('./temp/test_data/test_' + ref_file, {'M': allM, 'heights': refHeights, 'seps': seps}) - for i in range(0, nSeps): for k in range(0, nHeights): diff = abs(allM[i,k] - refM[i,k]) temp = diff < tol x = temp[0:6,6:12] - # breakpoint() - assert np.all(diff < tol) - -def checkPairComponent(indx, indy, allM, refM, nSeps, tol): - - indx -= 1 # shift from matlab to python indexing - indy -= 1 - for i in range(0, nSeps): - - x = allM[i, :, indx, indy] - x_ref = refM[i, :, indx, indy] - - for j in range(0, len(x)): - - diff = x[j]-x_ref[j] - - if x[j] > tol and x_ref[j] > tol and x_ref[j] != 0: - err = diff/x_ref[j] - else: - err = diff - - assert err < tol, f"Pair mobility does not match reference for component {indx+1}, {indy+1}" \ No newline at end of file + assert np.all(diff < tol) \ No newline at end of file diff --git a/tests/utils.py b/tests/utils.py index 40bc048..fd62749 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,7 +4,7 @@ sane_parameters = { "PSE": {"psi": 1.0, "Lx": 32, "Ly": 32, "Lz": 32, "shearStrain": 0.0}, "NBody": {"algorithm": "advise"}, - "DPStokes": {"dt": 1, "Lx": 16, "Ly": 16, "zmin": -6, "zmax": 6}, + "DPStokes": {"dt": 1, "Lx": 16, "Ly": 16, "zmin": -6, "zmax": 6, "allowChangingBoxSize": False}, "SelfMobility": {"parameter": 5.0}, }