diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c3c3d36..e94f6e1 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,228 +1,234 @@
stages:
- docker
- lint
- build
- test
- deploy
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
GIT_SUBMODULE_STRATEGY: recursive
BUILD_DIR: build-release
cache:
key: "$CI_COMMIT_REF_SLUG"
# ------------------------------------------------------------------------------
.docker_build:
stage: docker
image: docker:19.03.12
services:
- docker:19.03.12-dind
variables:
# Use TLS https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#tls-enabled
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
DEFAULT_IMAGE: $CI_REGISTRY_IMAGE:$CI_DEFAULT_BRANCH
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker pull $DEFAULT_IMAGE-$IMAGE_NAME || true
- docker build --cache-from $DEFAULT_IMAGE-$IMAGE_NAME
-t $IMAGE_TAG-$IMAGE_NAME -f $DOCKERFILE .
- docker push $IMAGE_TAG-$IMAGE_NAME
docker build:debian:
variables:
IMAGE_NAME: debian-stable
DOCKERFILE: tests/ci/docker/debian.mpi
extends: .docker_build
docker build:manylinux:
variables:
IMAGE_NAME: manylinux
DOCKERFILE: tests/ci/docker/manylinux
extends: .docker_build
docker build:cuda:
variables:
IMAGE_NAME: cuda
DOCKERFILE: tests/ci/docker/ubuntu_lts.cuda
extends: .docker_build
# ------------------------------------------------------------------------------
.debian_stable:
variables:
output: ${CI_COMMIT_REF_SLUG}-debian-stable
image: ${IMAGE_TAG}-debian-stable
.manylinux:
variables:
output: ${CI_COMMIT_REF_SLUG}-manylinux
image: ${IMAGE_TAG}-manylinux
.cuda:
variables:
output: ${CI_COMMIT_REF_SLUG}-cuda
image: ${IMAGE_TAG}-cuda
# ------------------------------------------------------------------------------
lint:
stage: lint
extends:
- .debian_stable
allow_failure: true
+ variables:
+ CLANG_PATCH: clang_format.patch
+ FLAKE_ERRORS: flake8_lint.txt
script:
- git remote remove upstream || true
- git remote add upstream "$CI_PROJECT_URL"
- git fetch upstream
- '[ -z "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" ] && HEAD="$CI_COMMIT_BEFORE_SHA" || HEAD="upstream/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"'
- echo "$HEAD"
- - git-clang-format --diff "$HEAD" | tee clang_format.patch
- - grep "no modified files to format" clang_format.patch || grep "clang-format did not modify any files" clang_format.patch
+ - git-clang-format --diff "$HEAD" | tee $CLANG_PATCH
+ - grep "no modified files to format" $CLANG_PATCH || grep "clang-format did not modify any files" $CLANG_PATCH
+ - flake8 python/ | tee $FLAKE_ERRORS
+ - test -s $FLAKE_ERRORS
artifacts:
when: on_failure
paths:
- - clang_format.patch
+ - $CLANG_PATCH
+ - $FLAKE_ERRORS
.build:
stage: build
variables:
COMPILE_LOG: compilation.log
script:
- scons 2>&1 | tee $COMPILE_LOG
artifacts:
when: always
paths:
- build-setup.conf
- $COMPILE_LOG
- $BUILD_DIR
- config.log
.ccache:
variables:
CCACHE_BASEDIR: $CI_PROJECT_DIR/$BUILD_DIR
CCACHE_DIR: $CI_PROJECT_DIR/.ccache
CCACHE_NOHASDIR: 1
CCACHE_COMPILERCHECK: content
CXX: /usr/lib/ccache/g++
OMPI_CXX: ${CXX}
cache:
key: ${output}
policy: pull-push
paths:
- .ccache
after_script:
- ccache --show-stats || true
build:mpi:
extends:
- .build
- .debian_stable
- .ccache
before_script:
- ccache --zero-stats || true
- scons build_tests=True
use_googletest=True
build_python=True
py_exec=python3
use_mpi=True
backend=omp
fftw_threads=omp
verbose=True -h
build:cuda:
extends:
- .build
- .cuda
before_script:
- scons build_tests=True
use_googletest=True
build_python=True
py_exec=python3
use_mpi=False
backend=cuda
fftw_threads=none
verbose=True -h
# ------------------------------------------------------------------------------
test:mpi:
stage: test
dependencies:
- build:mpi
extends: .debian_stable
variables:
PYTHONPATH: $CI_PROJECT_DIR/$BUILD_DIR/python
TESTS: $BUILD_DIR/tests
JUNITXML: results.xml
TESTS_LOG: tests.log
script:
- ls $PYTHONPATH
- python3 -c 'import sys; print(sys.path)'
- python3 -m pytest -vvv --last-failed --durations=0 --junitxml=$JUNITXML
$TESTS 2>&1 | tee $TESTS_LOG
after_script:
- python3 -m pytest --cache-show || true
artifacts:
when: always
paths:
- $JUNITXML
- $TESTS_LOG
reports:
junit:
- $JUNITXML
cache:
key: ${output}-pytest
policy: pull-push
paths:
- .pytest_cache
# ------------------------------------------------------------------------------
.protected_refs:
extends: .manylinux
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG =~ /^v.*/'
wheels:
stage: build
extends:
- .protected_refs
- .ccache
before_script:
- ccache --zero-stats || true
script:
- ./tests/ci/build_wheels.sh
artifacts:
paths:
- dist/wheelhouse
# ------------------------------------------------------------------------------
.deploy_wheels:
stage: deploy
extends: .protected_refs
dependencies:
- wheels
script:
python -m twine upload --verbose dist/wheelhouse/*
package:gitlab:
extends: .deploy_wheels
variables:
TWINE_USERNAME: gitlab-ci-token
TWINE_PASSWORD: ${CI_JOB_TOKEN}
TWINE_REPOSITORY_URL: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi
package:pypi:
extends: .deploy_wheels
variables:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${PYPI_TOKEN}
rules:
- if: '$CI_COMMIT_TAG =~ /^v.*/'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 98d8324..cac80ba 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,233 +1,234 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) for final versions and [PEP440](https://www.python.org/dev/peps/pep-0440/) in case intermediate versions need to be released (e.g. development version `2.2.3.dev1` or release candidates `2.2.3rc1`), or individual commits are packaged.
## Unreleased
### Changed
- The root `Dockerfile` now compiles Tamaas, so using Tamaas in Docker is easier.
- The model attributes dumped to Numpy files are now written in a JSON-formatted
string to avoid unsafe loading/unpickling of objects.
- Removed the `build_doc` build option: now the doc targets are automatically
added if the dependencies are met, and built if `scons doc` is called.
- Removed the `use_googletest` build option: if tests are built and gtest is
present, the corresponding tests will be built
## v2.3.0 -- 2021-06-15
### Added
- Added `read()` method to dumpers to create a model from a dump file
- `getClusters()` can be called in MPI contact with partial contact maps
- Added a JSON encoder class for models and a JSON dumper
- CUDA compatibility is re-established, but has not been tested
- Docstrings in the Python bindings for many classes/methods
+- Now using `clang-format` and `flake8` for linting
### Changed
- Tamaas version numbers are now managed by
[versioneer](https://github.com/python-versioneer/python-versioneer). This
means that Git tags prefixed with `v` (e.g. `v2.2.3`) carry meaning and
determine the version. When no tag is set, versioneer uses the last tag,
specifies the commit short hash and the distance to the last tag (e.g.
`2.2.2+33.ge314b0e`). This version string is used in the compiled library, the
`setup.py` script and the `__version__` variable in the python module.
- Tamaas migrated to [GitLab](https://gitlab.com/tamaas/tamaas)
- Continuous delivery has been implemented:
- the `master` branch will now automatically build and publish Python wheels
to `https://gitlab.com/api/v4/projects/19913787/packages/pypi/simple`. These
"nightly" builds can be installed with:
pip install \
--extra-index-url https://gitlab.com/api/v4/projects/19913787/packages/pypi/simple \
tamaas
- version tags pushed to `master` will automatically publish the wheels to
[PyPI](https://pypi.org/project/tamaas/)
### Deprecated
- The `finalize()` function is now deprecated, since it is automatically called
when the process terminates
- Python versions 3.5 and below are not supported anymore
### Fixed
- Fixed a host of dump read/write issues when model type was not `volume_*d`.
Dumper tests are now streamlined and systematic.
- Fixed a bug where `Model::solveDirichlet` would not compute correctly
- Fixed a bug where `Statistics::contact` would not normalize by the global
number of surface points
## v2.2.2 -- 2021-04-02
### Added
- Entry-point `tamaas` defines a grouped CLI for `examples/pipe_tools`. Try
executing `tamaas surface -h` from the command-line!
### Changed
- `CXXFLAGS` are now passed to the linker
- Added this changelog
- Using absolute paths for environmental variables when running `scons test`
- Reorganized documentation layout
- Gave the build system a facelift (docs are now generated directly with SCons
instead of a Makefile)
### Deprecated
- Python 2 support is discontinued. Version `v2.2.1` is the last PyPi build with
a Python 2 wheel.
- The scripts in `examples/pipe_tools` have been replaced by the `tamaas` command
### Fixed
- `UVWDumper` no longer imports `mpi4py` in sequential
- Compiling with different Thrust/FFTW backends
## v2.2.1 -- 2021-03-02
### Added
- Output registered fields and dumpers in `print(model)`
- Added `operator[]` to the C++ model class (for fields)
- Added `traction` and `displacement` properties to Python model bindings
- Added `operators` property to Python model bindings, which provides a
dict-like access to registered operators
- Added `shape` and `spectrum` to properties to Python surface generator
bindings
- Surface generator constructor accepts surface global shape as argument
- Choice of FFTW thread model
### Changed
- Tests use `/tmp` for temporary files
- Updated dependency versions (Thrust, Pybind11)
### Deprecated
- Most `get___()` and `set___()` in Python bindings have been deprecated. They
will generate a `DeprecationWarning`.
### Removed
- All legacy code
## v2.2.0 -- 2020-12-31
### Added
- More accurate function for computation of contact area
- Function to compute deviatoric of tensor fields
- MPI implementation
- Convenience `hdf5toVTK` function
- Readonly properties `shape`, `global_shape`, `boundary_shape` on model to give
shape information
### Changed
- Preprocessor defined macros are prefixed with `TAMAAS_`
- Moved `tamaas.to_voigt` to `tamaas.compute.to_voigt`
### Fixed
- Warning about deprecated constructors with recent GCC versions
- Wrong computation of grid strides
- Wrong computation of grid sizes in views
## v2.1.4 -- 2020-08-07
### Added
- Possibility to generate a static `libTamaas`
- C++ implementation of DFSANE solver
- Allowing compilation without OpenMP
### Changed
- NetCDF dumper writes frames to a single file
### Fixed
- Compatibility with SCons+Python 3
## v2.1.3 -- 2020-07-27
### Added
- Version number to `TamaasInfo`
### Changed
- Prepending root directory when generating archive
## v2.1.2 -- 2020-07-24
This release changes some core internals related to discrete Fourier transforms
for future MPI support.
### Added
- Caching `CXXFLAGS` in SCons build
- SCons shortcut to create code archive
- Test of the elastic-plastic contact solver
- Paraview data dumper (`.pvd` files)
- Compression for UVW dumper
- `__contains__` and `__iter__` Python bindings of model
- Warning message of possible overflow in Kelvin
### Changed
- Simplified `tamaas_info.cpp`, particularly the diff part
- Using a new class `FFTEngine` to manage discrete Fourier transforms. Plans are
re-used as much as possible with different data with the same shape. This is
in view of future MPI developments
- Redirecting I/O streams in solve functions so they can be used from Python
(e.g. in Jupyter notebooks)
- Calling `initialize()` and `finalize()` is no longer necessary
### Fixed
- Convergence issue with non-linear solvers
- Memory error in volume potentials
## v2.1.1 -- 2020-04-22
### Added
- SCons shortcut to run tests
### Fixed
- Correct `RPATH` for shared libraries
- Issues with SCons commands introduced in v2.1.0
- Tests with Python 2.7
## v2.1.0 -- 2020-04-17
### Added
- SCons shortcuts to build/install Tamaas and its components
- Selection of integration method for Kelvin operator
- Compilation option to remove the legacy part of Tamaas
- NetCDF dumper
### Fixed
- Link bug with clang
- NaNs in Kato saturated solver
## v2.0.0 -- 2019-11-11
First public release. Contains relatively mature elastic-plastic contact code.
diff --git a/python/tamaas/__main__.py b/python/tamaas/__main__.py
index bae4bbf..5dcae81 100755
--- a/python/tamaas/__main__.py
+++ b/python/tamaas/__main__.py
@@ -1,208 +1,207 @@
#!/usr/bin/env python3
# -*- mode: python; coding: utf-8 -*-
# vim: set ft=python:
#
# 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 sys
import io
import time
import argparse
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__ = "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)
def surface(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
params = {
'q0': generator.spectrum.q0,
'q1': generator.spectrum.q1,
'q2': generator.spectrum.q2,
'hurst': generator.spectrum.hurst,
'random_seed': generator.random_seed,
'rms_heights': args.rms,
'generator': args.generator,
}
try:
np.savetxt(output, surface, header=str(params))
except BrokenPipeError:
pass
def contact(args):
from tamaas.dumpers import NumpyDumper
tm.set_log_level(tm.LogLevel.error)
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)
def plot(args):
import matplotlib.pyplot as plt
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()
def main():
parser = argparse.ArgumentParser(
prog='tamaas',
description=("The tamaas command is a simple utility for surface"
" generation, contact computation and"
" plotting of contact solutions"),
)
subs = parser.add_subparsers(title='commands',
description='utility commands')
# Arguments for surface command
parser_surface = subs.add_parser(
'surface', description='Generate a self-affine rough surface')
parser_surface.add_argument("--cutoffs", "-K",
nargs=3,
type=int,
help="Long, rolloff, short wavelength cutoffs",
metavar=('k_l', 'k_r', 'k_s'),
required=True)
parser_surface.add_argument("--sizes",
nargs=2,
type=int,
help="Number of points",
metavar=('nx', 'ny'),
required=True)
parser_surface.add_argument("--hurst", "-H",
type=float,
help="Hurst exponent",
required=True)
parser_surface.add_argument("--rms",
type=float,
help="Root-mean-square of slopes",
default=1.)
parser_surface.add_argument("--seed",
type=int,
help="Random seed",
default=int(time.time()))
parser_surface.add_argument("--generator",
help="Generation method",
choices=('random_phase', 'filter'),
default='random_phase')
parser_surface.add_argument("--output", "-o",
help="Output file name (compressed if .gz)")
parser_surface.set_defaults(func=surface)
-
# Arguments for contact command
parser_contact = subs.add_parser(
'contact',
description="Compute the elastic contact solution with a given surface")
parser_contact.add_argument("--input", "-i",
help="Rough surface file (default stdin)")
parser_contact.add_argument("--tol",
type=float,
default=1e-12,
help="Solver tolerance")
parser_contact.add_argument("load",
type=float,
help="Applied average pressure")
parser_contact.set_defaults(func=contact)
-
# Arguments for plot command
parser_plot = subs.add_parser(
'plot', description='Plot contact solution')
parser_plot.set_defaults(func=plot)
args = parser.parse_args()
try:
args.func(args)
except AttributeError:
parser.print_usage()
+
if __name__ == '__main__':
main()
diff --git a/python/tamaas/nonlinear_solvers/__init__.py b/python/tamaas/nonlinear_solvers/__init__.py
index 2535fab..afa9b68 100644
--- a/python/tamaas/nonlinear_solvers/__init__.py
+++ b/python/tamaas/nonlinear_solvers/__init__.py
@@ -1,166 +1,163 @@
# -*- mode:python; coding: utf-8 -*-
#
# 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 .
"""
Pulling solvers to nonlinear_solvers module
"""
from functools import wraps
-import numpy as np
-from scipy.sparse.linalg import LinearOperator, lgmres
-from scipy.linalg import norm
from scipy.optimize import newton_krylov, root
from scipy.optimize.nonlin import NoConvergence
from .. import EPSolver, Logger, LogLevel, mpi
from .._tamaas import _tolerance_manager
from .._tamaas import _DFSANESolver as DFSANECXXSolver
__all__ = ['NLNoConvergence',
'DFSANESolver',
'DFSANECXXSolver',
'NewtonKrylovSolver',
'ToleranceManager']
class NLNoConvergence(Exception):
"""Convergence not reached exception"""
class ScipySolver(EPSolver):
"""
Base class for solvers wrapping SciPy routines
"""
def __init__(self, residual, _, callback=None):
super(ScipySolver, self).__init__(residual)
if mpi.size() > 1:
raise RuntimeError("Scipy solvers cannot be used with MPI; "
"DFSANECXXSolver can be used instead")
self.callback = callback
self._x = self.getStrainIncrement()
self._residual = self.getResidual()
self.options = {'ftol': 0, 'fatol': 1e-9}
def solve(self):
"""
Solve the nonlinear plasticity equation using the scipy_solve routine
"""
# For initial guess, compute the strain due to boundary tractions
# self._residual.computeResidual(self._x)
# self._x[...] = self._residual.getVector()
EPSolver.beforeSolve(self)
# Scipy root callback
def compute_residual(vec):
self._residual.computeResidual(vec)
return self._residual.getVector().copy()
# Solve
self._x[...] = self.scipy_solve(compute_residual)
# Computing displacements
self._residual.computeResidualDisplacement(self._x)
def reset(self):
"Set solution vector to zero"
self._x[...] = 0
class NewtonKrylovSolver(ScipySolver):
"""
Solve using a finite-difference Newton-Krylov method
"""
def __init__(self, residual, model=None, callback=None):
ScipySolver.__init__(self, residual, model,
callback=callback)
def scipy_solve(self, compute_residual):
"Solve R(delta epsilon) = 0 using a newton-krylov method"
try:
return newton_krylov(compute_residual, self._x,
f_tol=self.tolerance,
verbose=True, callback=self.callback)
except NoConvergence:
raise NLNoConvergence("Newton-Krylov did not converge")
class DFSANESolver(ScipySolver):
"""
Solve using a spectral residual jacobianless method
"""
def __init__(self, residual, model=None, callback=None):
ScipySolver.__init__(self, residual, model,
callback=callback)
def scipy_solve(self, compute_residual):
"Solve R(delta epsilon) = 0 using a df-sane method"
solution = root(compute_residual,
self._x,
method='df-sane',
options={'ftol': 0, 'fatol': self.tolerance},
callback=self.callback)
Logger().get(LogLevel.info) << \
"DF-SANE/Scipy: {} ({} iterations, {})".format(
solution.message,
solution.nit,
self.tolerance)
if not solution.success:
raise NLNoConvergence("DF-SANE/Scipy did not converge")
return solution.x.copy()
def ToleranceManager(start, end, rate):
"Decorator to manage tolerance of non-linear solver"
# start /= rate # just anticipating first multiplication
def actual_decorator(cls):
orig_init = cls.__init__
orig_solve = cls.solve
orig_update_state = cls.updateState
@wraps(cls.__init__)
def __init__(obj, *args, **kwargs):
orig_init(obj, *args, **kwargs)
obj.setToleranceManager(_tolerance_manager(start, end, rate))
@wraps(cls.solve)
def new_solve(obj, *args, **kwargs):
ftol = obj.tolerance
ftol *= rate
obj.tolerance = max(ftol, end)
return orig_solve(obj, *args, **kwargs)
@wraps(cls.updateState)
def updateState(obj, *args, **kwargs):
obj.tolerance = start
return orig_update_state(obj, *args, **kwargs)
cls.__init__ = __init__
# cls.solve = new_solve
# cls.updateState = updateState
return cls
return actual_decorator