diff --git a/tests/test_epic.py b/tests/test_epic.py index e8afe7a..e46e64e 100644 --- a/tests/test_epic.py +++ b/tests/test_epic.py @@ -1,78 +1,81 @@ # -*- 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 . from __future__ import division, print_function import pytest from conftest import pkridfn import tamaas as tm from numpy.linalg import norm from tamaas.nonlinear_solvers import DFSANESolver @pytest.fixture(scope="module", params=[tm.PolonskyKeerRey.pressure], ids=pkridfn) -def pkr_coarse(hertz_coarse, request): - model = tm.ModelFactory.createModel(tm.model_type.basic_2d, - [hertz_coarse.domain_size, - hertz_coarse.domain_size], - [hertz_coarse.n, hertz_coarse.n]) - model.E, model.nu = hertz_coarse.e_star, 0 - solver = tm.PolonskyKeerRey(model, hertz_coarse.surface, 1e-12, - request.param, - request.param) - solver.solve(hertz_coarse.load) +def model_setup(hertz_coarse, request): + models, solvers = [], [] - return model, hertz_coarse + for mtype in [tm.model_type.basic_2d, tm.model_type.volume_2d]: + dim = tm.type_traits[mtype].dimension + n = [hertz_coarse.n] * dim + d = [hertz_coarse.domain_size] * dim + if dim == 3: + n[0] = 2 + d[0] = 0.001 -@pytest.mark.xfail(tm.TamaasInfo.backend == 'tbb', - reason='TBB reductions are unstable?') -def test_epic(pkr_coarse): - """Test the full elastic-plastic solve step in elasticity""" - # We use computed values from basic PKR to test - model_elastic, hertz = pkr_coarse + model = tm.ModelFactory.createModel(mtype, d, n) + model.E, model.nu = hertz_coarse.e_star, 0 + + solver = tm.PolonskyKeerRey(model, hertz_coarse.surface, 1e-12, + request.param, + request.param) - model = tm.ModelFactory.createModel( - tm.model_type.volume_2d, - [0.001, hertz.domain_size, hertz.domain_size], - [2, hertz.n, hertz.n] - ) + models.append(model) + solvers.append(solver) - model.E, model.nu = hertz.e_star, 0 - residual = tm.ModelFactory.createResidual( + epsolver = DFSANESolver(tm.ModelFactory.createResidual( model, sigma_y=1e2 * model.E, hardening=0 - ) + )) + + solvers[1] = tm.EPICSolver(solvers[1], epsolver, 1e-12) - epsolver = DFSANESolver(residual) - csolver = tm.PolonskyKeerRey(model, hertz.surface, 1e-12) + for solver in solvers: + solver.solve(hertz_coarse.load) - epic = tm.EPICSolver(csolver, epsolver, 1e-12) - epic.solve(hertz.load) + return models, hertz_coarse + + +@pytest.mark.xfail(tm.TamaasInfo.backend == 'tbb', + reason='TBB reductions are unstable?') +def test_energy(model_setup): + """Test the full elastic-plastic solve step in elasticity""" + # We use computed values from basic PKR to test + (model_elastic, model_plastic), hertz = model_setup - error = norm((model['traction'][..., 2] - model_elastic['traction']) - * (model['displacement'][0, ..., 2] - - model_elastic['displacement'])) + error = norm( + (model_plastic.traction[..., 2] - model_elastic.traction) + * (model_plastic.displacement[0, ..., 2] - model_elastic.displacement)) error /= norm(hertz.pressure * hertz.displacement) assert error < 1e-16 diff --git a/tests/test_saturated_pressure.py b/tests/test_saturated_pressure.py index 270c15c..f104006 100644 --- a/tests/test_saturated_pressure.py +++ b/tests/test_saturated_pressure.py @@ -1,66 +1,73 @@ # -*- 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 . from __future__ import print_function, division import numpy as np import tamaas as tm +import pytest -def test_saturated_pressure(): - tm.set_log_level(tm.LogLevel.debug) +@pytest.fixture(scope="module") +def saturated(): grid_size = 256 load = 0.06 p_sat = 0.4 iso = tm.Isopowerlaw2D() iso.q0 = 4 iso.q1 = 4 iso.q2 = 16 iso.hurst = 0.8 sg = tm.SurfaceGeneratorFilter2D([grid_size] * 2) sg.random_seed = 2 sg.spectrum = iso surface = sg.buildSurface() surface *= 0.01 / iso.rmsHeights() 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 solver = tm.KatoSaturated(model, surface, 1e-12, p_sat) solver.max_iter = 6000 - assert solver.solve(load) < 1e-12 + solver.solve(load) - tractions = model['traction'] + return model, p_sat, load - mean_pressure_error = abs(np.mean(tractions)-load)/load - max_pressure_error = np.max(tractions)-p_sat - gaps = model['gap'] - gaps[np.where(tractions == p_sat)] = 0. - press = tractions - orthogonality_error = np.max(press*gaps) / (press.max() * gaps.max()) +def test_mean_pressure(saturated): + model, _, load = saturated + assert abs(model.traction.mean() - load) / load < 1e-12 - assert mean_pressure_error < 1e-12 and max_pressure_error < 1e-12 and \ - orthogonality_error < 4e-8 + +def test_max_pressure(saturated): + model, p_sat, _ = saturated + assert abs(model.traction.max() - p_sat) / p_sat < 1e-12 + + +def test_orthogonality(saturated): + model, _, _ = saturated + error = np.max(model.traction * model['gap']) \ + / (model.traction.max() * model['gap'].max()) + assert error < 4e-8 diff --git a/tests/test_surface.py b/tests/test_surface.py index f85af92..74bad02 100644 --- a/tests/test_surface.py +++ b/tests/test_surface.py @@ -1,137 +1,135 @@ # -*- 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 . from __future__ import print_function, division import numpy as np import tamaas as tm import pytest import scipy.integrate as integrate from scipy.special import jv from numpy import pi from scipy.interpolate import interp2d -def compute_mse(values, mean): - return np.mean((values - mean)**2) - - def spectrum(r, isopowerlaw): kl, kr, ks = isopowerlaw.q0, isopowerlaw.q1, isopowerlaw.q2 H = isopowerlaw.hurst C = 1. if r > ks: return 0 elif r > kr: return C * (r/float(kr))**(-2.*(H+1)) elif r > kl: return C else: return 0 def integrate_bessel(x, spectrum, n): return 2 * pi * integrate.quad(lambda y: y*jv(0, y*x) * spectrum(n*y), 0, np.inf, limit=200)[0] def radial_average(field): x = np.linspace(-1, 1, field.shape[0]) y = np.linspace(-1, 1, field.shape[1]) interpolation = interp2d(x, y, field) n = field.shape[0] // 2 radii = np.linspace(0, 1, n) thetas = np.linspace(0, 2 * pi, endpoint=False) sum = np.zeros_like(radii) for Θ in thetas: for i, r in enumerate(radii): sum[i] += interpolation(r * np.cos(Θ), r * np.sin(Θ)) return sum / thetas.size integrate_bessel = np.vectorize(integrate_bessel, otypes=[tm.dtype]) @pytest.fixture(scope="module", params=[tm.SurfaceGeneratorFilter2D, tm.SurfaceGeneratorRandomPhase2D]) -def isopowerlaw(request): +def stats(request): iso = tm.Isopowerlaw2D() iso.q0 = 16 iso.q1 = 32 iso.q2 = 64 iso.hurst = 0.8 N = 243 sg = request.param() sg.spectrum = iso sg.shape = [N, N] analytical_stats = { "rms_heights": iso.rmsHeights(), "rms_slopes_spectral": iso.rmsSlopes(), "moments": iso.moments(), "alpha": iso.alpha() } - return sg, analytical_stats - + stats = {k: [] for k in analytical_stats} -def test_surface_statistics(isopowerlaw): - sg, analytical_stats = isopowerlaw + acf = np.zeros(sg.shape, dtype=tm.dtype) realizations = 1000 - L = 1 * sg.spectrum.q0 - n = sg.shape[0] // 2 - x = np.linspace(0, L/2, n) - - true_acf = (L / (2 * pi))**2 \ - * integrate_bessel(x, lambda x: spectrum(x, sg.spectrum), L / (2 * pi)) - - stats = {k: [] for k in analytical_stats} - acf = np.zeros(sg.shape, dtype=tm.dtype) - for i in range(realizations): sg.random_seed = i s = sg.buildSurface() stats['rms_heights'].append( tm.Statistics2D.computeRMSHeights(s)) stats['rms_slopes_spectral'].append( tm.Statistics2D.computeSpectralRMSSlope(s)) stats['moments'].append(tm.Statistics2D.computeMoments(s)) acf += tm.Statistics2D.computeAutocorrelation(s) / realizations - # Testing only the more reliable measures - for key in ['rms_heights', 'rms_slopes_spectral']: - mu = np.mean(stats[key]) - error = np.abs(mu - analytical_stats[key]) / analytical_stats[key] - assert error < 3e-3, "Error in measure of {}".format(key) + L = 1 * sg.spectrum.q0 + n = sg.shape[0] // 2 + x = np.linspace(0, L/2, n) + + true_acf = (L / (2 * pi))**2 \ + * integrate_bessel(x, lambda x: spectrum(x, sg.spectrum), L / (2 * pi)) + + return analytical_stats, stats, acf, true_acf + + +@pytest.mark.parametrize('key', ['rms_heights', 'rms_slopes_spectral']) +def test_statistics(stats, key): + analytical_stats, stats, _, _ = stats + mu = np.mean(stats[key]) + error = np.abs(mu - analytical_stats[key]) / analytical_stats[key] + assert error < 3e-3 + +def test_autocorrelation(stats): + _, _, acf, true_acf = stats acf = radial_average(np.fft.fftshift(acf)) acf_error = np.sum((acf-true_acf)**2) / np.sum(true_acf**2) - assert acf_error < 2e-3, "Error with analytical ACF" + assert acf_error < 2e-3