diff --git a/examples/.gitignore b/examples/.gitignore index f3be36e..fb6ae0a 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -1 +1,3 @@ paraview +hdf5 +numpy* diff --git a/examples/stresses.py b/examples/stresses.py index 77e31fa..53151ff 100644 --- a/examples/stresses.py +++ b/examples/stresses.py @@ -1,92 +1,119 @@ #!/usr/bin/env python3 # @file # @section LICENSE # # Copyright (©) 2016-2020 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 -import argparse -from tamaas.dumpers import UVWDumper as Dumper +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 = [127, 127, 127] -system_size = [1., 1., 1.] +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) +local_size = model.getBoundaryDiscretization() +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.getTraction() -traction[r < a, 2] = p_0 * np.sqrt(1 - (r[r < a] / a)**2) +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.getDisplacement() stress = residual.getStress() gradient = residual.getVector() # Applying operator boussinesq = model.getIntegralOperator("boussinesq") boussinesq_gradient = model.getIntegralOperator("boussinesq_gradient") boussinesq.apply(traction, displacement) boussinesq_gradient.apply(traction, gradient) model.applyElasticity(stress, gradient) # Dumper dumper_helper = Dumper(args.name, 'stress') model.addDumper(dumper_helper) model.dump() -print("Done") + +# 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/tamaas/dumpers/_helper.py b/python/tamaas/dumpers/_helper.py index 24cb7ba..66bbbcb 100644 --- a/python/tamaas/dumpers/_helper.py +++ b/python/tamaas/dumpers/_helper.py @@ -1,141 +1,141 @@ # -*- mode:python; coding: utf-8 -*- # @file # @section LICENSE # # Copyright (©) 2016-2020 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 . """ Helper functions for dumpers """ from functools import wraps from collections import defaultdict import os import numpy as np from .. import model_type, type_traits, mpi __all__ = ["step_dump", "directory_dump"] def _is_surface_field(field, model): bn = model.getBoundaryDiscretization() return list(field.shape[:len(bn)]) == bn def local_slice(field, model): n = model.getDiscretization() bn = model.getBoundaryDiscretization() gshape = mpi.global_shape(bn) offsets = np.zeros_like(gshape) offsets[0] = mpi.local_offset(gshape) if not _is_surface_field(field, model) and len(n) > len(bn): gshape = [n[0]] + gshape offsets = np.concatenate(([0], offsets)) return tuple((slice(offset, offset+size, None) for offset, size in zip(offsets, field.shape))) def step_dump(cls): """ Decorator for dumper with counter for steps """ orig_init = cls.__init__ orig_dump = cls.dump @wraps(cls.__init__) def __init__(obj, *args, **kwargs): orig_init(obj, *args, **kwargs) obj.count = 0 def postfix(obj): return "_{:04d}".format(obj.count) @wraps(cls.dump) def dump(obj, *args, **kwargs): orig_dump(obj, *args, **kwargs) obj.count += 1 cls.__init__ = __init__ cls.dump = dump cls.postfix = property(postfix) return cls def directory_dump(directory=""): "Decorator for dumper in a directory" def actual_decorator(cls): orig_dump = cls.dump orig_filepath = cls.file_path.fget @wraps(cls.dump) def dump(obj, *args, **kwargs): if not os.path.exists(directory) and mpi.rank() == 0: os.mkdir(directory) - if not os.path.isdir(directory): + if not os.path.isdir(directory) and mpi.rank() == 0: raise Exception('{} is not a directory'.format(directory)) orig_dump(obj, *args, **kwargs) @wraps(cls.file_path.fget) def file_path(obj): return os.path.join(directory, orig_filepath(obj)) cls.dump = dump cls.file_path = property(file_path) return cls return actual_decorator def hdf5toVTK(inpath, outname): "Convert HDF5 dump of a model to VTK" import h5py as h5 # noqa from .. import ModelFactory # noqa from . import UVWDumper # noqa type_translate = { 'model_type.basic_1d': model_type.basic_1d, 'model_type.basic_2d': model_type.basic_2d, 'model_type.surface_1d': model_type.surface_1d, 'model_type.surface_2d': model_type.surface_2d, 'model_type.volume_1d': model_type.volume_1d, 'model_type.volume_2d': model_type.volume_2d, } with h5.File(inpath, 'r') as h5file: model_t = h5file.attrs['model_type'] system_size = list(h5file.attrs['system_size']) n = list(h5file.attrs['discretization']) model = ModelFactory.createModel(type_translate[model_t], system_size, n) fields = [] for field in h5file: model.registerField(field, h5file[field][:]) fields.append(field) uvw_dumper = UVWDumper(outname, *fields) uvw_dumper << model