diff --git a/README.md b/README.md index e58af7d..87b37ad 100644 --- a/README.md +++ b/README.md @@ -1,204 +1,203 @@ Tamaas --- A high-performance library for periodic rough surface contact ======================================================================== Tamaas is a C++/Python library that implements a number of numerical methods based on integral equations to efficiently solve contact problems with rough surfaces. The word تماس (tamaas) means "contact" in Arabic and Farsi. ## Quick Start If you have a Linux system, you can simply run `pip(3) install tamaas`. Note however that there due to compatibility reasons, this version of Tamaas is not built with parallel/multi-threading capabilities. So if you want parallelism, or encounter an issue with the [PyPI package](https://pypi.org/project/tamaas/), please compile from source. ## Dependencies Here is a list of dependencies to compile Tamaas: - a //C++ compiler// with full //C++14// and //OpenMP// support - [SCons](https://scons.org/) (python build system) -- [FFTW3](http://www.fftw.org/) compiled with //OpenMP// support +- [FFTW3](http://www.fftw.org/) - [boost](https://www.boost.org/) (preprocessor) - [thrust](https://github.com/thrust/thrust) (1.9.2+) -- [python 3+](https://www.python.org/) (probably works with python 2, but it - is not tested) with [numpy](https://numpy.org/) +- [python 3+](https://www.python.org/) with [numpy](https://numpy.org/) - [pybind11](https://github.com/pybind/pybind11) (included as submodule) - [expolit](https://c4science.ch/source/expolit/) (included as submodule) Optional dependencies are: - an MPI implmentation -- [FFTW3](http://www.fftw.org/) with MPI support +- [FFTW3](http://www.fftw.org/) with MPI/threads///OpenMP// support - [scipy](https://scipy.org) (for nonlinear solvers) - [uvw](https://pypi.org/project/uvw/), [netCDF4](https://unidata.github.io/netcdf4-python/netCDF4/index.html#section1), [h5py](https://www.h5py.org/) (for various dumpers) - [googletest](https://github.com/google/googletest) and [pytest](https://docs.pytest.org/en/latest/) (for tests) - [Doxygen](http://doxygen.nl/) and [Sphinx](https://www.sphinx-doc.org/en/stable/) (for documentation) Note that a Debian distribution should have the right packages for all these dependencies (they package the right version of thrust extracted from CUDA in `stretch-backports non-free` and `buster non-free`). ## Compiling You should first clone the git submodules that are dependencies to tamaas (expolit, pybind11 and googletest): git submodule update --init --recursive The build system uses SCons. In order to compile Tamaas with the default options: scons After compiling a first time, you can edit the compilation options in the file `build-setup.conf`, or alternatively supply the options directly in the command line: scons option=value [...] To get a list of //all// build options and their possible values, you can run `scons -h`. You can run `scons -H` to see the SCons-specific options (among them `-j n` executes the build with `n` threads and `-c` cleans the build). Note that the build is aware of the `CXX` and `CXXFLAGS` environment variables. ## Installing Before you can import tamaas in python, you need to install the python package in some way. ### Using pip You have two choices to install tamaas: - An out-of-repository installation to a given prefix (e.g. `/usr/local`, or a python virtual environment) - A development installation to `~/.local` which links to the build directory The former is simply achieved with: scons prefix=/your/prefix install # Equivalent to (if you build in release) install build-release/src/libTamaas.so* /your/prefix/lib pip3 install --prefix /your/prefix build-release/python The compiled parts of the python module should automatically know where to find the Tamaas shared library, so no need to tinker with `LD_LIBRARY_PATH`. The second installation choice is equally simple: scons dev # Equivalent to pip3 install --user -e build-release/python You can check that everything is working fine with: python3 -c 'import tamaas; print(tamaas)' ### Using environment variables (not recommended) You can source (e.g. in your `~/.bashrc` file) the file `build-release/tamaas_environment.sh` to modify the `PYTHONPATH` and `LD_LIBRARY_PATH` environment variables. This is however not recommended because these variables may conflict in a python virtual environment (i.e. if you use `virtualenv` with tamaas). ## Tests To run tests, make sure to have [pytest](https://docs.pytest.org/en/latest/) installed and run `scons test` if you have compiled Tamaas with tests activated (`scons build_tests=True use_googletest=True`). ## Documentation The latest documentation is available on [ReadTheDocs](https://tamaas.readthedocs.io/en/latest/)! Note however that due to technical limitations, the Python API documentation reflects the latest version of the [PyPI package](https://pypi.org/project/tamaas/), which may be sligthly out of sync with the master branch. To build the documentation locally, activate the `build_doc` option and run `scons doc`. Make sure you have [sphinx-rtd-theme](https://pypi.org/project/sphinx-rtd-theme/) and [breath](https://pypi.org/project/breathe/) installed. The compiled indexes for the doxygen C++ API and Sphinx documentation can be found in `doc/build/{doxygen,sphinx}/html/index.html`. Beware however that manually compiling documentation leads to a lot of warnings. ## Examples Example simulations can be found in the `examples/` directory. There is no guarantee that the examples in `examples/legacy/` all work however. - `rough_contact.py` shows a typical normal rough contact simulation - `adhesion.py` shows how you can derive some classes from Tamaas in python, here to implement a custom adhesion potential - `plasticity.py` computes an elastoplastic Hertz simulation and dumps the result in `examples/paraview/` in VTK format - `stresses.py` shows how you can compute stresses from a boundary traction distribution - the scripts in `pipe_tools` allow to execute elastic contact simulations without the need to code a custom script (see documentation for more details) ## Contributing Contributions to Tamaas are welcome! Please follow the guidelines below. ### Report an issue If you have an account on [c4science](https://c4science.ch), you can [submit an issue](https://c4science.ch/maniphest/task/edit/?owner=frerot&projectPHIDs=tamaas&view=public). All open issues are visible on the [workboard](https://c4science.ch/project/board/2036/), and the full list of issues is available [here](https://c4science.ch/maniphest/query/1jDBkIDDxCAP/). ### Submit a patch / pull-request C4Science runs [Phabricator](https://www.phacility.com/phabricator/) to host the code. The procedure to submit changes to repositories is described in this [guide](https://secure.phabricator.com/book/phabricator/article/arcanist_diff/). In a nutshell: ```lang=bash # Make changes git commit # Any number of times arc diff # Pushes all new commits for review # Wait for review... ``` ## Citing Tamaas is the result of a science research project. To give proper credit to Tamaas and the researchers who have developed the numerical methods that it implements, please cite Tamaas as: Frérot , L., Anciaux, G., Rey, V., Pham-Ba, S., & Molinari, J.-F. Tamaas: a library for elastic-plastic contact of periodic rough surfaces. Journal of Open Source Software, 5(51), 2121 (2020). [doi:10.21105/joss.02121](https://doi.org/10.21105/joss.02121) If you use the elastic-plastic contact capabilities of Tamaas, please cite: Frérot, L., Bonnet, M., Molinari, J.-F. & Anciaux, G. A Fourier-accelerated volume integral method for elastoplastic contact. Computer Methods in Applied Mechanics and Engineering 351, 951–976 (2019) [doi:10.1016/j.cma.2019.04.006](https://doi.org/10.1016/j.cma.2019.04.006). If you use the adhesive contact capabilities of Tamaas, please cite: Rey, V., Anciaux, G. & Molinari, J.-F. Normal adhesive contact on rough surfaces: efficient algorithm for FFT-based BEM resolution. Comput Mech 1–13 (2017) [doi:10.1007/s00466-017-1392-5](https://doi.org/10.1007/s00466-017-1392-5). ## License Tamaas is distributed under the terms of the [GNU Affero General Public License v3.0](https://www.gnu.org/licenses/agpl.html). diff --git a/SConstruct b/SConstruct index 472da6b..e2c8c6f 100644 --- a/SConstruct +++ b/SConstruct @@ -1,474 +1,474 @@ # -*- mode:python; coding: utf-8 -*- # vim: set ft=python: # @file # @section LICENSE # # Copyright (©) 2016-2021 EPFL (École Polytechnique Fédérale de Lausanne), # Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # ------------------------------------------------------------------------------ # Imports # ------------------------------------------------------------------------------ from __future__ import print_function import os from subprocess import check_output import SCons from os.path import join, abspath, basename # Import below not strictly necessary, but good for pep8 from SCons.Script import ( EnsurePythonVersion, EnsureSConsVersion, Help, Environment, Variables, EnumVariable, PathVariable, BoolVariable, Split, SConscript, Export, Dir ) from version import get_git_subst from detect import ( FindFFTW, FindBoost, FindThrust, FindCuda, FindExpolit, FindPybind11 ) # ------------------------------------------------------------------------------ EnsurePythonVersion(2, 7) EnsureSConsVersion(2, 4) # ------------------------------------------------------------------------------ tamaas_info = dict( - version="2.2.0", + version="2.2.1", authors=[ u'Lucas Frérot', 'Guillaume Anciaux', 'Valentine Rey', 'Son Pham-Ba', u'Jean-François Molinari' ], maintainer=u'Lucas Frérot', email='lucas.frerot@protonmail.com', copyright=u"Copyright (©) 2016-2021 EPFL " + u"(École Polytechnique Fédérale de Lausanne), " + u"Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)" ) # ------------------------------------------------------------------------------ def detect_dependencies(env): """Detect all dependencies""" fftw_comp = { 'omp': ['omp'], 'threads': ['threads'], 'none': [], } fftw_components = fftw_comp[env['fftw_threads']] if main_env['use_mpi']: fftw_components.append('mpi') FindFFTW(env, fftw_components, precision=env['real_type']) FindBoost(env, ['boost/preprocessor/seq.hpp']) FindExpolit(env) thrust_var = 'THRUST_ROOT' # Take cuda version of thrust if available if 'CUDA_ROOT' in env['ENV']: thrust_var = 'CUDA_ROOT' FindThrust(env, env['backend'], thrust_var) # Activate cuda if needed if env['backend'] == 'cuda': FindCuda(env) if env['build_python']: FindPybind11(env) # ------------------------------------------------------------------------------ # Main compilation # ------------------------------------------------------------------------------ # Compilation colors colors = { 'cyan': '\033[96m', 'purple': '\033[95m', 'blue': '\033[94m', 'green': '\033[92m', 'yellow': '\033[93m', 'gray': '\033[38;5;8m', 'orange': '\033[38;5;208m', 'red': '\033[91m', 'end': '\033[0m' } # Inherit all environment variables (for CXX detection, etc.) main_env = Environment( ENV=os.environ, ) # Set tamaas information for k, v in tamaas_info.items(): main_env[k] = v main_env['COLOR_DICT'] = colors # Build variables vars = Variables('build-setup.conf') vars.AddVariables( EnumVariable('build_type', 'Build type', 'release', allowed_values=('release', 'profiling', 'debug'), ignorecase=2), EnumVariable('backend', 'Thrust backend', 'omp', allowed_values=('cpp', 'omp', 'tbb'), ignorecase=2), EnumVariable('fftw_threads', 'Threads FFTW library preference', 'omp', allowed_values=('omp', 'threads', 'none'), ignorecase=2), EnumVariable('sanitizer', 'Sanitizer type', 'none', allowed_values=('none', 'memory', 'leaks', 'address'), ignorecase=2), PathVariable('prefix', 'Prefix where to install', '/usr/local'), # Dependencies paths PathVariable('FFTW_ROOT', 'FFTW custom path', os.getenv('FFTW_ROOT', ''), PathVariable.PathAccept), PathVariable('THRUST_ROOT', 'Thrust custom path', os.getenv('THRUST_ROOT', ''), PathVariable.PathAccept), PathVariable('BOOST_ROOT', 'Boost custom path', os.getenv('BOOST_ROOT', ''), PathVariable.PathAccept), PathVariable('CUDA_ROOT', 'Cuda custom path', os.getenv('CUDA_ROOT', ''), PathVariable.PathAccept), # Dependencies provided as submodule get different default PathVariable('GTEST_ROOT', 'Googletest custom path', os.getenv('GTEST_ROOT', '#third-party/googletest/googletest'), PathVariable.PathAccept), PathVariable('PYBIND11_ROOT', 'Pybind11 custom path', os.getenv('PYBIND11_ROOT', '#third-party/pybind11/include'), PathVariable.PathAccept), PathVariable('EXPOLIT_ROOT', 'Expolit custom path', os.getenv('EXPOLIT_ROOT', '#third-party/expolit/include'), PathVariable.PathAccept), # Executables ('CXX', 'Compiler', os.getenv('CXX', 'g++')), ('MPICXX', 'MPI Compiler wrapper', os.getenv('MPICXX', 'mpicxx')), ('py_exec', 'Python executable', 'python3'), # Compiler flags ('CXXFLAGS', 'C++ compiler flags', os.getenv('CXXFLAGS', "")), # Cosmetic BoolVariable('verbose', 'Activate verbosity', False), BoolVariable('color', 'Color the non-verbose compilation output', False), # Tamaas components BoolVariable('build_doc', 'Build documentation', False), BoolVariable('build_tests', 'Build test suite', False), BoolVariable('build_python', 'Build python wrapper', True), # Dependencies BoolVariable('use_googletest', 'Build tests using GTest', False), BoolVariable('use_mpi', 'Builds multi-process parallelism', False), # Distribution options BoolVariable('strip_info', 'Strip binary of added information', False), BoolVariable('build_static_lib', "Build a static libTamaas", False), # Type variables EnumVariable('real_type', 'Type for real precision variables', 'double', allowed_values=('double', 'long double')), EnumVariable('integer_type', 'Type for integer variables', 'int', allowed_values=('int', 'long')), ) # Set variables of environment vars.Update(main_env) help_text = vars.GenerateHelpText(main_env) help_text += """ Commands: scons [build] [options]... Compile Tamaas (and additional modules/tests) scons install [prefix=/your/prefix] [options]... Install Tamaas to prefix scons dev Install symlink to Tamaas python module (useful to development purposes) scons test Run tests with pytest scons doc Compile documentation with Doxygen and Sphinx+Breathe scons archive Create a gzipped archive from source """ # noqa Help(help_text) # Save all options, not just those that differ from default with open('build-setup.conf', 'w') as setup: for option in vars.options: setup.write("# " + option.help + "\n") setup.write("{} = '{}'\n".format(option.key, main_env[option.key])) main_env['should_configure'] = \ not main_env.GetOption('clean') and not main_env.GetOption('help') build_type = main_env['build_type'] build_dir = 'build-' + main_env['build_type'] main_env['build_dir'] = build_dir if main_env['should_configure']: print(main_env.subst("Build type: ${build_type}, Backend: ${backend}, " "FFTW threads: ${fftw_threads}, MPI: ${use_mpi}")) print("Building in " + build_dir) verbose = main_env['verbose'] # Remove colors if not set if not main_env['color']: for key in colors: colors[key] = '' if not verbose: main_env['CXXCOMSTR'] = main_env['SHCXXCOMSTR'] = \ u'{0}[Compiling ($SHCXX)] {1}$SOURCE'.format(colors['green'], colors['end']) main_env['LINKCOMSTR'] = main_env['SHLINKCOMSTR'] = \ u'{0}[Linking] {1}$TARGET'.format(colors['purple'], colors['end']) main_env['ARCOMSTR'] = u'{}[Ar]{} $TARGET'.format(colors['purple'], colors['end']) main_env['RANLIBCOMSTR'] = \ u'{}[Randlib]{} $TARGET'.format(colors['purple'], colors['end']) main_env['PRINT_CMD_LINE_FUNC'] = pretty_cmd_print # Include paths main_env.AppendUnique(CPPPATH=['#/src', '#/src/core', '#/src/mpi', '#/src/bem', '#/src/surface', '#/src/python', '#/src/percolation', '#/src/model', '#/src/model/elasto_plastic', '#/src/solvers', '#/src/gpu', '#/python']) # Changing the shared object extension main_env['SHOBJSUFFIX'] = '.o' # Back to gcc if cuda is activated if main_env['backend'] == "cuda" and "g++" not in main_env['CXX']: raise SCons.Errors.StopError('GCC should be used when compiling with CUDA') # OpenMP flags - compiler dependent omp_flags = { "g++": ["-fopenmp"], "clang++": ["-fopenmp"], "icpc": ["-qopenmp"] } def cxx_alias(cxx): for k in omp_flags.keys(): if k in cxx: return k raise SCons.Errors.StopError('Unsupported compiler: ' + cxx) cxx = cxx_alias(main_env['CXX']) # Setting main compilation flags main_env['CXXFLAGS'] = Split(main_env['CXXFLAGS']) main_env.AppendUnique( CXXFLAGS=Split('-std=c++14 -Wall -Wextra -pedantic'), CPPDEFINES=['TAMAAS_BACKEND=TAMAAS_BACKEND_{}'.format( main_env['backend'].upper() )], ) # Adding OpenMP flags if main_env['backend'] == 'omp': main_env.AppendUnique(CXXFLAGS=omp_flags[cxx]) main_env.AppendUnique(LINKFLAGS=omp_flags[cxx]) else: main_env.AppendUnique(CXXFLAGS=['-Wno-unknown-pragmas']) # Correct bug in clang? if main_env['backend'] == 'omp' and cxx == "clang++": main_env.AppendUnique(LIBS=["atomic"]) elif main_env['backend'] == 'tbb': main_env.AppendUnique(LIBS=['tbb']) # Manage MPI compiler if main_env['use_mpi']: main_env['CXX'] = '$MPICXX' main_env.AppendUnique(CPPDEFINES=['TAMAAS_USE_MPI']) main_env.AppendUnique(CXXFLAGS=['-Wno-cast-function-type']) # Flags and options if main_env['build_type'] == 'debug': main_env.AppendUnique(CPPDEFINES=['TAMAAS_DEBUG']) # Define the scalar types main_env.AppendUnique(CPPDEFINES={'TAMAAS_REAL_TYPE': '${real_type}', 'TAMAAS_INT_TYPE': '${integer_type}'}) # Compilation flags cxxflags_dict = { "debug": Split("-g -O0"), "profiling": Split("-g -O3 -fno-omit-frame-pointer"), "release": Split("-O3") } if main_env['sanitizer'] != 'none': if main_env['backend'] == 'cuda': raise SCons.Errors.StopError( "Sanitizers with cuda are not yet supported!") cxxflags_dict[build_type].append('-fsanitize=${sanitizer}') main_env.AppendUnique(CXXFLAGS=cxxflags_dict[build_type]) main_env.AppendUnique(SHLINKFLAGS=cxxflags_dict[build_type]) main_env.AppendUnique(LINKFLAGS=cxxflags_dict[build_type]) if main_env['should_configure']: detect_dependencies(main_env) # Setting up the python name with version if main_env['build_python']: args = (main_env.subst("${py_exec} -c").split() + ["from distutils.sysconfig import get_python_version;" "print(get_python_version())"]) main_env['py_version'] = bytes(check_output(args)).decode() # Writing information file main_env.Tool('textfile') main_env['SUBST_DICT'] = get_git_subst() # Empty values if requested if main_env['strip_info']: for k in main_env['SUBST_DICT']: main_env['SUBST_DICT'][k] = "" # Substitution of environment file main_env['SUBST_DICT'].update({ '@build_type@': '$build_type', '@build_dir@': abspath(build_dir), '@build_version@': '$version', }) # Environment file content env_content = """export PYTHONPATH=@build_dir@/python:$$PYTHONPATH export LD_LIBRARY_PATH=@build_dir@/src:$$LD_LIBRARY_PATH """ # Writing environment file env_file = main_env.Textfile(join(build_dir, 'tamaas_environment.sh'), env_content) # Building sub-directories def subdir(dir): return SConscript(join(dir, 'SConscript'), variant_dir=join(build_dir, dir), duplicate=True) # Building Tamaas library Export('main_env') subdir('src') build_targets = ['build-cpp', env_file] install_targets = ['install-lib'] # Building Tamaas extra components for dir in ['python', 'tests']: if main_env['build_{}'.format(dir)] and not main_env.GetOption('help'): subdir(dir) build_targets.append('build-{}'.format(dir)) # Building API + Sphinx documentation if requested if main_env['build_doc']: doc_env = main_env.Clone() doc = doc_env.Command('#.phony_doc', '', 'make -C {doc} clean && make -C {doc}' .format(doc=Dir('#/doc'))) if doc_env['build_python']: doc_env.PrependENVPath('PYTHONPATH', doc_env.subst('../${build_dir}/python')) doc_env.Depends(doc, 'build-python') main_env.Alias('doc', doc) else: dummy_command(main_env, 'doc', 'Command "doc" does not do anything' ' without documentation activated ("build_doc=True")') # Define dummy dev command when python is deactivated if not main_env['build_python']: dummy_command(main_env, 'dev', 'Command "dev" does not do anything' + ' without python activated ("build_python=True")') else: install_targets.append('install-python') # Define dummy test command when tests are deactivated if not main_env['build_tests']: dummy_command(main_env, 'test', 'Command "test" does not do anything' + ' without tests activated ("build_tests=True")') # Definition of target aliases, a.k.a. sub-commands main_env.Alias('build', build_targets) # Define proper install targets main_env.Alias('install', install_targets) # Default target is to build stuff main_env.Default('build') # Building a tar archive archive = main_env.Command( 'tamaas-{}.tar.gz'.format(main_env['version']), '', ('tar --exclude-vcs --exclude-vcs-ignores ' '--exclude=third-party/googletest ' '--exclude=third-party/pybind11 ' '--exclude=joss ' '--exclude=".*" ' '-czf $TARGET {}'.format(basename(os.getcwd()))), chdir='..', ) main_env.Alias('archive', archive) diff --git a/doc/sphinx/source/conf.py b/doc/sphinx/source/conf.py index 69fdd99..1a03e5c 100644 --- a/doc/sphinx/source/conf.py +++ b/doc/sphinx/source/conf.py @@ -1,211 +1,210 @@ # -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/master/config # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import re # import sys # sys.path.insert(0, os.path.abspath('.')) import subprocess # -- Project information ----------------------------------------------------- project = 'Tamaas' copyright = "2019-2021, EPFL (École Polytechnique Fédérale de Lausanne)," \ + " Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)" -author = 'Lucas Frérot, Guillaume Anciaux, Valentine Rey, Son Pham-ba, ' \ - 'Jean Fraçois Molinari' +author = 'Lucas Frérot' # Extracting version info with open('../../../SConstruct', 'r') as sconstruct: version_re = re.compile('version=["\']((\d+\.\d+)\.\d+-?(\d+)?)["\']') match = version_re.search(sconstruct.read()) # The short X.Y version version = match.group(2) # The full version, including alpha/beta/rc tags release = match.group(1) # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.mathjax', 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx', 'breathe', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['.templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = None # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} html_logo = '../../icon.svg' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['.static'] +html_static_path = [] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # # html_sidebars = {} # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = 'Tamaasdoc' # -- Options for LaTeX output ------------------------------------------------ latex_engine = 'lualatex' latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'Tamaas.tex', 'Tamaas Documentation', author, 'manual'), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'tamaas', 'Tamaas Documentation', [author], 1) ] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'Tamaas', 'Tamaas Documentation', author, 'Tamaas', 'One line description of project.', 'Miscellaneous'), ] # -- Options for Epub output ------------------------------------------------- # Bibliographic Dublin Core info. epub_title = project # The unique identifier of the text. This can be a ISBN number # or the project homepage. # # epub_identifier = '' # A unique identification for the text. # # epub_uid = '' # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html'] # -- Extension configuration ------------------------------------------------- # If on RTD build, run doxygen on_read_the_docs = os.environ.get('READTHEDOCS') == 'True' if on_read_the_docs: subprocess.call('cd ../../; mkdir -p build/doxygen; ' + 'doxygen doxygen/Doxyfile', shell=True) breathe_projects = { 'tamaas': '../../build/doxygen/xml' } breathe_default_project = 'tamaas' intersphinx_mapping = { 'numpy': ('https://numpy.org/doc/stable/', None), 'scipy': ('https://docs.scipy.org/doc/scipy/reference', None), } diff --git a/doc/sphinx/source/examples.rst b/doc/sphinx/source/examples.rst index 152b0ea..a631d12 100644 --- a/doc/sphinx/source/examples.rst +++ b/doc/sphinx/source/examples.rst @@ -1,44 +1,43 @@ Examples ======== The directory ``examples/`` in Tamaas' root repository contains example scripts dedicated to various aspects of Tamaas: ``statistics.py`` This script generates a rough surface and computes its power spectrum density as well as its autocorrelation function. ``rough_contact.py`` This script generates a rough surface and solves an adhesion-less elastic contact problem. ``adhesion.py`` This script solves a rough contact problem with an exponential energy functional for adhesion. It also shows how to derive an energy functional in python. ``saturation.py`` This script solves a saturated contact problem (i.e. pseudo-plasticity) with a rough surface. ``stresses.py`` This script solves an equilibrium problem with an eigenstrain distribution and a surface traction distribution and writes the output to a VTK file. It demonstrates how the integral operators that Tamaas uses internally for elastic-plastic contact can be used directly in Python. ``plasticity.py`` This script solves an elastoplastic Hertz contact problem with three load steps and writes the result to VTK files. ``pipe_tools/`` - This directory contains scripts that provide a simple interface to elastic contact: - Rough surface generation (``surface``) - Elastic contact solve (``contact``) - Plotting contact solution (``plot``) These can be composed together via standard UNIX pipes. Running them with the ``-h`` option describes all the options and required arguments. diff --git a/doc/sphinx/source/model.rst b/doc/sphinx/source/model.rst index 9ffbd20..7f75ba6 100644 --- a/doc/sphinx/source/model.rst +++ b/doc/sphinx/source/model.rst @@ -1,156 +1,165 @@ Model and integral operators ============================ The class :cpp:class:`tamaas::Model` (and its counterpart :py:class:`Model `) is both a central class in Tamaas and one of the simplest. It mostly serves as holder for material properties, fields and integral operators, and apart from a linear elastic behavior does not perform any computation on its own. Model types ----------- :cpp:class:`tamaas::Model` has a concrete subclass :cpp:class:`tamaas::ModelTemplate` which implements the model function for a given :cpp:type:`tamaas::model_type`: :cpp:enumerator:`tamaas::basic_2d` Model type used in normal frictionless contact: traction and displacement are 2D fields with only one component. :cpp:enumerator:`tamaas::surface_2d` Model type used in frictional contact: traction and displacement are 2D fields with three components. :cpp:enumerator:`tamaas::volume_2d` Model type used in elastoplastic contact: tractions are the same as with :cpp:enumerator:`tamaas::surface_2d` but the displacement is a 3D field. The enumeration values suffixed with ``_1d`` are the one dimensional (line contact) counterparts of the above model types. The domain physical dimension and number of components are encoded in the class :cpp:class:`tamaas::model_type_traits`. Model creation and basic functionality -------------------------------------- The instanciation of a :py:class:`Model ` is done with the :py:class:`ModelFactory ` class and its :py:func:`createModel ` function:: physical_size = [1., 1.] discretization = [512, 512] model = tm.ModelFactory.createModel(tm.model_type.basic_2d, physical_size, discretization) .. warning:: For models of type ``volume_*d``, the first component of the ``physical_size`` and ``discretization`` arrays corresponds to the depth dimension (:math:`z` in most cases). For example:: tm.ModelFactory.createModel(tm.model_type.basic_2d, [0.3, 1, 1], [64, 81, 81]) creates a model of depth 0.3 and surface size 1\ :superscript:`2`, while the number of points is 64 in depth and 81\ :sup:`2` on the surface. This is done for data contiguity reasons, as we do discrete Fourier transforms in the horizontal plane. .. note:: If ran in an MPI context, the method :py:meth:`createModel ` expects the *global* system sizes and discretization of the model. The properties ``E`` and ``nu`` can be used to set the Young's modulus and Poisson ratio respectively:: model.E = 1 model.nu = 0.3 Fields can be easlily accessed with the ``[]`` operator, similar to Python's dictionaries:: surface_traction = model['traction'] # surface_traction is a numpy array To know what fields are available, you can call the :py:class:`list` function on a model (``list(model)``). You can add new fields to a model object with the ``[]`` operator:``model['new_field'] = new_field``, which is convenient for dumping fields that are computed outside of Tamaas. .. note:: The fields ``traction`` and ``displacement`` are always registered in models, and are accessible via :py:attr:`model.traction ` and :py:attr:`model.displacement `. A model can also be used to compute stresses from a strain field:: import numpy as np strain = np.zeros(model.shape + [6]) # Mandel--Voigt notation stress = np.zeros_like(strain) model.applyElasticity(stress, strain) .. tip:: ``print(model)`` gives a lot of information about the model: the model type, shape, registered fields, and more! Integral operators ------------------ Integral operators are a central part of Tamaas: they are carefully designed for performance in periodic system. When a :py:class:`Model ` object is used with contact solvers or with a residual object (for plasticty), the objects using the model register integral operators with the model, so the user typically does not have to worry about creating integral operators. Integral operators are accessed through the :py:attr:`operators ` property of a model object. The ``[]`` operator gives access to the operators, and ``list(model.operators)`` gives the list of registered operators:: # Accessing operator elasticity = model.operators['hooke'] # Applying operator elasticity(strain, stress) # Print all registered operators print(list(model.operators)) .. note:: At model creation, these operators are automatically registered: - ``hooke``: Hooke's elasticity law - ``von_mises``: computes Von Mises stresses - ``deviatoric``: computes the deviatoric part of a stress tensor - ``eigenvalues``: computes the eigenvalues of a symetric tensor field :cpp:class:`Westergaard ` operators are automatically registered when :py:meth:`solveNeumann ` or :py:meth:`solveDirichlet ` are called. Model dumpers ------------- The submodule `tamaas.dumpers` contains a number of classes to save model data into different formats: :py:class:`UVWDumper ` Dumps a model to `VTK `_ format. Requires the `UVW `_ python package which you can install with pip:: pip install uvw This dumper is made for visualization with VTK based software like `Paraview `_. :py:class:`NumpyDumper ` Dumps a model to a compressed Numpy file. :py:class:`H5Dumper ` - Dumps a model to a compressed `HDF5 `_ file. Requires the `h5py `_ package. + Dumps a model to a compressed `HDF5 `_ + file. Requires the `h5py `_ package. Saves separate + files for each dump of a model. + +:py:class:`NetCDFDumper ` + Dumps a model to a compressed `NetCDF + `_ file. Requires the `netCDF4 + `_ package. Saves sequential + dumps of a model into a single file, with the ``frame`` dimension containing + the model dumps. The dumpers are initialzed with a basename and the fields that you wish to write to file (optionally you can set ``all_fields`` to ``True`` to dump all fields in the model). By default, each write operation creates a new file in a separate directory (e.g. :py:class:`UVWDumper ` creates a ``paraview`` directory). To write to a specific file you can use the `dump_to_file` method. Here is a usage example:: from tamaas.dumpers import UVWDumper, H5Dumper # Create dumper uvw_dumper = UVWDumper('rough_contact_example', 'stress', 'plastic_strain') # Dump model uvw_dumper << model # Or alternatively model.addDumper(H5Dumper('rough_contact_archive', all_fields=True)) model.addDumper(uvw_dumper) model.dump() -The last ``model.dump()`` call will call both dumpers. The resulting files will have the following hierachy:: +The last ``model.dump()`` call will trigger all dumpers. The resulting files will have the following hierachy:: ./paraview/rough_contact_example_0000.vtr ./paraview/rough_contact_example_0001.vtr ./hdf5/rough_contact_archive_0000.h5 -.. note:: +.. important:: Currently, only :py:class:`H5Dumper ` supports parallel output with MPI. diff --git a/doc/sphinx/source/performance.rst b/doc/sphinx/source/performance.rst index 5fb0e3f..973c400 100644 --- a/doc/sphinx/source/performance.rst +++ b/doc/sphinx/source/performance.rst @@ -1,181 +1,207 @@ Performance =========== Parallelism ----------- Tamaas implements shared-memory parallelism using `thrust -`_ and `OpenMP `_ -[1]_. The number of threads can be controlled with -the ``OMP_NUM_THREADS`` environment variable, or alternatively to call the -:func:`initialize ` function with the desired number -of threads. The default behavior is to use as many threads as available cores on -the system. +`_. The Thrust backend can be controlled with +the following values of the ``backend`` build option: + +``omp`` + Thrust uses its OpenMP backend. The number of threads is controlled by + OpenMP. + +``cpp`` + Thurst does not run in threads (i.e. sequential). This is the recommanded + option if running multiple MPI tasks. + +``tbb`` + Thrust uses its `TBB + `_ backend. Note + that this option is not fully supported by Tamaas. + +.. tip:: When using the OpenMP or TBB backend, the number of threads can be + manually controlled by the :py:func:`initialize ` + function. When OpenMP is select for the backend, the environment variable + ``OMP_NUM_THREADS`` can also be used to set the number of threads. + +FFTW has its own system for thread-level parallelism, which can be controlled +via the ``fftw_threads`` option: + +``none`` + FFTW does not use threads. + +``threads`` + FFTW uses POSIX/Win32 threads for parallelism. + +``omp`` + FFTW uses OpenMP. + +.. note:: As with the Thrust backend, the number of threads for FFTW can be + controlled with :py:func:`initialize `. + +Finally, the boolean variable ``use_mpi`` controls wheter Tamaas is compiled +with MPI-parallelism. If yes, Tamaas will be linked against ``libfftw3_mpi`` +regardless of the thread model. Multi-process parallelism ^^^^^^^^^^^^^^^^^^^^^^^^^ Distributed memory parallelism in Tamaas is implemented with `MPI `_. Due to the bottleneck role of the fast-Fourier transform in Tamaas' core routines, the data layout of Tamaas is that of `FFTW `_. Tamaas is somewhat affected by limitations of FFTW, and MPI only works on systems with a 2D boundary, i.e. ``basic_2d``, ``surface_2d`` and ``volume_2d`` model types (which are the most important anyways, since rough contact mechanics can yield different scaling laws in 1D). MPI support in Tamaas is still experimental, but the following parts are tested: - Rough surface generation - Surface statistics computation - Elastic normal contact - Elastic-plastic contact (with :py:class:`DFSANECXXSolver `). Adapting existing scripts to work in an MPI context can require some work, especially if said scripts rely on numpy and scipy for pre- and post-processing (e.g. constructing a parabolic surface for hertzian contact, computing the total contact area). It is the user's responsibility to make the changes necessary, but Tamaas provides some convenience functions to make this task easier. In the module :py:mod:`mpi `, the function :py:func:`local_shape ` gives the 2D shape of the local data if given the global 2D shape (its counterpart :py:func:`global_shape ` does the exact opposite), while :py:func:`local_offset ` gives the offset of the local data in the global :math:`x` dimension. These two functions mirror FFTW's own data distribution `functions `_. .. figure:: figures/mpi_data_distribution.svg :align: center :width: 75% 2D Data distribution scheme from FFTW. ``N0`` and ``N1`` are the number of points in the :math:`x` and :math:`y` directions respectively. The array ``local_N0``, indexed by the process rank, give the local size of the :math:`x` dimension. The :py:func:`local_offset ` function gives the offset in :math:`x` for each process rank. The :py:mod:`mpi ` module also contains a function :py:func:`sequential ` whose return value is meant to be used as a context manager. Within the sequential context the default communicator is ``MPI_COMM_SELF`` instead of ``MPI_COMM_WORLD``. In addition to the low-level functions in :py:mod:`mpi `, many functions in Tamaas do not require special treatment, e.g. the statistics functions. Integration algorithm --------------------- In its implementation of the volume integral operators necessary for elastic-plastic solutions, Tamaas differenciates two way of computing the intermediate integral along :math:`z` in the partial Fourier domain: - Cutoff integration: because the intermediate integral involves kernels of the form :math:`\exp(q(x-y))`, it is easy to truncate the integral when :math:`x` and :math:`y` are far apart, especially for large values of :math:`q`. This changes the complexity of the intermediate integral from :math:`O(N_1N_2N_3^2)` (the naive implementation) to :math:`O(\sqrt{N_1^2 + N_2^2}N_3^2)`. - Linear integration: this method relies on a separation of variables :math:`\exp(q(x-y)) = \exp(qx)\cdot\exp(-qy)`. This allows to break the dependency in :math:`N_3^2` of the number of operations, so that the overall complexity of the intermediate integral is :math:`O(N_1N_2N_3)`. -Details on both algorithms can be found in [2]_. Tamaas uses linear integration +Details on both algorithms can be found in [1]_. Tamaas uses linear integration by default because it is faster in many cases without introducing a truncation error. Unfortunatly, it has a severe drawback when considering systems with a fine surface discretization: due to :math:`q` increasing with the number of points on the surface, the separated terms :math:`\exp(qx)` and :math:`\exp(-qy)` may overflow and underflow respectively. Tamaas will warn if that is the case, and users have two options to remedy the situation: - Change the integration method by calling :func:`setIntegrationMethod ` with the desired :class:`integration_method ` on the :class:`Residual ` object you use in the computation. - Compile Tamaas with the option ``real_type='long double'``. To make manipulation of numpy arrays easier, a :class:`dtype` is provided in the :py:mod:`tamaas` module which can be used to create numpy arrays compatible with Tamaas' floating point type (e.g. ``x = np.linspace(0, 1, dtype=tamaas.dtype)``) Both these options negatively affect the performance, and it is up to the user to select the optimal solution for their particular use case. Computational methods --------------------- Tamaas uses specialized numerical methods to efficiently solve elastic and elastoplastic periodic contact problems. Using a boundary integral formulation and a half-space geometry for the former allow (a) the focus of computational power to the contact interface since the bulk response can be represented exactly, (b) the use of the fast-Fourier transform for the computation of convolution integrals. In conjunction with a boundary integral formulation of the bulk state equations, a conjugate gradient approach is used to solve the contact problem. -.. note:: - - The above methods are state-of-the-art in the domain of rough surface +.. note:: The above methods are state-of-the-art in the domain of rough surface contact. Below are selected publications detailing the methods used in elastic contact with and without adhesion: - Boundary integral formulation: - Stanley and Kato (`J. of Tribology, 1997 `_) + - Conjugate Gradient: - Polonsky and Keer (`Wear, 1999 `_) - Rey, Anciaux and Molinari (`Computational Mechanics, 2017 `_) + - Frictional contact: - Condat (`J. of Optimization Theory and Applications, 2012 `_) + For elastic-plastic contact, Tamaas uses a similar approach by implementing a *volume* integral formulation of the bulk equilibrium equations. Thanks to kernel expressions that are directly formulated in the Fourier domain, the method reduces the algorithmic complexity, memory requirements and sampling errors compared to traditional volume integral methods (Frérot, Bonnet, Anciaux and Molinari, `Computer Methods in Applied Mechanics and Engineering, 2019 `_, `arXiv:1811.11558 `_). The figure below shows a comparison of run times for an elasticity problem (only a single solve step) between Tamaas and `Akantu `_, a high-performance FEM code using the direct solver `MUMPS `_. .. figure:: figures/complexity.svg :align: center :width: 75% Comparison of run times between the volume integral implementation (with cutoff integration) of Tamaas and an FEM solve step with a Cholesky factorization performed by Akantu+MUMPS. :math:`N` is the total number of points. Further discussion about the elastic-plastic solver implemented in Tamaas can be found in Frérot, Bonnet, Anciaux and Molinari, (`Computer Methods in Applied Mechanics and Engineering, 2019 `_, `arXiv:1811.11558 `_). -.. [1] Thrust uses a backend system for thread-level parallelism, which can be - changed to use `TBB - `_ - in the build options (``build-setup.conf``). You can experiment with it, - but we do not formally support this backend. Note that this does not - affect FFTW, which always uses OpenMP. - - -.. [2] L. Frérot, “Bridging scales in wear modeling with volume integral +.. [1] L. Frérot, “Bridging scales in wear modeling with volume integral methods for elastic-plastic contact,” École Polytechnique Fédérale de Lausanne, 2020 (Section 2.3.2). `10.5075/epfl-thesis-7640 `_ diff --git a/doc/sphinx/source/quickstart.rst b/doc/sphinx/source/quickstart.rst index fa8e46d..13f1420 100644 --- a/doc/sphinx/source/quickstart.rst +++ b/doc/sphinx/source/quickstart.rst @@ -1,140 +1,183 @@ Quickstart ---------- Here is a quick introduction to get you started with Tamaas. Installation from PyPI ^^^^^^^^^^^^^^^^^^^^^^ If you have a Linux system, you can simply run ``pip(3) install tamaas``. Note however that there may be due to compatibility reasons, this version of Tamaas is not built with parallel capabilities. So if you want parallelism, or encounter an issue with the `PyPI package `_, please compile from source. Installation from source ^^^^^^^^^^^^^^^^^^^^^^^^ First make sure the following dependencies are installed for Tamaas: - a **C++ compiler** with full **C++14** and **OpenMP** support - **SCons** (python build system) -- **FFTW3** compiled with **OpenMP** support +- **FFTW3** - **thrust** (1.9.2+) - **boost** (pre-processor) -- **python 3+** (probably works with python 2, but it is not tested) with - **numpy** +- **python 3+** with **numpy** - **pybind11** (included as submodule) - **expolit** (included as submodule) Optional dependencies are: +- an MPI implementation +- **FFTW3** with MPI/threads/OpenMP (your pick) support - **scipy** (for nonlinear solvers) -- **uvw** (for dumpers) +- **uvw**, **h5py**, **netCDF4** (for dumpers) - **googletest** and **pytest** (for tests) - **Doxygen** and **Sphinx** (for documentation) .. tip:: On a HPC environment, use the following variables to specify where the dependencies are located: - ``FFTW_ROOT`` - ``THRUST_ROOT`` - ``BOOST_ROOT`` - ``PYBIND11_ROOT`` - ``GTEST_ROOT`` .. note:: You can use the provided Dockerfile to build an image with the external dependencies installed. You should first clone the git repository with the submodules that are dependencies to tamaas (`expolit `_, `pybind11 `_ and `googletest `_):: git clone --recursive https://c4science.ch/source/tamaas.git The build system uses `SCons `_. In order to compile Tamaas with the default options:: scons After compiling a first time, you can edit the compilation options in the file ``build-setup.conf``, or alternatively supply the options directly in the -command line:: +command line: + +.. code-block:: bash scons option=value [...] To get a list of **all** build options and their possible values, you can run ``scons -h``. You can run ``scons -H`` to see the SCons-specific options (among them ``-j n`` executes the build with ``n`` threads and ``-c`` cleans the build). Note that the build is aware of the ``CXX`` and ``CXXFLAGS`` environment variables. -Once compiled, you can install the python module with:: +Once compiled, you can install the python module with: + +.. code-block:: bash scons install prefix=/your/prefix The above command automatically calls ``pip(3)`` if it is installed, and falls back on ``setuptools`` otherwise. If you do not want to install Tamaas, you can use the following command:: scons dev This creates a symlink in ``~/.local/lib//site-packages`` and is equivalent to ``pip(3) install -e`` or ``./setup.py develop``. You can check -that everything is working fine with:: +that everything is working fine with: + +.. code-block:: bash python3 -c 'import tamaas; print(tamaas)' Using Docker ............ The Tamaas repository provides a `Dockerfile` that describes an appropriate -build environment. You can use it in the following way:: +build environment. You can use it in the following way: - # Build the image, run it and mount the tamaas repository - docker build -t tamaas_build . - docker run -v $PWD:/app/tamaas -it tamaas_build bash +.. code-block:: bash - # Once in the image shell: compile and install - cd /app/tamaas - scons - scons dev + # Build the image, run it and mount the tamaas repository + docker build -t tamaas_build . + docker run -v $PWD:/app/tamaas -it tamaas_build bash + + # Once in the image shell: compile and install + cd /app/tamaas + scons + scons dev The image also has some of the dependencies required to run the examples below (matplotlib, uvw). +Important build options +....................... + +Here are a selected few important compilation options: + +``build_type`` + Controls the type of build, which essentially changes the optimisation level + (``-O0`` for ``debug``, ``-O2`` for ``profiling`` and ``-O3`` for + ``release``) and the amount of debug information. Default type is ``release``. + +``CXX`` + Compiler (uses the environment variable ``CXX`` by default). + +``CXXFLAGS`` + Compiler flags (uses the environment variable ``CXXFLAGS`` by + default). Useful to add tuning flags (e.g. ``-march=native -mtune=native`` + for GCC or ``-xHOST`` for Intel), or additional optimisation flags (e.g. + ``-flto`` for link-time optimization). + +``backend`` + Controls the Thrust parallelism backend. Defaults to ``omp`` for OpenMP. + +``fftw_threads`` + Controls the FFTW thread model. Defaults to ``omp`` for OpenMP. + +``use_mpi`` + Activates MPI-parallelism. + +More details on the above options can be found in :doc:`performance`. + Building the docs ^^^^^^^^^^^^^^^^^ Documentation is built using ``scons doc``. Make sure to have the correct dependencies installed (they are already provided in the Docker image). Running the contact pipe tools ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In the ``examples/pipe_tools`` folder, you will find three scripts that can be used to explore the mechanics of elastic rough contact: - ``surface`` generates randomly rough surfaces (see :doc:`rough_surfaces`) - ``contact`` solves a contact problem with a surface read from ``stdin`` (see :doc:`contact`) - ``plot`` simply plots the surface tractions and displacements read from ``stdin`` -Here's a sample command line for you to try out:: +Here's a sample command line for you to try out: + +.. code-block:: bash ./surface --cutoffs 2 4 64 --size 512 512 --hurst 0.8 | ./contact 1 | ./plot Check out the help of each script for a description of the arguments. Running the tests ^^^^^^^^^^^^^^^^^ -You need to activate the ``build_tests`` option to compile the tests:: +You need to activate the ``build_tests`` option to compile the tests: + +.. code-block:: bash scons build_tests=True Tests can then be run with the ``scons test`` command. Make sure you have `pytest `_ installed. diff --git a/third-party/expolit b/third-party/expolit index a31a8ec..5f68da4 160000 --- a/third-party/expolit +++ b/third-party/expolit @@ -1 +1 @@ -Subproject commit a31a8ecdb3cd8f903744d2c39bd282f18cf0bada +Subproject commit 5f68da4ee972156d8676cd645e25fab984ecc75a