diff --git a/INFOS.py b/INFOS.py index 13128c0..3d6497f 100755 --- a/INFOS.py +++ b/INFOS.py @@ -1,55 +1,55 @@ #!/usr/bin/env python3 """Defines the information to be used throughout the builds.""" # -*- mode:python; coding: utf-8 -*- from collections import namedtuple import versioneer TamaasInfo = namedtuple('TamaasInfo', ['version', 'authors', 'maintainer', 'email', 'copyright', 'description', 'license']) TAMAAS_INFOS = TamaasInfo( version=versioneer.get_version(), 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@imtek.uni-freiburg.de', copyright=( - "Copyright (©) 2016-2022 EPFL " + "Copyright (©) 2016-2023 EPFL " "(École Polytechnique Fédérale de Lausanne), " "Laboratory (LSMS - Laboratoire de Simulation en " "Mécanique des Solides)\\n" - "Copyright (©) 2020-2022 Lucas Frérot" + "Copyright (©) 2020-2023 Lucas Frérot" ), description='A high-performance library for periodic rough surface contact', license="SPDX-License-Identifier: AGPL-3.0-or-later", ) def main(): import argparse parser = argparse.ArgumentParser(description="Print Tamaas info") parser.add_argument("--version", action="store_true") args = parser.parse_args() if args.version: print(TAMAAS_INFOS.version) if __name__ == "__main__": main() diff --git a/SConstruct b/SConstruct index e060a42..f4e8961 100644 --- a/SConstruct +++ b/SConstruct @@ -1,476 +1,476 @@ # -*- mode:python; coding: utf-8 -*- # vim: set ft=python: # -# Copyright (©) 2016-2022 EPFL (École Polytechnique Fédérale de Lausanne), +# Copyright (©) 2016-2023 EPFL (École Polytechnique Fédérale de Lausanne), # Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) -# Copyright (©) 2020-2022 Lucas Frérot +# Copyright (©) 2020-2023 Lucas Frérot # # 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 from warnings import warn # Import below not strictly necessary, but good for pep8 from SCons.Script import ( EnsurePythonVersion, EnsureSConsVersion, Help, Environment, Variables, EnumVariable, PathVariable, BoolVariable, ListVariable, Split, Export, ) 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(3, 6) EnsureSConsVersion(3, 0) # ------------------------------------------------------------------------------ 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/version.hpp', 'boost/preprocessor/seq.hpp', 'boost/variant.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', 'cpp', allowed_values=('cpp', 'omp', 'tbb', 'cuda'), ignorecase=2), EnumVariable('fftw_threads', 'Threads FFTW library preference', 'none', 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', PathVariable.PathAccept), # 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), PathVariable('GTEST_ROOT', 'Googletest custom path', os.getenv('GTEST_ROOT', ''), PathVariable.PathAccept), PathVariable('PYBIND11_ROOT', 'Pybind11 custom path', os.getenv('PYBIND11_ROOT', ''), 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', os.getenv('VERBOSE') in {'1', 'True', 'true'}), BoolVariable('color', 'Color the non-verbose compilation output', False), # Tamaas components 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_mpi', 'Builds multi-process parallelism', False), # Distribution options BoolVariable('strip_info', 'Strip binary of added information', True), 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])) # Printing unknown variables if vars.UnknownVariables(): warn(f'unknown variables provided:\n\t{vars.UnknownVariables()}') 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 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/surface', '#/src/percolation', '#/src/model', '#/src/model/elasto_plastic', '#/src/solvers', '#/src/gpu', '#/python', '#/third-party/expolit/include' ]) # 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') build_targets.append( main_env.CompilationDatabase(PRINT_CMD_LINE_FUNC=pretty_cmd_print if not main_env['verbose'] else None)) # 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 main_env.SubDirectory('doc') main_env.Alias('doc', 'build-doc') install_targets.append('install-doc') # 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 b180071..122c3d0 100644 --- a/doc/SConscript +++ b/doc/SConscript @@ -1,107 +1,107 @@ # -*- mode:python; coding: utf-8 -*- # vim: set ft=python: # -# Copyright (©) 2016-2022 EPFL (École Polytechnique Fédérale de Lausanne), +# Copyright (©) 2016-2023 EPFL (École Polytechnique Fédérale de Lausanne), # Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) -# Copyright (©) 2020-2022 Lucas Frérot +# Copyright (©) 2020-2023 Lucas Frérot # # 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 doc_targets = [] # Generate Doxygen API documentation doc_env.Tool('doxygen') if doc_env['has_doxygen']: # 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"]], }) doxygen_target = doc_env.Doxygen('doxygen/xml/index.xml', [doxyfile_target, 'icon.svg', 'icon.ico']) # Adding all source files as dependencies sources = [] Dir('#src').walk(add_sources, sources) doc_env.Depends(doxygen_target, sources) doc_targets.append(doxygen_target) # 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') sphinx_targets = None if doc_env['has_sphinx'] and len(doc_targets) != 0: 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) if main_env['build_python']: doc_env.Depends(target, 'build-python') doc_targets += list(sphinx_targets.values()) # Alias for both docs main_env.Alias('build-doc', doc_targets) # Install target for documentation share = Dir('share', doc_env['prefix']) doc_install = [] sphinx_prefix_map = {} if sphinx_targets is not None: 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 808f153..4ca2a8a 100644 --- a/examples/adhesion.py +++ b/examples/adhesion.py @@ -1,123 +1,123 @@ #!/usr/bin/env python3 # -# Copyright (©) 2016-2022 EPFL (École Polytechnique Fédérale de Lausanne), +# Copyright (©) 2016-2023 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 tamaas.utils import publications 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 = 1 # 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() publications() diff --git a/examples/pipe_tools/contact b/examples/pipe_tools/contact index 634c3a1..59d339c 100755 --- a/examples/pipe_tools/contact +++ b/examples/pipe_tools/contact @@ -1,58 +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-2022, EPFL (École Polytechnique Fédérale de Lausanne)," + "Copyright (©) 2019-2023, EPFL (École Polytechnique Fédérale de Lausanne)," "\nLaboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)" ) __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 b6c3f6e..6e2d6fc 100755 --- a/examples/pipe_tools/plot +++ b/examples/pipe_tools/plot @@ -1,49 +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-2022, EPFL (École Polytechnique Fédérale de Lausanne)," + "Copyright (©) 2019-2023, EPFL (École Polytechnique Fédérale de Lausanne)," "\nLaboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)" ) __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 0997600..87db867 100755 --- a/examples/pipe_tools/surface +++ b/examples/pipe_tools/surface @@ -1,77 +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-2022, EPFL (École Polytechnique Fédérale de Lausanne)," + "Copyright (©) 2019-2023, EPFL (École Polytechnique Fédérale de Lausanne)," "\nLaboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)" ) __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 8c48f80..4e4ba95 100644 --- a/examples/plasticity.py +++ b/examples/plasticity.py @@ -1,85 +1,85 @@ #!/usr/bin/env python3 # -# Copyright (©) 2016-2022 EPFL (École Polytechnique Fédérale de Lausanne), +# Copyright (©) 2016-2023 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 from tamaas.utils import publications # 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) # Scatter surface across MPI processes local_surface = tm.mpi.scatter(surface) 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)) # Print list of relevant publications publications() diff --git a/examples/rough_contact.py b/examples/rough_contact.py index 878c191..28ff8f6 100644 --- a/examples/rough_contact.py +++ b/examples/rough_contact.py @@ -1,66 +1,66 @@ #!/usr/bin/env python3 # -# Copyright (©) 2016-2022 EPFL (École Polytechnique Fédérale de Lausanne), +# Copyright (©) 2016-2023 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 from tamaas.utils import publications # 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 = 1 # 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() publications() diff --git a/examples/saturated.py b/examples/saturated.py index c48ab5e..48f0f04 100644 --- a/examples/saturated.py +++ b/examples/saturated.py @@ -1,68 +1,68 @@ # -*- coding: utf-8 -*- # -# Copyright (©) 2016-2022 EPFL (École Polytechnique Fédérale de Lausanne), +# Copyright (©) 2016-2023 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 from tamaas.utils import publications 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() publications() diff --git a/examples/scipy_penalty.py b/examples/scipy_penalty.py index efded2e..31edbbb 100644 --- a/examples/scipy_penalty.py +++ b/examples/scipy_penalty.py @@ -1,119 +1,119 @@ #!/usr/bin/env python3 # -# Copyright (©) 2016-2022 EPFL (École Polytechnique Fédérale de Lausanne), +# Copyright (©) 2016-2023 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 import scipy.optimize from tamaas.utils import hertz_surface class GapPenaltyFunctional(tm.Functional): """Quadratic penalty on negative gap functional.""" def __init__(self, penalty): super().__init__() self.penalty = penalty @staticmethod def clip(gap): """Clip negative gap.""" return np.clip(gap, -np.inf, 0) def computeF(self, gap, dual): """Compute penalty.""" g = self.clip(gap) return 0.5 / self.penalty * np.vdot(g, g) / g.size def computeGradF(self, gap, gradient): """Compute gradient.""" gradient[:] += (1 / self.penalty) * self.clip(gap) class ScipyContactSolver(tm.ContactSolver): """Gap-based penalty solver using https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html""" def __init__(self, model, surface, tolerance, penalty): super().__init__(model, surface, tolerance) if model.type != tm.model_type.basic_2d: raise TypeError("model type is not basic_2d") self.shape = model.boundary_shape self.N = np.prod(self.shape) model['gap'] = np.zeros(list(self.shape) + [1]) model.be_engine.registerDirichlet() self.elastic = tm.ElasticFunctionalGap( self.model.operators['Westergaard::dirichlet'], self.surface) self.penalty = GapPenaltyFunctional(penalty) self.addFunctionalTerm(self.elastic) self.addFunctionalTerm(self.penalty) def solve(self, mean_gap): """Solve with mean gap constraint.""" def cost(gap): gap = gap.reshape(self.shape) grad = np.zeros_like(gap).reshape(self.shape) self.functional.computeGradF(gap, grad) return self.functional.computeF(gap, grad) def jac(gap): gap = gap.reshape(self.shape) grad = np.zeros_like(gap).reshape(self.shape) self.functional.computeGradF(gap, grad) return np.ravel(grad) opt = scipy.optimize.minimize( cost, np.full(self.N, mean_gap), jac=jac, constraints=scipy.optimize.LinearConstraint( np.full((1, self.N), 1 / self.N), mean_gap, mean_gap), method='trust-constr', tol=self.tolerance * mean_gap) print(opt) # Setup solved model self.model['gap'][:] = opt.x.reshape(self.shape) self.model.displacement[:] = self.model['gap'] + self.surface.reshape( self.shape) self.model.solveDirichlet() self.model.traction[:] -= self.model.traction.min() def main(): """Run a simple Hertzian contact with penalty.""" N = 64 model = tm.Model(tm.model_type.basic_2d, [1, 1], [N] * 2) surface = hertz_surface(model.system_size, model.shape, 1) solver = ScipyContactSolver(model, surface, 1e-10, 1) solver.solve(0.04) plt.imshow(model.traction) plt.colorbar() plt.show() if __name__ == "__main__": main() diff --git a/examples/statistics.py b/examples/statistics.py index ca0ad5a..32c77ba 100644 --- a/examples/statistics.py +++ b/examples/statistics.py @@ -1,84 +1,84 @@ #!/usr/bin/env python3 # -# Copyright (©) 2016-2022 EPFL (École Polytechnique Fédérale de Lausanne), +# Copyright (©) 2016-2023 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 tamaas.utils import publications 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 = 1 # 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(np.clip(psd.real, 1e-10, np.inf), 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 publications() diff --git a/examples/stresses.py b/examples/stresses.py index a557794..1114ee2 100644 --- a/examples/stresses.py +++ b/examples/stresses.py @@ -1,123 +1,123 @@ #!/usr/bin/env python3 # -# Copyright (©) 2016-2022 EPFL (École Polytechnique Fédérale de Lausanne), +# Copyright (©) 2016-2023 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 from tamaas.utils import publications 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', f'{args.name}_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() publications() diff --git a/python/SConscript b/python/SConscript index f6467b9..506aeb9 100644 --- a/python/SConscript +++ b/python/SConscript @@ -1,177 +1,177 @@ # -*- mode:python; coding: utf-8 -*- # vim: set ft=python: # -# Copyright (©) 2016-2022 EPFL (École Polytechnique Fédérale de Lausanne), +# Copyright (©) 2016-2023 EPFL (École Polytechnique Fédérale de Lausanne), # Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) -# Copyright (©) 2020-2022 Lucas Frérot +# Copyright (©) 2020-2023 Lucas Frérot # # 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 utils.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 ] dist_files = """ MANIFEST.in pypi.md setup.py """.split() # pyproject.toml causes issues with develop mode dist_files.append("pyproject.toml") targets += [ copy_env.Command(copy_env.File(f, ''), copy_env.File(f, '#python'), Copy("$TARGET", "$SOURCE")) for f in dist_files ] subst_env = env_pybind.Clone( SUBST_DICT={ '@version@': '$version', '@authors@': str(copy_env['authors']), '@author_list@': ', '.join(copy_env['authors']), '@email@': '$email', '@description@': '$description', '@copyright@': '$copyright', '@maintainer@': '$maintainer', '@license@': '$license', } ) subst_env.Tool('textfile') targets.append(subst_env.Substfile('tamaas/__init__.py.in')) targets.append(subst_env.Substfile('setup.cfg.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'], # Temporary fix for https://github.com/pypa/pip/issues/7953 PYOPTIONS=['--prefix=$$HOME/.local/'] + 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 7ab3f19..2cd30f4 100644 --- a/python/cast.hh +++ b/python/cast.hh @@ -1,245 +1,245 @@ /* * SPDX-License-Indentifier: AGPL-3.0-or-later * - * Copyright (©) 2016-2022 EPFL (École Polytechnique Fédérale de Lausanne), + * Copyright (©) 2016-2023 EPFL (École Polytechnique Fédérale de Lausanne), * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) - * Copyright (©) 2020-2022 Lucas Frérot + * Copyright (©) 2020-2023 Lucas Frérot * * 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 "field_container.hh" #include "grid.hh" #include "grid_base.hh" #include "model_type.hh" #include "numpy.hh" /* -------------------------------------------------------------------------- */ #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(); } template handle grid_to_python(const tamaas::GridBase& grid, return_value_policy policy, handle parent) { parent = policy_switch(policy, parent); // reusing variable std::vector sizes = {grid.dataSize()}; 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