diff --git a/AUTHORS b/AUTHORS index 575d7f1..0188fbc 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,4 +1,5 @@ +- Lucas Frérot - Guillaume Anciaux -- Lucas Frérot -- Son Pham-Ba - Valentine Rey +- Son Pham-Ba +- Jean-François Molinari \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 21f3e14..fc66491 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,37 +1,37 @@ FROM debian:stable-slim -MAINTAINER Lucas Frérot +MAINTAINER Lucas Frérot # Add contrib and non-free RUN sed -i 's/main/main contrib non-free/' /etc/apt/sources.list # Install any needed packages from ubuntu repos RUN apt-get -qq update && apt-get install -y \ curl \ gcc \ git \ libboost-dev \ libpython3-dev \ libthrust-dev \ libfftw3-dev \ libfftw3-mpi-dev \ python3 \ python3-dev \ python3-numpy \ python3-pip \ python3-scipy \ python3-h5py \ python3-netcdf4 \ python3-phabricator \ python3-click \ python3-yaml \ python3-matplotlib \ python3-sphinx \ python3-breathe \ python3-sphinx-rtd-theme \ python3-mpi4py \ doxygen \ scons \ && rm -rf /var/lib/apt/lists/* RUN pip3 install pytest uvw diff --git a/INFOS.py b/INFOS.py index b2122e5..11d1f5b 100644 --- a/INFOS.py +++ b/INFOS.py @@ -1,34 +1,34 @@ # -*- mode:python; coding: utf-8 -*- "Defines the information to be used throughout the builds" from collections import namedtuple import versioneer TamaasInfo = namedtuple('TamaasInfo', ['version', 'authors', 'maintainer', 'email', 'copyright', 'description']) TAMAAS_INFOS = TamaasInfo( version=versioneer.get_version(), - authors=[ + 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', + email='lfrerot1@jhu.edu', copyright=( u"Copyright (©) 2016-2021 EPFL " u"(École Polytechnique Fédérale de Lausanne), " u"Laboratory (LSMS - Laboratoire de Simulation en " u"Mécanique des Solides)" ), description='A high-performance library for periodic rough surface contact', ) diff --git a/Jenkinsfile b/Jenkinsfile index 548905d..b0d1cb8 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,136 +1,136 @@ pipeline { parameters {string(defaultValue: '', description: 'api-token', name: 'API_TOKEN') string(defaultValue: '', description: 'Token for readthedocs', name: 'RTD_TOKEN') string(defaultValue: '', description: 'buildable phid', name: 'BUILD_TARGET_PHID') string(defaultValue: '', description: 'Commit id', name: 'COMMIT_ID') string(defaultValue: '', description: 'Diff id', name: 'DIFF_ID') string(defaultValue: 'PHID-PROJ-gbo56hpf2y5bi7t5jusk', description: 'ID of the project', name: 'PROJECT_ID') } environment { PHABRICATOR_HOST = 'https://c4science.ch/api/' PYTHONPATH = sh returnStdout: true, script: 'echo ${WORKSPACE}/tests/ci/script/' } agent { dockerfile { additionalBuildArgs '--tag tamaas-environment' } } options { timeout(time: 1, unit: 'HOURS') } stages { stage('SCM Checkout') { steps { checkout scm: [ $class: 'GitSCM', branches: scm.branches, extensions: [[ $class: 'SubmoduleOption', recursiveSubmodules: true, ]], userRemoteConfigs: scm.userRemoteConfigs ] } } stage('Configure') { steps { sh '''#!/usr/bin/env bash echo "py_exec = \'python3\'" > build-setup.conf echo "build_python = \'true\'" >> build-setup.conf echo "build_tests = \'true\'" >> build-setup.conf echo "use_googletest = \'true\'" >> build-setup.conf echo "use_mpi = \'true\'" >> build-setup.conf echo "verbose = \'true\'" >> build-setup.conf''' } } stage('Compile') { steps { sh '''#!/usr/bin/env bash set -o pipefail rm -rf .sconf_temp .sconsign.dblite build-release scons | tee compilation.log''' } post { always { uploadArtifact('config.log', 'Configure log') uploadArtifact('compilation.log', 'Compilation log') } } } stage('Run tests') { steps { sh '''#!/usr/bin/env bash set -o pipefail PYTHONPATH=$PWD/build-release/python/ python3 -m pytest -vvv --durations=0 --junitxml=results.xml build-release/tests | tee tests.log''' } post { always { uploadArtifact('tests.log', 'Tests log') } } } post { always { createArtifact("results.xml") junit 'results.xml' step([$class: 'XUnitBuilder', thresholds: [ [$class: 'SkippedThreshold', failureThreshold: '0'], [$class: 'FailedThreshold', failureThreshold: '0']], tools: [[$class: 'GoogleTestType', pattern: 'build-release/tests/gtest_results.xml']]]) } success { trigger_rtd() passed_hbm() } failure { failed_hbm() emailext( body: '''${SCRIPT, template="groovy-html.template"}''', mimeType: 'text/html', subject: "[Jenkins] ${currentBuild.fullDisplayName} Failed", recipientProviders: [[$class: 'CulpritsRecipientProvider']], - to: 'lucas.frerot@protonmail.com', + to: 'lfrerot1@jhu.edu', attachLog: true, compressLog: true) } } } } def createArtifact(filename) { sh "./tests/ci/scripts/hbm send-uri -k 'Jenkins URI' -u ${BUILD_URL} -l 'View Jenkins result'" sh "./tests/ci/scripts/hbm send-junit-results -f ${filename}" } def uploadArtifact(artifact, name) { archiveArtifacts artifacts: "${artifact}", fingerprint: true sh "./tests/ci/scripts/hbm upload-file -f ${artifact} -n \"${name}\" -v ${PROJECT_ID}" } def failed_hbm() { sh "./tests/ci/scripts/hbm failed" } def passed_hbm() { sh "./tests/ci/scripts/hbm passed" } def trigger_rtd() { sh """ set -x curl -X POST -d "token=${RTD_TOKEN}" https://readthedocs.org/api/v2/webhook/tamaas/106141/ """ } diff --git a/SConstruct b/SConstruct index 4deedac..e0ad13d 100644 --- a/SConstruct +++ b/SConstruct @@ -1,484 +1,481 @@ # -*- mode:python; coding: utf-8 -*- # vim: set ft=python: - -# @file -# 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 sys import os from subprocess import check_output # Import below not strictly necessary, but good for pep8 from SCons.Script import ( EnsurePythonVersion, EnsureSConsVersion, Help, Environment, Variables, EnumVariable, PathVariable, BoolVariable, ListVariable, Split, Export, Dir, ) from SCons.Errors import StopError from SCons import __version__ as scons_version from version import get_git_subst from detect import ( FindFFTW, FindBoost, FindThrust, FindCuda, FindExpolit, FindPybind11 ) from INFOS import TAMAAS_INFOS # ------------------------------------------------------------------------------ EnsurePythonVersion(2, 7) EnsureSConsVersion(2, 4) # ------------------------------------------------------------------------------ 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') if main_env['use_fftw']: FindFFTW(env, fftw_components, precision=env['real_type']) if main_env['use_cuda']: FindCuda(env) FindBoost(env, ['boost/preprocessor/seq.hpp']) # Use thrust shipped with cuda if cuda is requested thrust_var = 'CUDA_ROOT' if env['use_cuda'] else 'THRUST_ROOT' FindThrust(env, env['backend'], thrust_var) if env['build_python']: FindPybind11(env) FindExpolit(env) def subdir(env, dir): "Building a sub-directory" return env.SConscript(env.File('SConscript', dir), variant_dir=env.Dir(dir, env['build_dir']), duplicate=True) def print_build_info(env): info = ("-- Tamaas ${version}\n" + "-- SCons {} (Python {}.{})\n".format(scons_version, sys.version_info.major, sys.version_info.minor) + "-- Build type: ${build_type}\n" + "-- Thrust backend: ${backend}\n" + ("-- FFTW threads: ${fftw_threads}\n" if env['use_fftw'] else '') + "-- MPI: ${use_mpi}\n" + "-- Build directory: ${build_dir}\n" + ("-- Python version (bindings): $py_version" if env['build_python'] else '')) print(env.subst(info)) # ------------------------------------------------------------------------------ # 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_INFOS._asdict().items(): main_env[k] = v main_env['COLOR_DICT'] = colors main_env.AddMethod(subdir, 'SubDirectory') # 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', 'cuda'), 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), # Documentation ListVariable('doc_builders', 'Generated documentation formats', default='html', names=Split("html man")), # TODO include latex # 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.replace('\n', '\n# ') + "\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-${build_type}' main_env['build_dir'] = main_env.Dir(build_dir) # 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() 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 main_env['INSTALLSTR'] = \ u'{}[Installing] {}$SOURCE to $TARGET'.format(colors['blue'], colors['end']) # Include paths main_env.AppendUnique(CPPPATH=['#/src', '#/src/core', '#/src/bem', '#/src/surface', '#/src/percolation', '#/src/model', '#/src/model/elasto_plastic', '#/src/solvers', '#/src/gpu', '#/python']) # Changing the shared object extension main_env['SHOBJSUFFIX'] = '.o' # Variables for clarity main_env['use_cuda'] = main_env['backend'] == "cuda" main_env['use_fftw'] = not main_env['use_cuda'] main_env['use_mpi'] = main_env['use_mpi'] and not main_env['use_cuda'] if not main_env['use_fftw']: main_env['fftw_threads'] = 'none' # Back to gcc if cuda is activated if main_env['use_cuda'] and "g++" not in main_env['CXX']: raise StopError('GCC should be used when compiling with CUDA') # Printing some build infos if main_env['should_configure']: print_build_info(main_env) # 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 StopError('Unsupported compiler: ' + cxx) cxx = cxx_alias(main_env['CXX']) # Setting main compilation flags main_env['CXXFLAGS'] = Split(main_env['CXXFLAGS']) main_env['LINKFLAGS'] = main_env['CXXFLAGS'] main_env.AppendUnique( CXXFLAGS=Split('-std=c++14 -Wall -Wextra'), CPPDEFINES={ 'TAMAAS_LOOP_BACKEND': 'TAMAAS_LOOP_BACKEND_${backend.upper()}', 'TAMAAS_FFTW_BACKEND': 'TAMAAS_FFTW_BACKEND_${fftw_threads.upper()}' }, ) if main_env['backend'] != 'cuda': main_env.AppendUnique(CXXFLAGS=['-pedantic']) # 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 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']: basic_checks(main_env) detect_dependencies(main_env) # 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@': '${build_dir.abspath}', '@build_version@': '$version', '@backend@': '$backend', }) # 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( main_env.File('tamaas_environment.sh', main_env['build_dir']), env_content) # Default targets build_targets = ['build-cpp', env_file] install_targets = ['install-lib'] if main_env._get_major_minor_revision(scons_version)[0] >= 4: main_env.Tool('compilation_db') main_env.CompilationDatabase(PRINT_CMD_LINE_FUNC=pretty_cmd_print) # Building Tamaas library Export('main_env') main_env.SubDirectory('src') # Building Tamaas extra components for dir in ['python', 'tests']: if main_env['build_{}'.format(dir)] and not main_env.GetOption('help'): main_env.SubDirectory(dir) build_targets.append('build-{}'.format(dir)) # Building API + Sphinx documentation if requested if main_env['build_doc']: main_env.SubDirectory('doc') main_env.Alias('doc', 'build-doc') install_targets.append('install-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-${version}.tar.gz', '', ('git archive ' '--format=tar.gz ' '--prefix=tamaas/ ' '-o $TARGET HEAD'), ) main_env.Alias('archive', archive) diff --git a/doc/SConscript b/doc/SConscript index ffd7b3d..93dee47 100644 --- a/doc/SConscript +++ b/doc/SConscript @@ -1,97 +1,94 @@ # -*- mode:python; coding: utf-8 -*- # vim: set ft=python: - -# @file -# 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 . from __future__ import print_function from SCons.Script import Import, Glob, Dir, File def add_sources(source_list, dirname, fnames): source_list += Dir(dirname).glob('*.hh') source_list += Dir(dirname).glob('*.cpp') Import('main_env') Import('libTamaas') doc_env = main_env.Clone() doc_env['man_section'] = 7 # Generating Doxyfile doxygen_verbose = {True: "NO", False: "YES"} doxyfile_target = doc_env.Substfile('doxygen/Doxyfile', 'doxygen/Doxyfile.in', SUBST_DICT={ '@version@': '$version', '@build_dir@': Dir('doxygen').path, '@logo@': File('icon.svg').path, '@src_dir@': Dir('#src').abspath, '@verbose@': doxygen_verbose[doc_env["verbose"]], }) # Generate Doxygen API documentation doc_env.Tool('doxygen') doxygen_target = doc_env.Doxygen('doxygen/xml/index.xml', [doxyfile_target, 'icon.svg']) # Adding all source files as dependencies sources = [] Dir('#src').walk(add_sources, sources) doc_env.Depends(doxygen_target, sources) # Generate Sphinx User documentation sphinx_targets_map = { "html": "sphinx/html/index.html", "man": "sphinx/tamaas.${man_section}", "latex": "sphinx/latex/Tamaas.tex" } doc_env.Tool('sphinx') doc_env['IMPLICIT_COMMAND_DEPENDENCIES'] = 0 sphinx_sources = [Glob('sphinx/source/*'), Glob('sphinx/source/figures/*')] sphinx_targets = { builder: doc_env.Sphinx(sphinx_targets_map[builder], sphinx_sources) for builder in doc_env['doc_builders'] } for target in sphinx_targets.values(): doc_env.Depends(target, doxygen_target) # Alias for both docs doc_targets = [doxygen_target] + list(sphinx_targets.values()) main_env.Alias('build-doc', doc_targets) # Install target for documentation share = Dir('share', doc_env['prefix']) doc_install = [] sphinx_prefix_map = {} if "html" in doc_env['doc_builders']: doc_install.append(doc_env.Install(target=share.Dir('doc').Dir('tamaas'), source=sphinx_targets['html'][0].dir)) if "man" in doc_env['doc_builders']: doc_install.append(doc_env.Install( target=share.Dir('man') .Dir(doc_env.subst('man${man_section}')), source=sphinx_targets['man'])) main_env.Alias('install-doc', doc_install) diff --git a/examples/adhesion.py b/examples/adhesion.py index 84bcad8..876e1f2 100644 --- a/examples/adhesion.py +++ b/examples/adhesion.py @@ -1,122 +1,120 @@ #!/usr/bin/env python3 -# @file -# 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 . import tamaas as tm import matplotlib.pyplot as plt import numpy as np import argparse from matplotlib.colors import ListedColormap class AdhesionPython(tm.Functional): """ Functional class that extends a C++ class and implements the virtual methods """ def __init__(self, rho, gamma): tm.Functional.__init__(self) self.rho = rho self.gamma = gamma def computeF(self, gap, pressure): return -self.gamma * np.sum(np.exp(-gap / self.rho)) def computeGradF(self, gap, gradient): gradient += self.gamma * np.exp(-gap / self.rho) / self.rho parser = argparse.ArgumentParser() parser.add_argument('--local-functional', dest="py_adh", action="store_true", help="use the adhesion functional written in python") args = parser.parse_args() # Surface size n = 1024 # Surface generator sg = tm.SurfaceGeneratorFilter2D([n, n]) sg.random_seed = 0 # Spectrum sg.spectrum = tm.Isopowerlaw2D() # Parameters sg.spectrum.q0 = 16 sg.spectrum.q1 = 16 sg.spectrum.q2 = 64 sg.spectrum.hurst = 0.8 # Generating surface surface = sg.buildSurface() surface /= n plt.imshow(surface) plt.title('Rough surface') # Creating model model = tm.ModelFactory.createModel(tm.model_type.basic_2d, [1., 1.], [n, n]) # Solver solver = tm.PolonskyKeerRey(model, surface, 1e-12, tm.PolonskyKeerRey.gap, tm.PolonskyKeerRey.gap) adhesion_params = { "rho": 2e-3, "surface_energy": 2e-5 } # Use the python derived from C++ functional class if args.py_adh: adhesion = AdhesionPython(adhesion_params["rho"], adhesion_params["surface_energy"]) # Use the C++ class else: adhesion = tm.ExponentialAdhesionFunctional(surface) adhesion.parameters = adhesion_params solver.addFunctionalTerm(adhesion) # Solve for target pressure g_target = 5e-2 solver.solve(g_target) tractions = model.traction plt.figure() plt.imshow(tractions) plt.colorbar() plt.title('Contact tractions') plt.figure() zones = np.zeros_like(tractions) tol = 1e-6 zones[tractions > tol] = 1 zones[tractions < -tol] = -1 plt.imshow(zones, cmap=ListedColormap(['white', 'gray', 'black'])) plt.colorbar(ticks=[-2/3, 0, 2/3]).set_ticklabels([ 'Adhesion', 'No Contact', 'Contact' ]) plt.title('Contact and Adhesion Zones') plt.show() diff --git a/examples/pipe_tools/contact b/examples/pipe_tools/contact index 65b6e87..eb6a325 100755 --- a/examples/pipe_tools/contact +++ b/examples/pipe_tools/contact @@ -1,59 +1,58 @@ #!/usr/bin/env python3 # -*- mode: python; coding: utf-8 -*- # vim: set ft=python: """ Read from stdin a surface profile and solve an elastic contact problem with load given as an argument. """ import argparse import sys import tamaas as tm import numpy as np from tamaas.dumpers import NumpyDumper __author__ = "Lucas Frérot" __copyright__ = ( "Copyright (©) 2019-2021, EPFL (École Polytechnique Fédérale de Lausanne)," "\nLaboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)" ) -__license__ = "AGPL" -__email__ = "lucas.frerot@gmail.com" +__license__ = "SPDX-License-Identifier: AGPL-3.0-or-later" tm.set_log_level(tm.LogLevel.error) parser = argparse.ArgumentParser( description="Compute the elastic contact solution with a given surface") parser.add_argument("--input", "-i", help="Rough surface file (default read from stdin)") parser.add_argument("--tol", type=float, default=1e-12, help="Solver tolerance") parser.add_argument("load", type=float, help="Applied average pressure") args = parser.parse_args() if not args.input: input = sys.stdin else: input = args.input surface = np.loadtxt(input) discretization = surface.shape system_size = [1., 1.] model = tm.ModelFactory.createModel(tm.model_type.basic_2d, system_size, discretization) solver = tm.PolonskyKeerRey(model, surface, args.tol) solver.solve(args.load) dumper = NumpyDumper('numpy', 'traction', 'displacement') dumper.dump_to_file(sys.stdout.buffer, model) diff --git a/examples/pipe_tools/plot b/examples/pipe_tools/plot index 0fa79ab..d59c2f6 100755 --- a/examples/pipe_tools/plot +++ b/examples/pipe_tools/plot @@ -1,50 +1,49 @@ #!/usr/bin/env python3 # -*- mode: python; coding: utf-8 -*- # vim: set ft=python: """ Read Numpy data from standard input, plot contact tractions and displacements. """ import sys import io import matplotlib.pyplot as plt import numpy as np __author__ = "Lucas Frérot" __copyright__ = ( "Copyright (©) 2019-2021, EPFL (École Polytechnique Fédérale de Lausanne)," "\nLaboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)" ) -__license__ = "AGPL" -__email__ = "lucas.frerot@gmail.com" +__license__ = "SPDX-License-Identifier: AGPL-3.0-or-later" def load_stream(stream): """ Load numpy from binary stream (allows piping) Code from https://gist.github.com/CMCDragonkai/3c99fd4aabc8278b9e17f50494fcc30a """ np_magic = stream.read(6) # use the sys.stdin.buffer to read binary data np_data = stream.read() # read it all into an io.BytesIO object return io.BytesIO(np_magic + np_data) fig, (ax_traction, ax_displacement) = plt.subplots(1, 2) ax_traction.set_title('Traction') ax_displacement.set_title('Displacement') with load_stream(sys.stdin.buffer) as f_np: data = np.load(f_np) ax_traction.imshow(data['traction']) ax_displacement.imshow(data['displacement']) fig.set_size_inches(10, 6) fig.tight_layout() plt.show() diff --git a/examples/pipe_tools/surface b/examples/pipe_tools/surface index 8cccabf..8aecd5d 100755 --- a/examples/pipe_tools/surface +++ b/examples/pipe_tools/surface @@ -1,78 +1,77 @@ #!/usr/bin/env python3 # -*- mode: python; coding: utf-8 -*- # vim: set ft=python: """ Create a random self-affine rough surface from command line parameters. """ import argparse import sys import time import tamaas as tm import numpy as np __author__ = "Lucas Frérot" __copyright__ = ( "Copyright (©) 2019-2021, EPFL (École Polytechnique Fédérale de Lausanne)," "\nLaboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)" ) -__license__ = "AGPL" -__email__ = "lucas.frerot@gmail.com" +__license__ = "SPDX-License-Identifier: AGPL-3.0-or-later" parser = argparse.ArgumentParser( description="Generate a self-affine rough surface" ) parser.add_argument("--cutoffs", "-K", nargs=3, type=int, help="Long, rolloff and short wavelength cutoffs", metavar=('k_l', 'k_r', 'k_s'), required=True) parser.add_argument("--sizes", nargs=2, type=int, help="Number of points", metavar=('nx', 'ny'), required=True) parser.add_argument("--hurst", "-H", type=float, help="Hurst exponent", required=True) parser.add_argument("--rms", type=float, help="Root-mean-square of slopes", default=1.) parser.add_argument("--seed", type=int, help="Random seed", default=int(time.time())) parser.add_argument("--generator", help="Generation method", choices=('random_phase', 'filter'), default='random_phase') parser.add_argument("--output", "-o", help="Output file name (compressed if .gz)") args = parser.parse_args() if args.generator == 'random_phase': generator = tm.SurfaceGeneratorRandomPhase2D(args.sizes) elif args.generator == 'filter': generator = tm.SurfaceGeneratorFilter2D(args.sizes) else: raise ValueError('Unknown generator method {}'.format(args.generator)) generator.spectrum = tm.Isopowerlaw2D() generator.spectrum.q0 = args.cutoffs[0] generator.spectrum.q1 = args.cutoffs[1] generator.spectrum.q2 = args.cutoffs[2] generator.spectrum.hurst = args.hurst generator.random_seed = args.seed surface = generator.buildSurface() / generator.spectrum.rmsSlopes() * args.rms output = args.output if args.output is not None else sys.stdout np.savetxt(output, surface) diff --git a/examples/plasticity.py b/examples/plasticity.py index 2683e1f..6d680b3 100644 --- a/examples/plasticity.py +++ b/examples/plasticity.py @@ -1,85 +1,83 @@ #!/usr/bin/env python3 -# @file -# 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 . import numpy as np import tamaas as tm from tamaas.dumpers import H5Dumper as Dumper from tamaas.nonlinear_solvers import DFSANECXXSolver as Solver, ToleranceManager # Definition of modeled domain model_type = tm.model_type.volume_2d discretization = [32, 51, 51] flat_domain = [1, 1] system_size = [0.5] + flat_domain # Creation of model model = tm.ModelFactory.createModel(model_type, system_size, discretization) model.E = 1. model.nu = 0.3 # Setup for plasticity residual = tm.ModelFactory.createResidual(model, sigma_y=0.1 * model.E, hardening=0.01 * model.E) # Possibly change integration method # residual.setIntegrationMethod(tm.integration_method.cutoff, 1e-12) # Setup non-linear solver with variable tolerance epsolver = ToleranceManager(1e-5, 1e-9, 1/4)(Solver)(residual) # Setup for contact x = np.linspace(0, system_size[1], discretization[1], endpoint=False, dtype=tm.dtype) y = np.linspace(0, system_size[2], discretization[2], endpoint=False, dtype=tm.dtype) xx, yy = np.meshgrid(x, y, indexing='ij') R = 0.2 surface = -((xx - flat_domain[0] / 2)**2 + (yy - flat_domain[1] / 2)**2) / (2 * R) # Extract local part of the global surface local_shape = tm.mpi.local_shape(surface.shape) offset = tm.mpi.local_offset(surface.shape) local_surface = surface[offset:offset+local_shape[0], :].copy() csolver = tm.PolonskyKeerRey(model, local_surface, 1e-12, tm.PolonskyKeerRey.pressure, tm.PolonskyKeerRey.pressure) # EPIC setup epic = tm.EPICSolver(csolver, epsolver, 1e-7) # Dumper dumper_helper = Dumper('hertz', 'displacement', 'stress', 'plastic_strain') model.addDumper(dumper_helper) loads = np.linspace(0.001, 0.005, 3) for i, load in enumerate(loads): epic.acceleratedSolve(load) model.dump() tm.Logger().get(tm.LogLevel.info) \ << "---> Solved load step {}/{}".format(i+1, len(loads)) diff --git a/examples/rough_contact.py b/examples/rough_contact.py index 4502767..ba49fb0 100644 --- a/examples/rough_contact.py +++ b/examples/rough_contact.py @@ -1,64 +1,62 @@ #!/usr/bin/env python3 -# @file -# 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 . import tamaas as tm import matplotlib.pyplot as plt # Initialize threads and fftw tm.set_log_level(tm.LogLevel.info) # Show progression of solver # Surface size n = 512 # Surface generator sg = tm.SurfaceGeneratorFilter2D([n, n]) sg.random_seed = 0 # Spectrum sg.spectrum = tm.Isopowerlaw2D() # Parameters sg.spectrum.q0 = 16 sg.spectrum.q1 = 16 sg.spectrum.q2 = 64 sg.spectrum.hurst = 0.8 # Generating surface surface = sg.buildSurface() surface /= tm.Statistics2D.computeSpectralRMSSlope(surface) plt.imshow(surface) # Creating model model = tm.ModelFactory.createModel(tm.model_type.basic_2d, [1., 1.], [n, n]) # Solver solver = tm.PolonskyKeerRey(model, surface, 1e-12) # Solve for target pressure p_target = 0.1 solver.solve(p_target) plt.figure() plt.imshow(model.traction) plt.title('Contact tractions') print(model.traction.mean()) plt.show() diff --git a/examples/saturated.py b/examples/saturated.py index b9f772a..c6017e2 100644 --- a/examples/saturated.py +++ b/examples/saturated.py @@ -1,66 +1,64 @@ # -*- coding: utf-8 -*- -# @file -# 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 . import tamaas as tm import numpy as np import matplotlib.pyplot as plt grid_size = 256 load = 1.6 p_sat = 100 iso = tm.Isopowerlaw2D() iso.q0 = 4 iso.q1 = 4 iso.q2 = 16 iso.hurst = 0.8 sg = tm.SurfaceGeneratorFilter2D([grid_size, grid_size]) sg.random_seed = 2 sg.spectrum = iso surface = sg.buildSurface() surface -= np.max(surface) model = tm.ModelFactory.createModel(tm.model_type.basic_2d, [1., 1.], [grid_size, grid_size]) model.E = 1. model.nu = 0 esolver = tm.PolonskyKeerRey(model, surface, 1e-12, tm.PolonskyKeerRey.pressure, tm.PolonskyKeerRey.pressure) esolver.solve(load) elastic_tractions = model.traction.copy() plt.imshow(elastic_tractions) plt.title('Elastic contact tractions') plt.colorbar() model.traction[:] = 0. solver = tm.KatoSaturated(model, surface, 1e-12, p_sat) solver.dump_freq = 1 solver.max_iter = 200 solver.solve(load) plt.figure() plt.imshow(model.traction) plt.title('Saturated contact tractions') plt.colorbar() plt.show() diff --git a/examples/statistics.py b/examples/statistics.py index 142404a..cefd45e 100644 --- a/examples/statistics.py +++ b/examples/statistics.py @@ -1,83 +1,81 @@ #!/usr/bin/env python3 -# @file -# 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 . import os import numpy as np import matplotlib.pyplot as plt import tamaas as tm from matplotlib.colors import LogNorm tm.set_log_level(tm.LogLevel.info) # Show progression of solver # Surface size n = 512 # Surface generator sg = tm.SurfaceGeneratorFilter2D([n, n]) sg.random_seed = 0 # Spectrum sg.spectrum = tm.Isopowerlaw2D() # Parameters sg.spectrum.q0 = 16 sg.spectrum.q1 = 16 sg.spectrum.q2 = 64 sg.spectrum.hurst = 0.8 # Generating surface surface = sg.buildSurface() # Computing PSD and shifting for plot psd = tm.Statistics2D.computePowerSpectrum(surface) psd = np.fft.fftshift(psd, axes=0) plt.imshow(psd.real, norm=LogNorm()) plt.gca().set_title('Power Spectrum Density') plt.gcf().tight_layout() # Computing autocorrelation and shifting for plot acf = tm.Statistics2D.computeAutocorrelation(surface) acf = np.fft.fftshift(acf) plt.figure() plt.imshow(acf) plt.gca().set_title('Autocorrelation') plt.gcf().tight_layout() plt.show() # Write the rough surface in paraview's VTK format try: import uvw try: os.mkdir('paraview') except FileExistsError: pass x = np.linspace(0, 1, n, endpoint=True) y = x.copy() with uvw.RectilinearGrid(os.path.join('paraview', 'surface.vtr'), (x, y)) as grid: grid.addPointData(uvw.DataArray(surface, range(2), 'surface')) except ImportError: print("uvw not installed, will not write VTK file") pass diff --git a/examples/stresses.py b/examples/stresses.py index d5ff938..40d73e2 100644 --- a/examples/stresses.py +++ b/examples/stresses.py @@ -1,122 +1,120 @@ #!/usr/bin/env python3 -# @file -# 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 . import argparse import os import numpy as np import tamaas as tm from tamaas.dumpers import H5Dumper as Dumper from tamaas.dumpers._helper import hdf5toVTK parser = argparse.ArgumentParser( description="Hertzian tractios applied on elastic half-space") parser.add_argument("radius", type=float, help="Radius of sphere") parser.add_argument("load", type=float, help="Applied normal force") parser.add_argument("name", help="Output file name") parser.add_argument("--plots", help='Show surface normal stress', action="store_true") args = parser.parse_args() # Definition of modeled domain model_type = tm.model_type.volume_2d discretization = [32, 127, 127] system_size = [0.25, 1., 1.] # Material contants E = 1. # Young's modulus nu = 0.3 # Poisson's ratio E_star = E/(1-nu**2) # Hertz modulus # Creation of model model = tm.ModelFactory.createModel(model_type, system_size, discretization) model.E = E model.nu = nu # Setup for integral operators residual = tm.ModelFactory.createResidual(model, 0, 0) # Coordinates x = np.linspace(0, system_size[1], discretization[1], endpoint=False) y = np.linspace(0, system_size[2], discretization[2], endpoint=False) x, y = np.meshgrid(x, y, indexing='ij') center = [0.5, 0.5] r = np.sqrt((x-center[0])**2 + (y-center[1])**2) # Span of local data local_size = model.boundary_shape local_offset = tm.mpi.local_offset(r.shape) local_slice = np.s_[local_offset:local_offset+local_size[0], :] # Sphere R = args.radius P = args.load # Contact area a = (3*P*R/(4*E_star))**(1/3) p_0 = 3 * P / (2 * np.pi * a**2) # Pressure definition traction = model.traction hertz_traction = np.zeros(discretization[1:]) hertz_traction[r < a] = p_0 * np.sqrt(1 - (r[r < a] / a)**2) traction[..., 2] = hertz_traction[local_slice] # Array references displacement = model.displacement stress = model['stress'] gradient = np.zeros_like(stress) # Getting integral operators boussinesq = model.operators["boussinesq"] boussinesq_gradient = model.operators["boussinesq_gradient"] # Applying operators boussinesq(traction, displacement) boussinesq_gradient(traction, gradient) model.operators["hooke"](gradient, stress) # Dumper dumper_helper = Dumper(args.name, 'stress') model.addDumper(dumper_helper) model.dump() # Converting HDF dump to VTK with tm.mpi.sequential(): if tm.mpi.rank() == 0: hdf5toVTK(os.path.join('hdf5', 'stress_0000.h5'), args.name) if args.plots: import matplotlib.pyplot as plt # noqa fig, ax = plt.subplots(1, 2) fig.suptitle("Rank {}".format(tm.mpi.rank())) ax[0].set_title("Traction") ax[1].set_title("Normal Stress") ax[0].imshow(traction[..., 2]) ax[1].imshow(stress[0, ..., 2]) fig.tight_layout() plt.show() diff --git a/python/SConscript b/python/SConscript index fe89901..c3ec2e0 100644 --- a/python/SConscript +++ b/python/SConscript @@ -1,164 +1,161 @@ # -*- mode:python; coding: utf-8 -*- # vim: set ft=python: - -# @file -# 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 . from __future__ import print_function from SCons.Script import Import, Split, Copy, Dir Import('main_env') # Pybind11 wrapper env_pybind = main_env.Clone(SHLIBPREFIX='') # Remove pedantic warnings cxx_flags = env_pybind['CXXFLAGS'] try: del cxx_flags[cxx_flags.index('-pedantic')] except ValueError: pass env_pybind.Tool(pybind11) pybind_sources = Split(""" tamaas_module.cpp wrap/core.cpp wrap/percolation.cpp wrap/surface.cpp wrap/model.cpp wrap/solvers.cpp wrap/compute.cpp wrap/mpi.cpp wrap/test_features.cpp """) # Setting paths to find libTamaas env_pybind.AppendUnique(LIBPATH=['../src']) # Link against a static libTamaas if env_pybind['build_static_lib']: env_pybind.PrependUnique(LIBS=['Tamaas']) # keep other libs for link env_pybind['RPATH'] = "" # no need for rpath w/ static lib # Link against a dynamic libTamaas else: env_pybind.AppendUnique(RPATH=[ "'$$$$ORIGIN/../../src'", # path to lib in build_dir "'$$$$ORIGIN/../../..'", # path to lib in install prefix ]) env_pybind['LIBS'] = ['Tamaas'] # discard other libs for link # Building the pybind library tamaas_wrap = env_pybind.Pybind11Module( target='tamaas/_tamaas', source=pybind_sources, ) # For some reason link happens too early Import('libTamaas') env_pybind.Depends(tamaas_wrap, libTamaas) # Copying the __init__.py file with extra python classes copy_env = env_pybind.Clone() # Copying additional python files python_files = """ __main__.py compute.py dumpers/__init__.py dumpers/_helper.py nonlinear_solvers/__init__.py """.split() targets = [tamaas_wrap] targets += [ copy_env.Command(copy_env.File(f, 'tamaas'), copy_env.File(f, '#python/tamaas'), Copy("$TARGET", "$SOURCE")) for f in python_files ] targets.append(copy_env.Command('MANIFEST.in', '#python/MANIFEST.in', Copy("$TARGET", "$SOURCE"))) subst_env = env_pybind.Clone( SUBST_DICT={ '@version@': '$version', '@authors@': str(copy_env['authors']), '@email@': '$email', '@description@': '$email', # TODO change when issue with unicode fixed # '@copyright@': '$copyright', # '@maintainer@': '$maintainer', } ) subst_env.Tool('textfile') targets.append(subst_env.Substfile('setup.py.in')) targets.append(subst_env.Substfile('tamaas/__init__.py.in')) # Defining alias for python builds main_env.Alias('build-python', targets) # Checking if we can use pip to install (more convenient for end-user) install_env = main_env.Clone() conf = Configure(install_env, custom_tests={'CheckPythonModule': CheckPythonModule}) has_pip = conf.CheckPythonModule('pip') install_env = conf.Finish() # Current build directory install_env['PYDIR'] = Dir('.') # Setting command line for installation if has_pip: install_env['PYINSTALLCOM'] = '${py_exec} -m pip install -U $PYOPTIONS .' install_env['PYDEVELOPCOM'] = \ '${py_exec} -m pip install $PYOPTIONS -e .[all]' else: install_env['PYINSTALLCOM'] = '${py_exec} setup.py install $PYOPTIONS' install_env['PYDEVELOPCOM'] = '${py_exec} setup.py develop $PYOPTIONS' install_env['py_version'] = get_python_version(install_env) install_env.PrependENVPath( 'PYTHONPATH', install_env.subst('${prefix}/lib/python${py_version}/site-packages')) # Specify install target PYOPTIONS = ['${"" if verbose else "-q"}'] python_install = install_env.Command( '.python_install_phony', targets, install_env['PYINSTALLCOM'], PYOPTIONS=['--prefix', '${prefix}'] + PYOPTIONS, chdir=install_env['PYDIR']) python_install_dev = install_env.Command( '.python_install_local_phony', targets, install_env['PYDEVELOPCOM'], PYOPTIONS=['--user'] + PYOPTIONS, chdir=install_env['PYDIR']) # Defining aliases main_env.Alias('install-python', python_install) main_env.Alias('dev', python_install_dev) diff --git a/python/cast.hh b/python/cast.hh index 485ad7f..4f7e2c1 100644 --- a/python/cast.hh +++ b/python/cast.hh @@ -1,182 +1,181 @@ -/** - * @file - * LICENSE +/* + * SPDX-License-Indentifier: AGPL-3.0-or-later * * 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 . * */ /* -------------------------------------------------------------------------- */ #ifndef CAST_HH #define CAST_HH /* -------------------------------------------------------------------------- */ #include "grid_base.hh" #include "grid.hh" #include "numpy.hh" /* -------------------------------------------------------------------------- */ #include #include /* -------------------------------------------------------------------------- */ namespace pybind11 { // Format descriptor necessary for correct wrap of tamaas complex type template struct format_descriptor< tamaas::complex, detail::enable_if_t::value>> { static constexpr const char c = format_descriptor::c; static constexpr const char value[3] = {'Z', c, '\0'}; static std::string format() { return std::string(value); } }; #ifndef PYBIND11_CPP17 template constexpr const char format_descriptor< tamaas::complex, detail::enable_if_t::value>>::value[3]; #endif namespace detail { // declare tamaas complex as a complex type for pybind11 template struct is_complex> : std::true_type {}; template struct is_fmt_numeric, detail::enable_if_t::value>> : std::true_type { static constexpr int index = is_fmt_numeric::index + 3; }; static inline handle policy_switch(return_value_policy policy, handle parent) { switch (policy) { case return_value_policy::copy: case return_value_policy::move: return handle(); case return_value_policy::automatic_reference: // happens in python-derived // classes case return_value_policy::reference: return none(); case return_value_policy::reference_internal: return parent; default: TAMAAS_EXCEPTION("Policy is not handled"); } } template handle grid_to_python(const tamaas::Grid& grid, return_value_policy policy, handle parent) { parent = policy_switch(policy, parent); // reusing variable std::vector sizes(dim); std::copy(grid.sizes().begin(), grid.sizes().end(), sizes.begin()); if (grid.getNbComponents() != 1) sizes.push_back(grid.getNbComponents()); return array(sizes, grid.getInternalData(), parent).release(); } /** * Type caster for grid classes * inspired by https://tinyurl.com/y8m47qh3 from T. De Geus * and pybind11/eigen.h */ template