diff --git a/CMakeLists.txt b/CMakeLists.txt index 16351f0..4ad6309 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,247 +1,288 @@ # ============================================================================= # file CMakeLists.txt # # @author Till Junge # # @date 08 Jan 2018 # # @brief Main configuration file # # @section LICENSE # # Copyright © 2018 Till Junge # # µSpectre is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation, either version 3, or (at # your option) any later version. # # µSpectre 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 # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with µSpectre; see the file COPYING. If not, write to the # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # # Additional permission under GNU GPL version 3 section 7 # # If you modify this Program, or any covered work, by linking or combining it # with proprietary FFT implementations or numerical libraries, containing parts # covered by the terms of those libraries' licenses, the licensors of this # Program grant you additional permission to convey the resulting work. # ============================================================================= cmake_minimum_required(VERSION 3.0.0) project(µSpectre) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(BUILD_SHARED_LIBS ON) set(MUSPECTRE_PYTHON_MAJOR_VERSION 3) add_compile_options(-Wall -Wextra -Weffc++) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) set(MAKE_DOC_TARGET "OFF" CACHE BOOL "If on, a target dev_doc (which builds the documentation) is added") set(MAKE_TESTS "ON" CACHE BOOL "If on, several ctest targets will be built automatically") set(MAKE_EXAMPLES "ON" CACHE BOOL "If on, the executables in the bin folder will be compiled") set(MAKE_BENCHMARKS "ON" CACHE BOOL "If on, the benchmarks will be compiled") set(MPI_PARALLEL "OFF" CACHE BOOL "If on, MPI-parallel solvers become available") set(RUNNING_IN_CI "OFF" CACHE INTERNAL "changes output format for tests") if(${MAKE_TESTS}) enable_testing() find_package(Boost COMPONENTS unit_test_framework REQUIRED) endif(${MAKE_TESTS}) if(${MPI_PARALLEL}) add_definitions(-DWITH_MPI) find_package(MPI) if (NOT ${MPI_FOUND}) message(SEND_ERROR "You chose MPI but CMake cannot find the MPI package") endif(NOT ${MPI_FOUND}) endif(${MPI_PARALLEL}) include(muspectreTools) include(cpplint) string( TOLOWER "${CMAKE_BUILD_TYPE}" build_type ) if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") # using Clang add_compile_options(-Wno-missing-braces) if ("debug" STREQUAL "${build_type}") add_compile_options(-O0) endif() elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") # using GCC add_compile_options(-Wno-non-virtual-dtor) add_compile_options(-march=native) if (("relwithdebinfo" STREQUAL "${build_type}") OR ("release" STREQUAL "${build_type}" )) add_compile_options(-march=native) endif() if ("debug" STREQUAL "${build_type}" ) add_compile_options(-O0) endif() elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") # using Intel C++ elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") # using Visual Studio C++ endif() # Do not trust old gcc. the std::optional has memory bugs if(${CMAKE_COMPILER_IS_GNUCC}) if(${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 6.0.0) add_definitions(-DNO_EXPERIMENTAL) endif() endif() - add_external_package(Eigen3 VERSION 3.3.0 CONFIG) add_external_package(pybind11 VERSION 2.2 CONFIG) +find_package(PythonLibsNew ${MUSPECTRE_PYTHON_MAJOR_VERSION} MODULE REQUIRED) include_directories( ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR} ) if(APPLE) include_directories(${CMAKE_INSTALL_PREFIX}/include ${Boost_INCLUDE_DIRS}) endif() #build tests (these are before we add -Werror to the compile options) if (${MAKE_TESTS}) ############################################################################## # build library tests file( GLOB TEST_SRCS "${CMAKE_SOURCE_DIR}/tests/test_*.cc") add_executable(main_test_suite tests/main_test_suite.cc ${TEST_SRCS}) target_link_libraries(main_test_suite ${Boost_LIBRARIES} muSpectre) muSpectre_add_test(main_test_suite TYPE BOOST main_test_suite --report_level=detailed) + add_executable(mattb_test tests/main_test_suite tests/test_materials_toolbox.cc) + target_link_libraries(mattb_test ${Boost_LIBRARIES} muSpectre) + muSpectre_add_test(mattb_test TYPE BOOST mattb_test --report_level=detailed) + # build header tests file( GLOB HEADER_TEST_SRCS "${CMAKE_SOURCE_DIR}/tests/header_test_*.cc") foreach(header_test ${HEADER_TEST_SRCS}) get_filename_component(header_test_name ${header_test} NAME_WE) string(SUBSTRING ${header_test_name} 12 -1 test_name) list(APPEND header_tests ${test_name}) add_executable(${test_name} tests/main_test_suite.cc ${header_test}) target_link_libraries(${test_name} ${Boost_LIBRARIES} Eigen3::Eigen) target_include_directories(${test_name} INTERFACE ${muSpectre_INCLUDES}) muSpectre_add_test(${test_name} TYPE BOOST ${test_name} --report_level=detailed) endforeach(header_test ${HEADER_TEST_SRCS}) add_custom_target(header_tests) add_dependencies(header_tests ${header_tests}) + ############################################################################## + # build py_comparison tests + file (GLOB PY_COMP_TEST_SRCS "${CMAKE_SOURCE_DIR}/tests/py_comparison_*.cc") + + find_package(PythonInterp ${MUSPECTRE_PYTHON_MAJOR_VERSION} REQUIRED) + foreach(py_comp_test ${PY_COMP_TEST_SRCS}) + get_filename_component(py_comp_test_fname ${py_comp_test} NAME_WE) + + string (SUBSTRING ${py_comp_test_fname} 19 -1 py_comp_test_name) + + pybind11_add_module(${py_comp_test_name} ${py_comp_test}) + target_include_directories(${py_comp_test_name} PUBLIC + ${PYTHON_INCLUDE_DIRS}) + target_link_libraries(${py_comp_test_name} PRIVATE muSpectre) + configure_file( + tests/${py_comp_test_fname}.py + "${CMAKE_BINARY_DIR}/${py_comp_test_fname}.py" + COPYONLY) + muSpectre_add_test(${py_comp_test_fname} + TYPE PYTHON ${py_comp_test_fname}.py) + + endforeach(py_comp_test ${PY_COMP_TEST_SRCS}) + ############################################################################## # copy python test file( GLOB PY_TEST_SRCS "${CMAKE_SOURCE_DIR}/tests/python_*.py") foreach(pytest ${PY_TEST_SRCS}) get_filename_component(pytest_name ${pytest} NAME) configure_file( ${pytest} "${CMAKE_BINARY_DIR}/${pytest_name}" COPYONLY) endforeach(pytest ${PY_TEST_SRCS}) - find_package(PythonInterp ${MUSPECTRE_PYTHON_MAJOR_VERSION} REQUIRED) muSpectre_add_test(python_binding_test TYPE PYTHON python_binding_tests.py) if(${MPI_PARALLEL}) ############################################################################ # add MPI tests file( GLOB TEST_SRCS "${CMAKE_SOURCE_DIR}/tests/mpi_test_*.cc") add_executable(mpi_main_test_suite tests/mpi_main_test_suite.cc ${TEST_SRCS}) target_link_libraries(mpi_main_test_suite ${Boost_LIBRARIES} muSpectre) muSpectre_add_test(mpi_main_test_suite1 TYPE BOOST MPI_NB_PROCS 1 mpi_main_test_suite --report_level=detailed) muSpectre_add_test(mpi_main_test_suite2 TYPE BOOST MPI_NB_PROCS 2 mpi_main_test_suite --report_level=detailed) muSpectre_add_test(python_mpi_binding_test1 TYPE PYTHON MPI_NB_PROCS 1 python_mpi_binding_tests.py) muSpectre_add_test(python_mpi_binding_test2 TYPE PYTHON MPI_NB_PROCS 2 python_mpi_binding_tests.py) endif(${MPI_PARALLEL}) endif(${MAKE_TESTS}) ################################################################################ # compile the library add_compile_options( -Werror) add_subdirectory( ${CMAKE_SOURCE_DIR}/src/ ) add_subdirectory( ${CMAKE_SOURCE_DIR}/language_bindings/ ) if (${MAKE_DOC_TARGET}) add_subdirectory( ${CMAKE_SOURCE_DIR}/doc/ ) endif() ################################################################################ if (${MAKE_EXAMPLES}) #compile executables set(binaries ${CMAKE_SOURCE_DIR}/bin/demonstrator1.cc ${CMAKE_SOURCE_DIR}/bin/demonstrator_dynamic_solve.cc ${CMAKE_SOURCE_DIR}/bin/demonstrator2.cc ${CMAKE_SOURCE_DIR}/bin/hyper-elasticity.cc ${CMAKE_SOURCE_DIR}/bin/small_case.cc) if (${MPI_PARALLEL}) set (binaries ${binaries} ${CMAKE_SOURCE_DIR}/bin/demonstrator_mpi.cc ) endif (${MPI_PARALLEL}) foreach(binaryfile ${binaries}) get_filename_component(binaryname ${binaryfile} NAME_WE) add_executable(${binaryname} ${binaryfile}) target_link_libraries(${binaryname} ${Boost_LIBRARIES} muSpectre) endforeach(binaryfile ${binaries}) - #or copy them + # or copy them file (GLOB pybins "${CMAKE_SOURCE_DIR}/bin/*.py") foreach(pybin ${pybins}) get_filename_component(binaryname ${pybin} NAME_WE) configure_file( ${pybin} "${CMAKE_BINARY_DIR}/${binaryname}.py" COPYONLY) endforeach(pybin ${pybins}) + + # additional files to copy + set (FILES_FOR_COPY + ${CMAKE_SOURCE_DIR}/bin/odd_image.npz) + + foreach(FILE_FOR_COPY ${FILES_FOR_COPY}) + + get_filename_component(binaryname ${FILE_FOR_COPY} NAME) + configure_file( + ${FILE_FOR_COPY} + "${CMAKE_BINARY_DIR}/${binaryname}" + COPYONLY) + endforeach(FILE_FOR_COPY ${FILES_FOR_COPY}) + endif (${MAKE_EXAMPLES}) + ################################################################################ # compile benchmarks if(${MAKE_BENCHMARKS}) file(GLOB benchmarks "${CMAKE_SOURCE_DIR}/benchmarks/benchmark*cc") foreach(benchmark ${benchmarks}) get_filename_component(benchmark_name ${benchmark} NAME_WE) add_executable(${benchmark_name} ${benchmark}) target_link_libraries(${benchmark_name} ${BOOST_LIBRARIES} muSpectre) endforeach(benchmark ${benchmark}) endif(${MAKE_BENCHMARKS}) cpplint_add_subdirectory("${CMAKE_SOURCE_DIR}/src" "") cpplint_add_subdirectory("${CMAKE_SOURCE_DIR}/tests" "") cpplint_add_subdirectory("${CMAKE_SOURCE_DIR}/language_bindings" "") cpplint_add_subdirectory("${CMAKE_SOURCE_DIR}/bin" "--filter=-build/namespaces") diff --git a/bin/odd_image.npz b/bin/odd_image.npz new file mode 100644 index 0000000..62536ef Binary files /dev/null and b/bin/odd_image.npz differ diff --git a/bin/small_elasto_plastic_case.py b/bin/small_elasto_plastic_case.py new file mode 100644 index 0000000..25e4348 --- /dev/null +++ b/bin/small_elasto_plastic_case.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +""" +file small_case.py + +@author Till Junge + +@date 12 Jan 2018 + +@brief small case for debugging elasto-plasticity + +@section LICENSE + +Copyright © 2018 Till Junge + +µSpectre is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3, or (at +your option) any later version. + +µSpectre 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 +General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with µSpectre; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. + +Additional permission under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or combining it +with proprietary FFT implementations or numerical libraries, containing parts +covered by the terms of those libraries' licenses, the licensors of this +Program grant you additional permission to convey the resulting work. +""" + +import sys +import os +import numpy as np + +sys.path.append(os.path.join(os.getcwd(), "language_bindings/python")) +import muSpectre as µ + + +resolution = [3, 3] +center = np.array([r//2 for r in resolution]) +incl = resolution[0]//5 + +lengths = [7., 5.] +formulation = µ.Formulation.finite_strain + +K = .833 +mu= .386 +H = .004 +tauy0 = .006 +Young = 9*K*mu/(3*K + mu) +Poisson = (3*K-2*mu)/(2*(3*K+mu)) +rve = µ.Cell(resolution, lengths, formulation) +hard = µ.material.MaterialHyperElastoPlastic1_2d.make( + rve, "hard", Young, Poisson, 2*tauy0, h=2*H) +soft = µ.material.MaterialHyperElastoPlastic1_2d.make( + rve, "soft", Young, Poisson, tauy0, h= H) + + +for i, pixel in enumerate(rve): + #if np.linalg.norm(center - np.array(pixel),2) # # @date 08 Jan 2018 # # @brief configuration for python binding using pybind11 # # @section LICENSE # # Copyright © 2018 Till Junge # # µSpectre is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation, either version 3, or (at # your option) any later version. # # µSpectre 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 # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with µSpectre; see the file COPYING. If not, write to the # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # # Additional permission under GNU GPL version 3 section 7 # # If you modify this Program, or any covered work, by linking or combining it # with proprietary FFT implementations or numerical libraries, containing parts # covered by the terms of those libraries' licenses, the licensors of this # Program grant you additional permission to convey the resulting work. # ============================================================================= # FIXME! The user should have a choice to configure this path. execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-m" "site" "--user-site" RESULT_VARIABLE _PYTHON_SUCCESS OUTPUT_VARIABLE PYTHON_USER_SITE ERROR_VARIABLE _PYTHON_ERROR_VALUE) if(NOT _PYTHON_SUCCESS MATCHES 0) message(FATAL_ERROR "Python config failure:\n${_PYTHON_ERROR_VALUE}") endif() string(REGEX REPLACE "\n" "" PYTHON_USER_SITE ${PYTHON_USER_SITE}) set (PY_BINDING_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_module.cc ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_common.cc ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_cell.cc ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_material.cc + ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_material_linear_elastic1.cc + ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_material_linear_elastic2.cc + ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_material_linear_elastic3.cc + ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_material_linear_elastic4.cc + ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_material_hyper_elasto_plastic1.cc ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_material_linear_elastic_generic.cc ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_solvers.cc ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_fftengine.cc ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_projections.cc ${CMAKE_CURRENT_SOURCE_DIR}/bind_py_field_collection.cc ) if (${USE_FFTWMPI}) add_definitions(-DWITH_FFTWMPI) endif(${USE_FFTWMPI}) if (${USE_PFFT}) add_definitions(-DWITH_PFFT) endif(${USE_PFFT}) find_package(PythonLibsNew ${MUSPECTRE_PYTHON_MAJOR_VERSION} MODULE REQUIRED) # On OS X, add -Wno-deprecated-declarations IF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") add_compile_options(-Wno-deprecated-declarations) ENDIF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") pybind11_add_module(pyMuSpectreLib ${PY_BINDING_SRCS}) target_link_libraries(pyMuSpectreLib PRIVATE muSpectre) # Want to rename the output, so that the python module is called muSpectre set_target_properties(pyMuSpectreLib PROPERTIES OUTPUT_NAME _muSpectre) target_include_directories(pyMuSpectreLib PUBLIC ${PYTHON_INCLUDE_DIRS}) add_custom_target(pyMuSpectre ALL SOURCES muSpectre/__init__.py muSpectre/fft.py) add_custom_command(TARGET pyMuSpectre POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/language_bindings/python/muSpectre $/muSpectre) install(TARGETS pyMuSpectreLib LIBRARY DESTINATION ${PYTHON_USER_SITE}) install(FILES muSpectre/__init__.py muSpectre/fft.py DESTINATION ${PYTHON_USER_SITE}/muSpectre) diff --git a/language_bindings/python/bind_py_cell.cc b/language_bindings/python/bind_py_cell.cc index 6d3514f..9fbc090 100644 --- a/language_bindings/python/bind_py_cell.cc +++ b/language_bindings/python/bind_py_cell.cc @@ -1,224 +1,262 @@ /** * @file bind_py_cell.cc * * @author Till Junge * * @date 09 Jan 2018 * * @brief Python bindings for the cell factory function * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #include "common/common.hh" #include "common/ccoord_operations.hh" #include "cell/cell_factory.hh" #include "cell/cell_base.hh" #ifdef WITH_FFTWMPI #include "fft/fftwmpi_engine.hh" #endif #ifdef WITH_PFFT #include "fft/pfft_engine.hh" #endif #include #include +#include #include "pybind11/eigen.h" #include #include -using namespace muSpectre; // NOLINT // TODO(junge): figure this out +using muSpectre::Ccoord_t; +using muSpectre::Dim_t; +using muSpectre::Formulation; +using muSpectre::Rcoord_t; +using pybind11::literals::operator""_a; namespace py = pybind11; -using namespace pybind11::literals; // NOLINT: recommended usage /** * cell factory for specific FFT engine */ #ifdef WITH_MPI template void add_parallel_cell_factory_helper(py::module & mod, const char * name) { using Ccoord = Ccoord_t; using Rcoord = Rcoord_t; mod.def(name, [](Ccoord res, Rcoord lens, Formulation form, size_t comm) { return make_parallel_cell, FFTEngine>( std::move(res), std::move(lens), std::move(form), std::move(Communicator(MPI_Comm(comm)))); }, "resolutions"_a, "lengths"_a = CcoordOps::get_cube(1.), "formulation"_a = Formulation::finite_strain, "communicator"_a = size_t(MPI_COMM_SELF)); } #endif /** * the cell factory is only bound for default template parameters */ template void add_cell_factory_helper(py::module & mod) { using Ccoord = Ccoord_t; using Rcoord = Rcoord_t; mod.def("CellFactory", [](Ccoord res, Rcoord lens, Formulation form) { return make_cell(std::move(res), std::move(lens), std::move(form)); }, - "resolutions"_a, "lengths"_a = CcoordOps::get_cube(1.), + "resolutions"_a, + "lengths"_a = muSpectre::CcoordOps::get_cube(1.), "formulation"_a = Formulation::finite_strain); #ifdef WITH_FFTWMPI add_parallel_cell_factory_helper>( mod, "FFTWMPICellFactory"); #endif #ifdef WITH_PFFT add_parallel_cell_factory_helper>(mod, "PFFTCellFactory"); #endif } void add_cell_factory(py::module & mod) { - add_cell_factory_helper(mod); - add_cell_factory_helper(mod); + add_cell_factory_helper(mod); + add_cell_factory_helper(mod); } /** * CellBase for which the material and spatial dimension are identical */ template void add_cell_base_helper(py::module & mod) { std::stringstream name_stream{}; name_stream << "CellBase" << dim << 'd'; const std::string name = name_stream.str(); - using sys_t = CellBase; - py::class_(mod, name.c_str()) + using sys_t = muSpectre::CellBase; + py::class_(mod, name.c_str()) .def("__len__", &sys_t::size) .def("__iter__", [](sys_t & s) { return py::make_iterator(s.begin(), s.end()); }) .def("initialise", &sys_t::initialise, - "flags"_a = FFT_PlanFlags::estimate) + "flags"_a = muSpectre::FFT_PlanFlags::estimate) .def( "directional_stiffness", [](sys_t & cell, py::EigenDRef & v) { if ((size_t(v.cols()) != cell.size() || size_t(v.rows()) != dim * dim)) { std::stringstream err{}; err << "need array of shape (" << dim * dim << ", " << cell.size() << ") but got (" << v.rows() << ", " << v.cols() << ")."; throw std::runtime_error(err.str()); } if (!cell.is_initialised()) { cell.initialise(); } const std::string out_name{"temp output for directional stiffness"}; const std::string in_name{"temp input for directional stiffness"}; constexpr bool create_tangent{true}; auto & K = cell.get_tangent(create_tangent); auto & input = cell.get_managed_T2_field(in_name); auto & output = cell.get_managed_T2_field(out_name); input.eigen() = v; cell.directional_stiffness(K, input, output); return output.eigen(); }, "δF"_a) .def("project", [](sys_t & cell, py::EigenDRef & v) { if ((size_t(v.cols()) != cell.size() || size_t(v.rows()) != dim * dim)) { std::stringstream err{}; err << "need array of shape (" << dim * dim << ", " << cell.size() << ") but got (" << v.rows() << ", " << v.cols() << ")."; throw std::runtime_error(err.str()); } if (!cell.is_initialised()) { cell.initialise(); } const std::string in_name{"temp input for projection"}; auto & input = cell.get_managed_T2_field(in_name); input.eigen() = v; cell.project(input); return input.eigen(); }, "field"_a) - .def("get_strain", - [](sys_t & s) { return Eigen::ArrayXXd(s.get_strain().eigen()); }) + .def("get_strain", [](sys_t & s) { return s.get_strain().eigen(); }, + py::return_value_policy::reference_internal) .def("get_stress", [](sys_t & s) { return Eigen::ArrayXXd(s.get_stress().eigen()); }) .def_property_readonly("size", &sys_t::size) .def("evaluate_stress_tangent", [](sys_t & cell, py::EigenDRef & v) { if ((size_t(v.cols()) != cell.size() || size_t(v.rows()) != dim * dim)) { std::stringstream err{}; err << "need array of shape (" << dim * dim << ", " << cell.size() << ") but got (" << v.rows() << ", " << v.cols() << ")."; throw std::runtime_error(err.str()); } auto & strain{cell.get_strain()}; strain.eigen() = v; - cell.evaluate_stress_tangent(strain); + auto stress_tgt{cell.evaluate_stress_tangent(strain)}; + return std::tuple( + std::get<0>(stress_tgt).eigen(), + std::get<1>(stress_tgt).eigen()); }, "strain"_a) + .def("evaluate_stress", + [](sys_t & cell, py::EigenDRef & v) { + if ((size_t(v.cols()) != cell.size() || + size_t(v.rows()) != dim * dim)) { + std::stringstream err{}; + err << "need array of shape (" << dim * dim << ", " + << cell.size() << ") but got (" << v.rows() << ", " + << v.cols() << ")."; + throw std::runtime_error(err.str()); + } + auto & strain{cell.get_strain()}; + strain.eigen() = v; + return cell.evaluate_stress(); + }, + "strain"_a, py::return_value_policy::reference_internal) .def("get_projection", &sys_t::get_projection) .def("get_subdomain_resolutions", &sys_t::get_subdomain_resolutions) .def("get_subdomain_locations", &sys_t::get_subdomain_locations) .def("get_domain_resolutions", &sys_t::get_domain_resolutions) - .def("get_domain_lengths", &sys_t::get_domain_resolutions); + .def("get_domain_lengths", &sys_t::get_domain_resolutions) + .def("set_uniform_strain", + [](sys_t & cell, py::EigenDRef & v) -> void { + cell.set_uniform_strain(v); + }, + "strain"_a) + .def("save_history_variables", &sys_t::save_history_variables); } void add_cell_base(py::module & mod) { - py::class_(mod, "Cell") + py::class_(mod, "Cell") .def("get_globalised_internal_real_array", - &Cell::get_globalised_internal_real_array, "unique_name"_a, + &muSpectre::Cell::get_globalised_internal_real_array, + "unique_name"_a, "Convenience function to copy local (internal) fields of " "materials into a global field. At least one of the materials in " "the cell needs to contain an internal field named " "`unique_name`. If multiple materials contain such a field, they " "all need to be of same scalar type and same number of " "components. This does not work for split pixel cells or " "laminate pixel cells, as they can have multiple entries for the " "same pixel. Pixels for which no field named `unique_name` " "exists get an array of zeros." "\n" "Parameters:\n" "unique_name: fieldname to fill the global field with. At " "least one material must have such a field, or an " - "Exception is raised."); - add_cell_base_helper(mod); - add_cell_base_helper(mod); + "Exception is raised.") + .def("get_globalised_current_real_array", + &muSpectre::Cell::get_globalised_current_real_array, "unique_name"_a) + .def("get_globalised_old_real_array", + &muSpectre::Cell::get_globalised_old_real_array, "unique_name"_a, + "nb_steps_ago"_a = 1) + .def("get_managed_real_array", &muSpectre::Cell::get_managed_real_array, + "unique_name"_a, "nb_components"_a, + "returns a field or nb_components real numbers per pixel"); + add_cell_base_helper(mod); + add_cell_base_helper(mod); } void add_cell(py::module & mod) { add_cell_factory(mod); auto cell{mod.def_submodule("cell")}; cell.doc() = "bindings for cells and cell factories"; add_cell_base(cell); } diff --git a/language_bindings/python/bind_py_common.cc b/language_bindings/python/bind_py_common.cc index ae414a0..b143661 100644 --- a/language_bindings/python/bind_py_common.cc +++ b/language_bindings/python/bind_py_common.cc @@ -1,156 +1,164 @@ /** * @file bind_py_common.cc * * @author Till Junge * * @date 08 Jan 2018 * * @brief Python bindings for the common part of µSpectre * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #include "common/common.hh" #include "common/ccoord_operations.hh" #include #include #include -using namespace muSpectre; // NOLINT // TODO(junge): figure this out +using muSpectre::Dim_t; +using muSpectre::Real; +using muSpectre::StressMeasure; +using muSpectre::StrainMeasure; +using muSpectre::Formulation; +using pybind11::literals::operator""_a; + namespace py = pybind11; -using namespace pybind11::literals; // NOLINT: recommended usage template void add_get_cube_helper(py::module & mod) { std::stringstream name{}; name << "get_" << dim << "d_cube"; - mod.def(name.str().c_str(), &CcoordOps::get_cube, "size"_a, + mod.def(name.str().c_str(), &muSpectre::CcoordOps::get_cube, "size"_a, "return a Ccoord with the value 'size' repeated in each dimension"); } template void add_get_hermitian_helper(py::module & mod) { - mod.def("get_hermitian_sizes", &CcoordOps::get_hermitian_sizes, - "full_sizes"_a, + mod.def("get_hermitian_sizes", + &muSpectre::CcoordOps::get_hermitian_sizes, "full_sizes"_a, "return the hermitian sizes corresponding to the true sizes"); } template void add_get_ccoord_helper(py::module & mod) { - using Ccoord = Ccoord_t; + using Ccoord = muSpectre::Ccoord_t; mod.def( "get_domain_ccoord", [](Ccoord resolutions, Dim_t index) { - return CcoordOps::get_ccoord(resolutions, Ccoord{}, index); + return muSpectre::CcoordOps::get_ccoord(resolutions, Ccoord{}, + index); }, "resolutions"_a, "i"_a, "return the cell coordinate corresponding to the i'th cell in a grid of " "shape resolutions"); } void add_get_cube(py::module & mod) { - add_get_cube_helper(mod); - add_get_cube_helper(mod); - add_get_cube_helper(mod); - add_get_cube_helper(mod); + add_get_cube_helper(mod); + add_get_cube_helper(mod); + add_get_cube_helper(mod); + add_get_cube_helper(mod); - add_get_hermitian_helper(mod); - add_get_hermitian_helper(mod); + add_get_hermitian_helper(mod); + add_get_hermitian_helper(mod); - add_get_ccoord_helper(mod); - add_get_ccoord_helper(mod); + add_get_ccoord_helper(mod); + add_get_ccoord_helper(mod); } template void add_get_index_helper(py::module & mod) { - using Ccoord = Ccoord_t; + using Ccoord = muSpectre::Ccoord_t; mod.def("get_domain_index", [](Ccoord sizes, Ccoord ccoord) { - return CcoordOps::get_index(sizes, Ccoord{}, ccoord); + return muSpectre::CcoordOps::get_index(sizes, Ccoord{}, + ccoord); }, "sizes"_a, "ccoord"_a, "return the linear index corresponding to grid point 'ccoord' in a " "grid of size 'sizes'"); } void add_get_index(py::module & mod) { - add_get_index_helper(mod); - add_get_index_helper(mod); + add_get_index_helper(mod); + add_get_index_helper(mod); } template void add_Pixels_helper(py::module & mod) { std::stringstream name{}; name << "Pixels" << dim << "d"; - using Ccoord = Ccoord_t; - py::class_> Pixels(mod, name.str().c_str()); + using Ccoord = muSpectre::Ccoord_t; + py::class_> Pixels(mod, name.str().c_str()); Pixels.def(py::init()); } void add_Pixels(py::module & mod) { - add_Pixels_helper(mod); - add_Pixels_helper(mod); + add_Pixels_helper(mod); + add_Pixels_helper(mod); } void add_common(py::module & mod) { py::enum_(mod, "Formulation") .value("finite_strain", Formulation::finite_strain) .value("small_strain", Formulation::small_strain); py::enum_(mod, "StressMeasure") .value("Cauchy", StressMeasure::Cauchy) .value("PK1", StressMeasure::PK1) .value("PK2", StressMeasure::PK2) .value("Kirchhoff", StressMeasure::Kirchhoff) .value("Biot", StressMeasure::Biot) .value("Mandel", StressMeasure::Mandel) .value("no_stress_", StressMeasure::no_stress_); py::enum_(mod, "StrainMeasure") .value("Gradient", StrainMeasure::Gradient) .value("Infinitesimal", StrainMeasure::Infinitesimal) .value("GreenLagrange", StrainMeasure::GreenLagrange) .value("Biot", StrainMeasure::Biot) .value("Log", StrainMeasure::Log) .value("Almansi", StrainMeasure::Almansi) .value("RCauchyGreen", StrainMeasure::RCauchyGreen) .value("LCauchyGreen", StrainMeasure::LCauchyGreen) .value("no_strain_", StrainMeasure::no_strain_); - py::enum_(mod, "FFT_PlanFlags") - .value("estimate", FFT_PlanFlags::estimate) - .value("measure", FFT_PlanFlags::measure) - .value("patient", FFT_PlanFlags::patient); + py::enum_(mod, "FFT_PlanFlags") + .value("estimate", muSpectre::FFT_PlanFlags::estimate) + .value("measure", muSpectre::FFT_PlanFlags::measure) + .value("patient", muSpectre::FFT_PlanFlags::patient); - mod.def("banner", &banner, "name"_a, "year"_a, "copyright_holder"_a); + mod.def("banner", &muSpectre::banner, "name"_a, "year"_a, + "copyright_holder"_a); add_get_cube(mod); add_Pixels(mod); add_get_index(mod); } diff --git a/language_bindings/python/bind_py_fftengine.cc b/language_bindings/python/bind_py_fftengine.cc index 00044e6..56b1217 100644 --- a/language_bindings/python/bind_py_fftengine.cc +++ b/language_bindings/python/bind_py_fftengine.cc @@ -1,118 +1,122 @@ /** * @file bind_py_fftengine.cc * * @author Till Junge * * @date 17 Jan 2018 * * @brief Python bindings for the FFT engines * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #include "fft/fftw_engine.hh" #ifdef WITH_FFTWMPI #include "fft/fftwmpi_engine.hh" #endif #ifdef WITH_PFFT #include "fft/pfft_engine.hh" #endif #include "bind_py_declarations.hh" #include #include #include -using namespace muSpectre; // NOLINT // TODO(junge): figure this out +using muSpectre::Ccoord_t; +using muSpectre::Complex; +using muSpectre::Dim_t; +using pybind11::literals::operator""_a; namespace py = pybind11; -using namespace pybind11::literals; // NOLINT: recommended usage template void add_engine_helper(py::module & mod, std::string name) { using Ccoord = Ccoord_t; using ArrayXXc = Eigen::Array; py::class_(mod, name.c_str()) #ifdef WITH_MPI .def(py::init([](Ccoord res, Dim_t nb_components, size_t comm) { return new Engine(res, nb_components, std::move(Communicator(MPI_Comm(comm)))); }), "resolutions"_a, "nb_components"_a, "communicator"_a = size_t(MPI_COMM_SELF)) #else .def(py::init()) #endif .def("fft", [](Engine & eng, py::EigenDRef v) { using Coll_t = typename Engine::GFieldCollection_t; using Field_t = typename Engine::Field_t; Coll_t coll{}; coll.initialise(eng.get_subdomain_resolutions(), eng.get_subdomain_locations()); - Field_t & temp{make_field("temp_field", coll, - eng.get_nb_components())}; + Field_t & temp{muSpectre::make_field( + "temp_field", coll, eng.get_nb_components())}; temp.eigen() = v; return ArrayXXc{eng.fft(temp).eigen()}; }, "array"_a) .def("ifft", [](Engine & eng, py::EigenDRef v) { using Coll_t = typename Engine::GFieldCollection_t; using Field_t = typename Engine::Field_t; Coll_t coll{}; coll.initialise(eng.get_subdomain_resolutions(), eng.get_subdomain_locations()); - Field_t & temp{make_field("temp_field", coll, - eng.get_nb_components())}; + Field_t & temp{muSpectre::make_field( + "temp_field", coll, eng.get_nb_components())}; eng.get_work_space().eigen() = v; eng.ifft(temp); return Eigen::ArrayXXd{temp.eigen()}; }, "array"_a) .def("initialise", &Engine::initialise, - "flags"_a = FFT_PlanFlags::estimate) + "flags"_a = muSpectre::FFT_PlanFlags::estimate) .def("normalisation", &Engine::normalisation) .def("get_subdomain_resolutions", &Engine::get_subdomain_resolutions) .def("get_subdomain_locations", &Engine::get_subdomain_locations) .def("get_fourier_resolutions", &Engine::get_fourier_resolutions) .def("get_fourier_locations", &Engine::get_fourier_locations) .def("get_domain_resolutions", &Engine::get_domain_resolutions); } void add_fft_engines(py::module & mod) { auto fft{mod.def_submodule("fft")}; fft.doc() = "bindings for µSpectre's fft engines"; - add_engine_helper, twoD>(fft, "FFTW_2d"); - add_engine_helper, threeD>(fft, "FFTW_3d"); + add_engine_helper, muSpectre::twoD>( + fft, "FFTW_2d"); + add_engine_helper, + muSpectre::threeD>(fft, "FFTW_3d"); #ifdef WITH_FFTWMPI add_engine_helper, twoD>(fft, "FFTWMPI_2d"); add_engine_helper, threeD>(fft, "FFTWMPI_3d"); #endif #ifdef WITH_PFFT add_engine_helper, twoD>(fft, "PFFT_2d"); add_engine_helper, threeD>(fft, "PFFT_3d"); #endif add_projections(fft); } diff --git a/language_bindings/python/bind_py_field_collection.cc b/language_bindings/python/bind_py_field_collection.cc index 6d0b219..7d0c04d 100644 --- a/language_bindings/python/bind_py_field_collection.cc +++ b/language_bindings/python/bind_py_field_collection.cc @@ -1,229 +1,276 @@ /** * file bind_py_field_collection.cc * * @author Till Junge * * @date 05 Jul 2018 * * @brief Python bindings for µSpectre field collections * * @section LICENSE * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #include "common/common.hh" #include "common/field.hh" #include "common/field_collection.hh" #include #include #include #include #include -using namespace muSpectre; // NOLINT // TODO(junge): figure this out +using muSpectre::Complex; +using muSpectre::Dim_t; +using muSpectre::Int; +using muSpectre::Real; +using muSpectre::Uint; +using pybind11::literals::operator""_a; namespace py = pybind11; -using namespace pybind11::literals; // NOLINT: recommended usage + template void add_field_collection(py::module & mod) { std::stringstream name_stream{}; name_stream << "_" << (FieldCollectionDerived::Global ? "Global" : "Local") << "FieldCollection_" << Dim << 'd'; const auto name{name_stream.str()}; - using FC_t = FieldCollectionBase; + using FC_t = muSpectre::FieldCollectionBase; py::class_(mod, name.c_str()) .def("get_real_field", [](FC_t & field_collection, std::string unique_name) -> typename FC_t::template TypedField_t & { return field_collection.template get_typed_field( unique_name); }, "unique_name"_a, py::return_value_policy::reference_internal) .def("get_int_field", [](FC_t & field_collection, std::string unique_name) -> typename FC_t::template TypedField_t & { return field_collection.template get_typed_field(unique_name); }, "unique_name"_a, py::return_value_policy::reference_internal) .def("get_uint_field", [](FC_t & field_collection, std::string unique_name) -> typename FC_t::template TypedField_t & { return field_collection.template get_typed_field( unique_name); }, "unique_name"_a, py::return_value_policy::reference_internal) .def("get_complex_field", [](FC_t & field_collection, std::string unique_name) -> typename FC_t::template TypedField_t & { return field_collection.template get_typed_field( unique_name); }, "unique_name"_a, py::return_value_policy::reference_internal) .def("get_real_statefield", [](FC_t & field_collection, std::string unique_name) -> typename FC_t::template TypedStateField_t & { return field_collection.template get_typed_statefield( unique_name); }, "unique_name"_a, py::return_value_policy::reference_internal) .def("get_int_statefield", [](FC_t & field_collection, std::string unique_name) -> typename FC_t::template TypedStateField_t & { return field_collection.template get_typed_statefield( unique_name); }, "unique_name"_a, py::return_value_policy::reference_internal) .def("get_uint_statefield", [](FC_t & field_collection, std::string unique_name) -> typename FC_t::template TypedStateField_t & { return field_collection.template get_typed_statefield( unique_name); }, "unique_name"_a, py::return_value_policy::reference_internal) .def("get_complex_statefield", [](FC_t & field_collection, std::string unique_name) -> typename FC_t::template TypedStateField_t & { return field_collection.template get_typed_statefield( unique_name); }, "unique_name"_a, py::return_value_policy::reference_internal) .def_property_readonly( "field_names", &FC_t::get_field_names, "returns the names of all fields in this collection") .def_property_readonly("statefield_names", &FC_t::get_statefield_names, "returns the names of all state fields in this " "collection"); } +namespace internal_fill { + + /** + * Needed for static switch when adding fillers to fields (static + * switch on whether they are global). Default case is for global + * fields. + */ + template + struct FillHelper { + static void add_fill(PyField & py_field) { + py_field.def( + "fill_from_local", + [](Field & field, const typename Field::LocalField_t & local) { + field.fill_from_local(local); + }, + "local"_a, + "Fills the content of a local field into a global field " + "(modifies only the pixels that are not present in the " + "local field"); + } + }; + + /** + * Specialisation for local fields + */ + template + struct FillHelper { + static void add_fill(PyField & py_field) { + py_field.def( + "fill_from_global", + [](Field & field, const typename Field::GlobalField_t & global) { + field.fill_from_global(global); + }, + "global"_a, + "Fills the content of a global field into a local field."); + } + }; + +} // namespace internal_fill template void add_field(py::module & mod, std::string dtype_name) { - using Field_t = TypedField; + using Field_t = muSpectre::TypedField; std::stringstream name_stream{}; name_stream << (FieldCollection::Global ? "Global" : "Local") << "Field" << dtype_name << "_" << FieldCollection::spatial_dim(); std::string name{name_stream.str()}; using Ref_t = py::EigenDRef>; - py::class_(mod, name.c_str()) + py::class_ py_field(mod, name.c_str()); + py_field .def_property("array", [](Field_t & field) { return field.eigen(); }, [](Field_t & field, Ref_t mat) { field.eigen() = mat; }, "array of stored data") .def_property_readonly( "array", [](const Field_t & field) { return field.eigen(); }, "array of stored data") .def_property("vector", [](Field_t & field) { return field.eigenvec(); }, [](Field_t & field, Ref_t mat) { field.eigen() = mat; }, "flattened array of stored data") .def_property_readonly( "vector", [](const Field_t & field) { return field.eigenvec(); }, "flattened array of stored data"); + using FillHelper_t = internal_fill::FillHelper; + FillHelper_t::add_fill(py_field); } template void add_field_helper(py::module & mod) { std::stringstream name_stream{}; name_stream << (FieldCollection::Global ? "Global" : "Local") << "Field" << "_" << Dim; std::string name{name_stream.str()}; - using Field_t = internal::FieldBase; + using Field_t = muSpectre::internal::FieldBase; py::class_(mod, name.c_str()) .def_property_readonly("name", &Field_t::get_name, "field name") .def_property_readonly("collection", &Field_t::get_collection, "Collection containing this field") .def_property_readonly("nb_components", &Field_t::get_nb_components, "number of scalars stored per pixel in this field") .def_property_readonly("stored_type", [](const Field_t & field) { return field.get_stored_typeid().name(); }, "fundamental type of scalars stored in this field") .def_property_readonly("size", &Field_t::size, "number of pixels in this field") .def("set_zero", &Field_t::set_zero, "Set all components in the field to zero"); add_field(mod, "Real"); add_field(mod, "Int"); } template void add_statefield(py::module & mod, std::string dtype_name) { - using StateField_t = TypedStateField; + using StateField_t = muSpectre::TypedStateField; std::stringstream name_stream{}; name_stream << (FieldCollection::Global ? "Global" : "Local") << "StateField" << dtype_name << "_" << FieldCollection::spatial_dim(); std::string name{name_stream.str()}; py::class_(mod, name.c_str()) - .def("get_current_field", &StateField_t::get_current_field, - "returns the current field value", - py::return_value_policy::reference_internal) + .def_property_readonly("current_field", &StateField_t::get_current_field, + "returns the current field value") .def("get_old_field", &StateField_t::get_old_field, "nb_steps_ago"_a = 1, "returns the value this field held 'nb_steps_ago' steps ago", py::return_value_policy::reference_internal); } template void add_statefield_helper(py::module & mod) { std::stringstream name_stream{}; name_stream << (FieldCollection::Global ? "Global" : "Local") << "StateField" << "_" << Dim; std::string name{name_stream.str()}; - using StateField_t = StateFieldBase; + using StateField_t = muSpectre::StateFieldBase; py::class_(mod, name.c_str()) .def_property_readonly("prefix", &StateField_t::get_prefix, "state field prefix") .def_property_readonly("collection", &StateField_t::get_collection, "Collection containing this field") .def_property_readonly("nb_memory", &StateField_t::get_nb_memory, "number of old states stored") .def_property_readonly( "stored_type", [](const StateField_t & field) { return field.get_stored_typeid().name(); }, "fundamental type of scalars stored in this field"); add_statefield(mod, "Real"); add_statefield(mod, "Int"); } template void add_field_collection_helper(py::module & mod) { - add_field_helper>(mod); - add_field_helper>(mod); + add_field_helper>(mod); + add_field_helper>(mod); - add_statefield_helper>(mod); - add_statefield_helper>(mod); + add_statefield_helper>(mod); + add_statefield_helper>(mod); - add_field_collection>(mod); - add_field_collection>(mod); + add_field_collection>(mod); + add_field_collection>(mod); } void add_field_collections(py::module & mod) { - add_field_collection_helper(mod); - add_field_collection_helper(mod); + add_field_collection_helper(mod); + add_field_collection_helper(mod); } diff --git a/language_bindings/python/bind_py_material.cc b/language_bindings/python/bind_py_material.cc index 767b7b9..5270398 100644 --- a/language_bindings/python/bind_py_material.cc +++ b/language_bindings/python/bind_py_material.cc @@ -1,192 +1,159 @@ /** * @file bind_py_material.cc * * @author Till Junge * * @date 09 Jan 2018 * * @brief python bindings for µSpectre's materials * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #include "common/common.hh" -#include "materials/material_linear_elastic1.hh" -#include "materials/material_linear_elastic2.hh" -#include "materials/material_linear_elastic3.hh" -#include "materials/material_linear_elastic4.hh" -#include "cell/cell_base.hh" -#include "common/field_collection.hh" - +#include "materials/material_base.hh" #include #include #include #include #include -using namespace muSpectre; // NOLINT // TODO(junge): figure this out +using muSpectre::Dim_t; +using pybind11::literals::operator""_a; namespace py = pybind11; -using namespace pybind11::literals; // NOLINT: recommended usage /* ---------------------------------------------------------------------- */ template void add_material_linear_elastic_generic1_helper(py::module & mod); /* ---------------------------------------------------------------------- */ template void add_material_linear_elastic_generic2_helper(py::module & mod); /** * python binding for the optionally objective form of Hooke's law */ template -void add_material_linear_elastic1_helper(py::module & mod) { - std::stringstream name_stream{}; - name_stream << "MaterialLinearElastic1_" << dim << 'd'; - const auto name{name_stream.str()}; - - using Mat_t = MaterialLinearElastic1; - using Sys_t = CellBase; - py::class_>(mod, name.c_str()) - .def_static("make", - [](Sys_t & sys, std::string n, Real e, Real p) -> Mat_t & { - return Mat_t::make(sys, n, e, p); - }, - "cell"_a, "name"_a, "Young"_a, "Poisson"_a, - py::return_value_policy::reference, py::keep_alive<1, 0>()) - .def("add_pixel", - [](Mat_t & mat, Ccoord_t pix) { mat.add_pixel(pix); }, - "pixel"_a) - .def("size", &Mat_t::size); -} - +void add_material_linear_elastic1_helper(py::module & mod); template -void add_material_linear_elastic2_helper(py::module & mod) { - std::stringstream name_stream{}; - name_stream << "MaterialLinearElastic2_" << dim << 'd'; - const auto name{name_stream.str()}; - - using Mat_t = MaterialLinearElastic2; - using Sys_t = CellBase; - - py::class_>(mod, name.c_str()) - .def_static("make", - [](Sys_t & sys, std::string n, Real e, Real p) -> Mat_t & { - return Mat_t::make(sys, n, e, p); - }, - "cell"_a, "name"_a, "Young"_a, "Poisson"_a, - py::return_value_policy::reference, py::keep_alive<1, 0>()) - .def("add_pixel", - [](Mat_t & mat, Ccoord_t pix, - py::EigenDRef & eig) { - Eigen::Matrix eig_strain{eig}; - mat.add_pixel(pix, eig_strain); - }, - "pixel"_a, "eigenstrain"_a) - .def("size", &Mat_t::size); -} - +void add_material_linear_elastic2_helper(py::module & mod); template -void add_material_linear_elastic3_helper(py::module & mod) { - std::stringstream name_stream{}; - name_stream << "MaterialLinearElastic3_" << dim << 'd'; - const auto name{name_stream.str()}; - - using Mat_t = MaterialLinearElastic3; - using Sys_t = CellBase; - - py::class_>(mod, name.c_str()) - .def(py::init(), "name"_a) - .def_static("make", - [](Sys_t & sys, std::string n) -> Mat_t & { - return Mat_t::make(sys, n); - }, - "cell"_a, "name"_a, py::return_value_policy::reference, - py::keep_alive<1, 0>()) - .def("add_pixel", - [](Mat_t & mat, Ccoord_t pix, Real Young, Real Poisson) { - mat.add_pixel(pix, Young, Poisson); - }, - "pixel"_a, "Young"_a, "Poisson"_a) - .def("size", &Mat_t::size); -} - +void add_material_linear_elastic3_helper(py::module & mod); template -void add_material_linear_elastic4_helper(py::module & mod) { - std::stringstream name_stream{}; - name_stream << "MaterialLinearElastic4_" << dim << 'd'; - const auto name{name_stream.str()}; - - using Mat_t = MaterialLinearElastic4; - using Sys_t = CellBase; +void add_material_linear_elastic4_helper(py::module & mod); +template +void add_material_hyper_elasto_plastic1_helper(py::module & mod); - py::class_>(mod, name.c_str()) - .def(py::init(), "name"_a) - .def_static("make", - [](Sys_t & sys, std::string n) -> Mat_t & { - return Mat_t::make(sys, n); - }, - "cell"_a, "name"_a, py::return_value_policy::reference, - py::keep_alive<1, 0>()) - .def("add_pixel", - [](Mat_t & mat, Ccoord_t pix, Real Young, Real Poisson) { - mat.add_pixel(pix, Young, Poisson); - }, - "pixel"_a, "Young"_a, "Poisson"_a) - .def("size", &Mat_t::size); -} +/* ---------------------------------------------------------------------- */ +template +class PyMaterialBase : public muSpectre::MaterialBase { + public: + /* Inherit the constructors */ + using Parent = muSpectre::MaterialBase; + using Parent::Parent; + + /* Trampoline (need one for each virtual function) */ + void save_history_variables() override { + PYBIND11_OVERLOAD_PURE(void, // Return type + Parent, // Parent class + save_history_variables); // Name of function in C++ + // (must match Python name) + } + + /* Trampoline (need one for each virtual function) */ + void initialise() override { + PYBIND11_OVERLOAD_PURE( + void, // Return type + Parent, // Parent class + initialise); // Name of function in C++ (must match Python name) + } + + void compute_stresses(const typename Parent::StrainField_t & F, + typename Parent::StressField_t & P, + muSpectre::Formulation form) override { + PYBIND11_OVERLOAD_PURE( + void, // Return type + Parent, // Parent class + compute_stresses, // Name of function in C++ (must match Python name) + F, P, form); + } + + void compute_stresses_tangent(const typename Parent::StrainField_t & F, + typename Parent::StressField_t & P, + typename Parent::TangentField_t & K, + muSpectre::Formulation form) override { + PYBIND11_OVERLOAD_PURE( + void, /* Return type */ + Parent, /* Parent class */ + compute_stresses, /* Name of function in C++ (must match Python name) */ + F, P, K, form); + } +}; template void add_material_helper(py::module & mod) { std::stringstream name_stream{}; - name_stream << "Material_" << dim << 'd'; - const std::string name{name_stream.str()}; - using Mat_t = MaterialBase; - using FC_t = LocalFieldCollection; - using FCBase_t = FieldCollectionBase; - - py::class_(mod, name.c_str()) + name_stream << "MaterialBase_" << dim << "d"; + std::string name{name_stream.str()}; + using Material = muSpectre::MaterialBase; + using MaterialTrampoline = PyMaterialBase; + using FC_t = muSpectre::LocalFieldCollection; + using FCBase_t = muSpectre::FieldCollectionBase; + + py::class_(mod, + name.c_str()) + .def(py::init()) + .def("save_history_variables", &Material::save_history_variables) + .def("list_fields", &Material::list_fields) + .def("get_real_field", &Material::get_real_field, "field_name"_a, + py::return_value_policy::reference_internal) + .def("size", &Material::size) + .def("add_pixel", + [](Material & mat, muSpectre::Ccoord_t pix) { + mat.add_pixel(pix); + }, + "pixel"_a) .def_property_readonly("collection", - [](Mat_t & material) -> FCBase_t & { + [](Material & material) -> FCBase_t & { return material.get_collection(); }, "returns the field collection containing internal " - "fields of this material", - py::return_value_policy::reference_internal); + "fields of this material"); add_material_linear_elastic1_helper(mod); add_material_linear_elastic2_helper(mod); add_material_linear_elastic3_helper(mod); add_material_linear_elastic4_helper(mod); + add_material_hyper_elasto_plastic1_helper(mod); add_material_linear_elastic_generic1_helper(mod); add_material_linear_elastic_generic2_helper(mod); } void add_material(py::module & mod) { auto material{mod.def_submodule("material")}; material.doc() = "bindings for constitutive laws"; - add_material_helper(material); - add_material_helper(material); + add_material_helper(material); + add_material_helper(material); } diff --git a/language_bindings/python/bind_py_material_hyper_elasto_plastic1.cc b/language_bindings/python/bind_py_material_hyper_elasto_plastic1.cc new file mode 100644 index 0000000..ae201fa --- /dev/null +++ b/language_bindings/python/bind_py_material_hyper_elasto_plastic1.cc @@ -0,0 +1,72 @@ +/** + * @file bind_py_material_hyper_elasto_plastic1.cc + * + * @author Till Junge + * + * @date 29 Oct 2018 + * + * @brief python binding for MaterialHyperElastoPlastic1 + * + * Copyright © 2018 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with µSpectre; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "common/common.hh" +#include "materials/stress_transformations_Kirchhoff.hh" +#include "materials/material_hyper_elasto_plastic1.hh" +#include "cell/cell_base.hh" + +#include +#include +#include + +#include +#include + +using muSpectre::Dim_t; +using muSpectre::Real; +using pybind11::literals::operator""_a; +namespace py = pybind11; + +/** + * python binding for the optionally objective form of Hooke's law + * with per-pixel elastic properties + */ +template +void add_material_hyper_elasto_plastic1_helper(py::module & mod) { + std::stringstream name_stream{}; + name_stream << "MaterialHyperElastoPlastic1_" << Dim << "d"; + const auto name{name_stream.str()}; + + using Mat_t = muSpectre::MaterialHyperElastoPlastic1; + using Cell_t = muSpectre::CellBase; + + py::class_>(mod, name.c_str()) + .def_static("make", + [](Cell_t & cell, std::string name, Real Young, Real Poisson, + Real tau_y0, Real h) -> Mat_t & { + return Mat_t::make(cell, name, Young, Poisson, tau_y0, h); + }, + "cell"_a, "name"_a, "YoungModulus"_a, "PoissonRatio"_a, + "τ_y₀"_a, "h"_a, py::return_value_policy::reference, + py::keep_alive<1, 0>()); +} + +template void +add_material_hyper_elasto_plastic1_helper(py::module &); +template void +add_material_hyper_elasto_plastic1_helper(py::module &); diff --git a/language_bindings/python/bind_py_material_linear_elastic1.cc b/language_bindings/python/bind_py_material_linear_elastic1.cc new file mode 100644 index 0000000..4f40993 --- /dev/null +++ b/language_bindings/python/bind_py_material_linear_elastic1.cc @@ -0,0 +1,68 @@ +/** + * @file bind_py_material_linear_elastic1.cc + * + * @author Till Junge + * + * @date 29 Oct 2018 + * + * @brief python bindings for MaterialLinearElastic1 + * + * Copyright © 2018 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with µSpectre; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "common/common.hh" +#include "materials/material_linear_elastic1.hh" +#include "cell/cell_base.hh" + +#include +#include +#include + +#include +#include + +using muSpectre::Dim_t; +using muSpectre::Real; +using pybind11::literals::operator""_a; +namespace py = pybind11; + + +/** + * python binding for the optionally objective form of Hooke's law + */ +template +void add_material_linear_elastic1_helper(py::module & mod) { + std::stringstream name_stream{}; + name_stream << "MaterialLinearElastic1_" << dim << 'd'; + const auto name{name_stream.str()}; + + using Mat_t = muSpectre::MaterialLinearElastic1; + using Sys_t = muSpectre::CellBase; + py::class_>(mod, name.c_str()) + .def_static("make", + [](Sys_t & sys, std::string n, Real e, Real p) -> Mat_t & { + return Mat_t::make(sys, n, e, p); + }, + "cell"_a, "name"_a, "Young"_a, "Poisson"_a, + py::return_value_policy::reference, py::keep_alive<1, 0>()); +} + +template void +add_material_linear_elastic1_helper(py::module &); +template void +add_material_linear_elastic1_helper(py::module &); diff --git a/language_bindings/python/bind_py_material_linear_elastic2.cc b/language_bindings/python/bind_py_material_linear_elastic2.cc new file mode 100644 index 0000000..9ac60ce --- /dev/null +++ b/language_bindings/python/bind_py_material_linear_elastic2.cc @@ -0,0 +1,77 @@ +/** + * @file bind_py_material_linear_elastic2.cc + * + * @author Till Junge + * + * @date 29 Oct 2018 + * + * @brief python bindings for MaterialLinearElastic2 + * + * Copyright © 2018 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with µSpectre; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "common/common.hh" +#include "materials/material_linear_elastic2.hh" +#include "cell/cell_base.hh" + +#include +#include +#include + +#include +#include + +using muSpectre::Dim_t; +using muSpectre::Real; + +namespace py = pybind11; +using pybind11::literals::operator""_a; + +/** + * python binding for the optionally objective form of Hooke's law + * with a per pixel eigenstrain + */ +template +void add_material_linear_elastic2_helper(py::module & mod) { + std::stringstream name_stream{}; + name_stream << "MaterialLinearElastic2_" << dim << 'd'; + const auto name{name_stream.str()}; + + using Mat_t = muSpectre::MaterialLinearElastic2; + using Sys_t = muSpectre::CellBase; + + py::class_>(mod, name.c_str()) + .def_static("make", + [](Sys_t & sys, std::string n, Real e, Real p) -> Mat_t & { + return Mat_t::make(sys, n, e, p); + }, + "cell"_a, "name"_a, "Young"_a, "Poisson"_a, + py::return_value_policy::reference, py::keep_alive<1, 0>()) + .def("add_pixel", + [](Mat_t & mat, muSpectre::Ccoord_t pix, + py::EigenDRef & eig) { + Eigen::Matrix eig_strain{eig}; + mat.add_pixel(pix, eig_strain); + }, + "pixel"_a, "eigenstrain"_a); +} + +template void +add_material_linear_elastic2_helper(py::module &); +template void +add_material_linear_elastic2_helper(py::module &); diff --git a/language_bindings/python/bind_py_material_linear_elastic3.cc b/language_bindings/python/bind_py_material_linear_elastic3.cc new file mode 100644 index 0000000..8778196 --- /dev/null +++ b/language_bindings/python/bind_py_material_linear_elastic3.cc @@ -0,0 +1,74 @@ +/** + * @file bind_py_material_linear_elastic3.cc + * + * @author Till Junge + * + * @date 29 Oct 2018 + * + * @brief python bindings for MaterialLinearElastic3 + * + * Copyright © 2018 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with µSpectre; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "common/common.hh" +#include "materials/material_linear_elastic3.hh" +#include "cell/cell_base.hh" + +#include +#include +#include + +#include +#include + +using muSpectre::Dim_t; +using muSpectre::Real; +using pybind11::literals::operator""_a; +namespace py = pybind11; + +/** + * python binding for the optionally objective form of Hooke's law + * with per-pixel elastic properties + */ +template +void add_material_linear_elastic3_helper(py::module & mod) { + std::stringstream name_stream{}; + name_stream << "MaterialLinearElastic3_" << dim << 'd'; + const auto name{name_stream.str()}; + + using Mat_t = muSpectre::MaterialLinearElastic3; + using Sys_t = muSpectre::CellBase; + + py::class_>(mod, name.c_str()) + .def(py::init(), "name"_a) + .def_static("make", + [](Sys_t & sys, std::string n) -> Mat_t & { + return Mat_t::make(sys, n); + }, + "cell"_a, "name"_a, py::return_value_policy::reference, + py::keep_alive<1, 0>()) + .def("add_pixel", + [](Mat_t & mat, muSpectre::Ccoord_t pix, Real Young, + Real Poisson) { mat.add_pixel(pix, Young, Poisson); }, + "pixel"_a, "Young"_a, "Poisson"_a); +} + +template void +add_material_linear_elastic3_helper(py::module &); +template void +add_material_linear_elastic3_helper(py::module &); diff --git a/language_bindings/python/bind_py_material_linear_elastic4.cc b/language_bindings/python/bind_py_material_linear_elastic4.cc new file mode 100644 index 0000000..fa3f782 --- /dev/null +++ b/language_bindings/python/bind_py_material_linear_elastic4.cc @@ -0,0 +1,74 @@ +/** + * @file bind_py_material_linear_elastic4.cc + * + * @author Till Junge + * + * @date 29 Oct 2018 + * + * @brief python binding for MaterialLinearElastic4 + * + * Copyright © 2018 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with µSpectre; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "common/common.hh" +#include "materials/material_linear_elastic4.hh" +#include "cell/cell_base.hh" + +#include +#include +#include + +#include +#include + +using muSpectre::Dim_t; +using muSpectre::Real; +using pybind11::literals::operator""_a; +namespace py = pybind11; + +/** + * python binding for the optionally objective form of Hooke's law + * with per-pixel elastic properties + */ +template +void add_material_linear_elastic4_helper(py::module & mod) { + std::stringstream name_stream{}; + name_stream << "MaterialLinearElastic4_" << dim << 'd'; + const auto name{name_stream.str()}; + + using Mat_t = muSpectre::MaterialLinearElastic4; + using Sys_t = muSpectre::CellBase; + + py::class_>(mod, name.c_str()) + .def(py::init(), "name"_a) + .def_static("make", + [](Sys_t & sys, std::string n) -> Mat_t & { + return Mat_t::make(sys, n); + }, + "cell"_a, "name"_a, py::return_value_policy::reference, + py::keep_alive<1, 0>()) + .def("add_pixel", + [](Mat_t & mat, muSpectre::Ccoord_t pix, Real Young, + Real Poisson) { mat.add_pixel(pix, Young, Poisson); }, + "pixel"_a, "Young"_a, "Poisson"_a); +} + +template void +add_material_linear_elastic4_helper(py::module &); +template void +add_material_linear_elastic4_helper(py::module &); diff --git a/language_bindings/python/bind_py_module.cc b/language_bindings/python/bind_py_module.cc index e0bf9e7..1530281 100644 --- a/language_bindings/python/bind_py_module.cc +++ b/language_bindings/python/bind_py_module.cc @@ -1,51 +1,48 @@ /** * @file bind_py_module.cc * * @author Till Junge * * @date 12 Jan 2018 * * @brief Python bindings for µSpectre * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #include "bind_py_declarations.hh" #include -using namespace pybind11::literals; // NOLINT: recommended use -namespace py = pybind11; - PYBIND11_MODULE(_muSpectre, mod) { mod.doc() = "Python bindings to the µSpectre library"; add_common(mod); add_cell(mod); add_material(mod); add_solvers(mod); add_fft_engines(mod); add_field_collections(mod); } diff --git a/language_bindings/python/bind_py_projections.cc b/language_bindings/python/bind_py_projections.cc index 36d0268..ec82f6b 100644 --- a/language_bindings/python/bind_py_projections.cc +++ b/language_bindings/python/bind_py_projections.cc @@ -1,183 +1,191 @@ /** * @file bind_py_projections.cc * * @author Till Junge * * @date 18 Jan 2018 * * @brief Python bindings for the Projection operators * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #include "fft/projection_small_strain.hh" #include "fft/projection_finite_strain.hh" #include "fft/projection_finite_strain_fast.hh" #include "fft/fftw_engine.hh" #ifdef WITH_FFTWMPI #include "fft/fftwmpi_engine.hh" #endif #ifdef WITH_PFFT #include "fft/pfft_engine.hh" #endif #include #include #include #include #include -using namespace muSpectre; // NOLINT // TODO(junge): figure this out +using muSpectre::Dim_t; +using muSpectre::ProjectionBase; +using pybind11::literals::operator""_a; namespace py = pybind11; -using namespace pybind11::literals; // NOLINT: recommended usage /** * "Trampoline" class for handling the pure virtual methods, see * [http://pybind11.readthedocs.io/en/stable/advanced/classes.html#overriding-virtual-functions-in-python] * for details */ template class PyProjectionBase : public ProjectionBase { public: //! base class using Parent = ProjectionBase; //! field type on which projection is applied using Field_t = typename Parent::Field_t; void apply_projection(Field_t & field) override { PYBIND11_OVERLOAD_PURE(void, Parent, apply_projection, field); } Eigen::Map get_operator() override { PYBIND11_OVERLOAD_PURE(Eigen::Map, Parent, get_operator); } }; template void add_proj_helper(py::module & mod, std::string name_start) { - using Ccoord = Ccoord_t; - using Rcoord = Rcoord_t; + using Ccoord = muSpectre::Ccoord_t; + using Rcoord = muSpectre::Rcoord_t; using Field_t = typename Proj::Field_t; static_assert(DimS == DimM, "currently only for DimS==DimM"); std::stringstream name{}; name << name_start << '_' << DimS << 'd'; py::class_(mod, name.str().c_str()) #ifdef WITH_MPI .def(py::init([](Ccoord res, Rcoord lengths, const std::string & fft, size_t comm) { if (fft == "fftw") { auto engine = std::make_unique>( res, Proj::NbComponents(), std::move(Communicator(MPI_Comm(comm)))); return Proj(std::move(engine), lengths); #else .def(py::init([](Ccoord res, Rcoord lengths, const std::string & fft) { if (fft == "fftw") { - auto engine = std::make_unique>( + auto engine = std::make_unique>( res, Proj::NbComponents()); return Proj(std::move(engine), lengths); #endif #ifdef WITH_FFTWMPI } else if (fft == "fftwmpi") { auto engine = std::make_unique>( res, Proj::NbComponents(), std::move(Communicator(MPI_Comm(comm)))); return Proj(std::move(engine), lengths); #endif #ifdef WITH_PFFT } else if (fft == "pfft") { auto engine = std::make_unique>( res, Proj::NbComponents(), std::move(Communicator(MPI_Comm(comm)))); return Proj(std::move(engine), lengths); #endif } else { throw std::runtime_error("Unknown FFT engine '" + fft + "' specified."); } }), "resolutions"_a, "lengths"_a, #ifdef WITH_MPI "fft"_a = "fftw", "communicator"_a = size_t(MPI_COMM_SELF)) #else "fft"_a = "fftw") #endif - .def("initialise", &Proj::initialise, "flags"_a = FFT_PlanFlags::estimate, + .def("initialise", &Proj::initialise, + "flags"_a = muSpectre::FFT_PlanFlags::estimate, "initialises the fft engine (plan the transform)") .def("apply_projection", [](Proj & proj, py::EigenDRef v) { - typename FFTEngineBase::GFieldCollection_t coll{}; - Eigen::Index subdomain_size = - CcoordOps::get_size(proj.get_subdomain_resolutions()); + typename muSpectre::FFTEngineBase::GFieldCollection_t coll{}; + Eigen::Index subdomain_size = muSpectre::CcoordOps::get_size( + proj.get_subdomain_resolutions()); if (v.rows() != DimS * DimM || v.cols() != subdomain_size) { throw std::runtime_error("Expected input array of shape (" + std::to_string(DimS * DimM) + ", " + std::to_string(subdomain_size) + "), but input array has shape (" + std::to_string(v.rows()) + ", " + std::to_string(v.cols()) + ")."); } coll.initialise(proj.get_subdomain_resolutions(), proj.get_subdomain_locations()); - Field_t & temp{make_field("temp_field", coll, - proj.get_nb_components())}; + Field_t & temp{muSpectre::make_field( + "temp_field", coll, proj.get_nb_components())}; temp.eigen() = v; proj.apply_projection(temp); return Eigen::ArrayXXd{temp.eigen()}; }) .def("get_operator", &Proj::get_operator) .def( "get_formulation", &Proj::get_formulation, "return a Formulation enum indicating whether the projection is small" " or finite strain") .def("get_subdomain_resolutions", &Proj::get_subdomain_resolutions) .def("get_subdomain_locations", &Proj::get_subdomain_locations) .def("get_domain_resolutions", &Proj::get_domain_resolutions) .def("get_domain_lengths", &Proj::get_domain_resolutions); } void add_proj_dispatcher(py::module & mod) { - add_proj_helper, twoD>( - mod, "ProjectionSmallStrain"); - add_proj_helper, threeD>( - mod, "ProjectionSmallStrain"); - - add_proj_helper, twoD>( - mod, "ProjectionFiniteStrain"); - add_proj_helper, threeD>( - mod, "ProjectionFiniteStrain"); - - add_proj_helper, twoD>( - mod, "ProjectionFiniteStrainFast"); - add_proj_helper, threeD>( - mod, "ProjectionFiniteStrainFast"); + add_proj_helper< + muSpectre::ProjectionSmallStrain, + muSpectre::twoD>(mod, "ProjectionSmallStrain"); + add_proj_helper< + muSpectre::ProjectionSmallStrain, + muSpectre::threeD>(mod, "ProjectionSmallStrain"); + + add_proj_helper< + muSpectre::ProjectionFiniteStrain, + muSpectre::twoD>(mod, "ProjectionFiniteStrain"); + add_proj_helper< + muSpectre::ProjectionFiniteStrain, + muSpectre::threeD>(mod, "ProjectionFiniteStrain"); + + add_proj_helper< + muSpectre::ProjectionFiniteStrainFast, + muSpectre::twoD>(mod, "ProjectionFiniteStrainFast"); + add_proj_helper, + muSpectre::threeD>(mod, "ProjectionFiniteStrainFast"); } void add_projections(py::module & mod) { add_proj_dispatcher(mod); } diff --git a/language_bindings/python/bind_py_solvers.cc b/language_bindings/python/bind_py_solvers.cc index 2dddd31..9a30c54 100644 --- a/language_bindings/python/bind_py_solvers.cc +++ b/language_bindings/python/bind_py_solvers.cc @@ -1,139 +1,146 @@ /** * @file bind_py_solver.cc * * @author Till Junge * * @date 09 Jan 2018 * * @brief python bindings for the muSpectre solvers * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #include "common/common.hh" #include "solver/solvers.hh" #include "solver/solver_cg.hh" #include "solver/solver_eigen.hh" #include #include #include -using namespace muSpectre; // NOLINT // TODO(junge): figure this out +using muSpectre::Dim_t; +using muSpectre::OptimizeResult; +using muSpectre::Real; +using muSpectre::Uint; +using pybind11::literals::operator""_a; namespace py = pybind11; -using namespace pybind11::literals; // NOLINT: recommended usage /** * Solvers instanciated for cells with equal spatial and material dimension */ template void add_iterative_solver_helper(py::module & mod, std::string name) { py::class_(mod, name.c_str()) - .def(py::init(), "cell"_a, "tol"_a, "maxiter"_a, - "verbose"_a = false) + .def(py::init(), "cell"_a, "tol"_a, + "maxiter"_a, "verbose"_a = false) .def("name", &Solver::get_name); } void add_iterative_solver(py::module & mod) { std::stringstream name{}; name << "SolverBase"; - py::class_(mod, name.str().c_str()); - add_iterative_solver_helper(mod, "SolverCG"); - add_iterative_solver_helper(mod, "SolverCGEigen"); - add_iterative_solver_helper(mod, "SolverGMRESEigen"); - add_iterative_solver_helper(mod, "SolverBiCGSTABEigen"); - add_iterative_solver_helper(mod, "SolverDGMRESEigen"); - add_iterative_solver_helper(mod, "SolverMINRESEigen"); + py::class_(mod, name.str().c_str()); + add_iterative_solver_helper(mod, "SolverCG"); + add_iterative_solver_helper(mod, "SolverCGEigen"); + add_iterative_solver_helper(mod, + "SolverGMRESEigen"); + add_iterative_solver_helper( + mod, "SolverBiCGSTABEigen"); + add_iterative_solver_helper( + mod, "SolverDGMRESEigen"); + add_iterative_solver_helper( + mod, "SolverMINRESEigen"); } void add_newton_cg_helper(py::module & mod) { const char name[]{"newton_cg"}; - using solver = SolverBase; + using solver = muSpectre::SolverBase; using grad = py::EigenDRef; - using grad_vec = LoadSteps_t; + using grad_vec = muSpectre::LoadSteps_t; mod.def(name, - [](Cell & s, const grad & g, solver & so, Real nt, Real eqt, - Dim_t verb) -> OptimizeResult { + [](muSpectre::Cell & s, const grad & g, solver & so, Real nt, + Real eqt, Dim_t verb) -> OptimizeResult { Eigen::MatrixXd tmp{g}; return newton_cg(s, tmp, so, nt, eqt, verb); }, "cell"_a, "ΔF₀"_a, "solver"_a, "newton_tol"_a, "equil_tol"_a, "verbose"_a = 0); mod.def(name, - [](Cell & s, const grad_vec & g, solver & so, Real nt, Real eqt, - Dim_t verb) -> std::vector { + [](muSpectre::Cell & s, const grad_vec & g, solver & so, Real nt, + Real eqt, Dim_t verb) -> std::vector { return newton_cg(s, g, so, nt, eqt, verb); }, "cell"_a, "ΔF₀"_a, "solver"_a, "newton_tol"_a, "equilibrium_tol"_a, "verbose"_a = 0); } void add_de_geus_helper(py::module & mod) { const char name[]{"de_geus"}; - using solver = SolverBase; + using solver = muSpectre::SolverBase; using grad = py::EigenDRef; - using grad_vec = LoadSteps_t; + using grad_vec = muSpectre::LoadSteps_t; mod.def(name, - [](Cell & s, const grad & g, solver & so, Real nt, Real eqt, - Dim_t verb) -> OptimizeResult { + [](muSpectre::Cell & s, const grad & g, solver & so, Real nt, + Real eqt, Dim_t verb) -> OptimizeResult { Eigen::MatrixXd tmp{g}; return de_geus(s, tmp, so, nt, eqt, verb); }, "cell"_a, "ΔF₀"_a, "solver"_a, "newton_tol"_a, "equilibrium_tol"_a, "verbose"_a = 0); mod.def(name, - [](Cell & s, const grad_vec & g, solver & so, Real nt, Real eqt, - Dim_t verb) -> std::vector { + [](muSpectre::Cell & s, const grad_vec & g, solver & so, Real nt, + Real eqt, Dim_t verb) -> std::vector { return de_geus(s, g, so, nt, eqt, verb); }, "cell"_a, "ΔF₀"_a, "solver"_a, "newton_tol"_a, "equilibrium_tol"_a, "verbose"_a = 0); } void add_solver_helper(py::module & mod) { add_newton_cg_helper(mod); add_de_geus_helper(mod); } void add_solvers(py::module & mod) { auto solvers{mod.def_submodule("solvers")}; solvers.doc() = "bindings for solvers"; py::class_(mod, "OptimizeResult") .def_readwrite("grad", &OptimizeResult::grad) .def_readwrite("stress", &OptimizeResult::stress) .def_readwrite("success", &OptimizeResult::success) .def_readwrite("status", &OptimizeResult::status) .def_readwrite("message", &OptimizeResult::message) .def_readwrite("nb_it", &OptimizeResult::nb_it) .def_readwrite("nb_fev", &OptimizeResult::nb_fev); add_iterative_solver(solvers); add_solver_helper(solvers); } diff --git a/language_bindings/python/muSpectre/__init__.py b/language_bindings/python/muSpectre/__init__.py index cf0c580..7c0da4d 100644 --- a/language_bindings/python/muSpectre/__init__.py +++ b/language_bindings/python/muSpectre/__init__.py @@ -1,103 +1,104 @@ #!/usr/bin/env python3 # -*- coding:utf-8 -*- """ @file __init__.py @author Lars Pastewka @date 21 Mar 2018 @brief Main entry point for muSpectre Python module Copyright © 2018 Till Junge µSpectre is free software; you can redistribute it and/or modify it under the terms of the GNU General Lesser Public License as published by the Free Software Foundation, either version 3, or (at your option) any later version. µSpectre 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 General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with µSpectre; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Additional permission under GNU GPL version 3 section 7 If you modify this Program, or any covered work, by linking or combining it with proprietary FFT implementations or numerical libraries, containing parts covered by the terms of those libraries' licenses, the licensors of this Program grant you additional permission to convey the resulting work. """ try: from mpi4py import MPI except ImportError: MPI = None import _muSpectre from _muSpectre import (Formulation, get_domain_ccoord, get_domain_index, - get_hermitian_sizes, material, solvers) + get_hermitian_sizes, material, solvers, + FFT_PlanFlags) import muSpectre.fft _factories = {'fftw': ('CellFactory', False), 'fftwmpi': ('FFTWMPICellFactory', True), 'pfft': ('PFFTCellFactory', True), 'p3dfft': ('P3DFFTCellFactory', True)} def Cell(resolutions, lengths, formulation=Formulation.finite_strain, fft='fftw', communicator=None): """ Instantiate a muSpectre Cell class. Parameters ---------- resolutions: list Grid resolutions in the Cartesian directions. lengths: list Physical size of the cell in the Cartesian directions. formulation: Formulation Formulation for strains and stresses used by the solver. Options are `Formulation.finite_strain` and `Formulation.small_strain`. Finite strain formulation is the default. fft: string FFT engine to use. Options are 'fftw', 'fftwmpi', 'pfft' and 'p3dfft'. Default is 'fftw'. communicator: mpi4py communicator mpi4py communicator object passed to parallel FFT engines. Note that the default 'fftw' engine does not support parallel execution. Returns ------- cell: object Return a muSpectre Cell object. """ try: factory_name, is_parallel = _factories[fft] except KeyError: raise KeyError("Unknown FFT engine '{}'.".format(fft)) try: factory = _muSpectre.__dict__[factory_name] except KeyError: raise KeyError("FFT engine '{}' has not been compiled into the " "muSpectre library.".format(fft)) if is_parallel: if MPI is None: raise RuntimeError('Parallel solver requested but mpi4py could' ' not be imported.') if communicator is None: communicator = MPI.COMM_SELF return factory(resolutions, lengths, formulation, MPI._handleof(communicator)) else: if communicator is not None: raise ValueError("FFT engine '{}' does not support parallel " "execution.".format(fft)) return factory(resolutions, lengths, formulation) diff --git a/src/cell/cell_base.cc b/src/cell/cell_base.cc index 1474f58..2ee5a5c 100644 --- a/src/cell/cell_base.cc +++ b/src/cell/cell_base.cc @@ -1,513 +1,608 @@ /** * @file cell_base.cc * * @author Till Junge * * @date 01 Nov 2017 * * @brief Implementation for cell base class * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #include "cell/cell_base.hh" #include "common/ccoord_operations.hh" #include "common/iterators.hh" #include "common/tensor_algebra.hh" #include #include #include #include namespace muSpectre { /* ---------------------------------------------------------------------- */ template CellBase::CellBase(Projection_ptr projection_) : subdomain_resolutions{projection_->get_subdomain_resolutions()}, subdomain_locations{projection_->get_subdomain_locations()}, domain_resolutions{projection_->get_domain_resolutions()}, pixels(subdomain_resolutions, subdomain_locations), domain_lengths{projection_->get_domain_lengths()}, fields{std::make_unique()}, F{make_field("Gradient", *this->fields)}, P{make_field("Piola-Kirchhoff-1", *this->fields)}, projection{std::move(projection_)} { // resize all global fields (strain, stress, etc) this->fields->initialise(this->subdomain_resolutions, this->subdomain_locations); } /** * turns out that the default move container in combination with * clang segfaults under certain (unclear) cicumstances, because the * move constructor of the optional appears to be busted in gcc * 7.2. Copying it (K) instead of moving it fixes the issue, and * since it is a reference, the cost is practically nil */ template CellBase::CellBase(CellBase && other) : subdomain_resolutions{std::move(other.subdomain_resolutions)}, subdomain_locations{std::move(other.subdomain_locations)}, domain_resolutions{std::move(other.domain_resolutions)}, pixels{std::move(other.pixels)}, domain_lengths{std::move( other.domain_lengths)}, fields{std::move(other.fields)}, F{other.F}, P{other.P}, K{other.K}, // this seems to segfault under clang if it's not a move materials{std::move(other.materials)}, projection{std::move( other.projection)} {} /* ---------------------------------------------------------------------- */ template typename CellBase::Material_t & CellBase::add_material(Material_ptr mat) { this->materials.push_back(std::move(mat)); return *this->materials.back(); } /* ---------------------------------------------------------------------- */ template auto CellBase::get_strain_vector() -> Vector_ref { return this->get_strain().eigenvec(); } /* ---------------------------------------------------------------------- */ template auto CellBase::get_stress_vector() const -> ConstVector_ref { return this->get_stress().eigenvec(); } /* ---------------------------------------------------------------------- */ template void CellBase::set_uniform_strain( const Eigen::Ref & strain) { + if (not this->initialised) { + this->initialise(); + } this->F.get_map() = strain; } /* ---------------------------------------------------------------------- */ template auto CellBase::evaluate_stress() -> ConstVector_ref { if (not this->initialised) { this->initialise(); } for (auto & mat : this->materials) { mat->compute_stresses(this->F, this->P, this->get_formulation()); } return this->P.const_eigenvec(); } /* ---------------------------------------------------------------------- */ template auto CellBase::evaluate_stress_tangent() -> std::array { if (not this->initialised) { this->initialise(); } constexpr bool create_tangent{true}; this->get_tangent(create_tangent); for (auto & mat : this->materials) { mat->compute_stresses_tangent(this->F, this->P, this->K.value(), this->get_formulation()); } const TangentField_t & k = this->K.value(); return std::array{this->P.const_eigenvec(), k.const_eigenvec()}; } /* ---------------------------------------------------------------------- */ template auto CellBase::evaluate_projected_directional_stiffness( Eigen::Ref delF) -> Vector_ref { // the following const_cast should be safe, as long as the // constructed delF_field is const itself const TypedField delF_field( "Proxied raw memory for strain increment", *this->fields, Eigen::Map(const_cast(delF.data()), delF.size()), this->F.get_nb_components()); if (!this->K) { throw std::runtime_error( "currently only implemented for cases where a stiffness matrix " "exists"); } if (delF.size() != this->get_nb_dof()) { std::stringstream err{}; err << "input should be of size ndof = ¶(" << this->subdomain_resolutions << ") × " << DimS << "² = " << this->get_nb_dof() << " but I got " << delF.size(); throw std::runtime_error(err.str()); } const std::string out_name{"δP; temp output for directional stiffness"}; auto & delP = this->get_managed_T2_field(out_name); auto Kmap{this->K.value().get().get_map()}; auto delPmap{delP.get_map()}; MatrixFieldMap delFmap( delF_field); for (auto && tup : akantu::zip(Kmap, delFmap, delPmap)) { auto & k = std::get<0>(tup); auto & df = std::get<1>(tup); auto & dp = std::get<2>(tup); dp = Matrices::tensmult(k, df); } return Vector_ref(this->project(delP).data(), this->get_nb_dof()); } /* ---------------------------------------------------------------------- */ template std::array CellBase::get_strain_shape() const { return this->projection->get_strain_shape(); } + /* ---------------------------------------------------------------------- */ + template + auto CellBase::evaluate_projection(Eigen::Ref P) + -> Vector_ref { + if (P.size() != this->get_nb_dof()) { + std::stringstream err{}; + err << "input should be of size ndof = ¶(" << this->subdomain_resolutions + << ") × " << DimS << "² = " << this->get_nb_dof() << " but I got " + << P.size(); + throw std::runtime_error(err.str()); + } + + const std::string out_name{"RHS; temp output for projection"}; + auto & rhs = this->get_managed_T2_field(out_name); + rhs.eigen() = P; + + return Vector_ref(this->project(rhs).data(), this->get_nb_dof()); + } + /* ---------------------------------------------------------------------- */ template void CellBase::apply_projection(Eigen::Ref vec) { TypedField field("Proxy for projection", *this->fields, vec, this->F.get_nb_components()); this->projection->apply_projection(field); } /* ---------------------------------------------------------------------- */ template typename CellBase::FullResponse_t CellBase::evaluate_stress_tangent(StrainField_t & grad) { if (this->initialised == false) { this->initialise(); } //! High level compatibility checks if (grad.size() != this->F.size()) { throw std::runtime_error("Size mismatch"); } constexpr bool create_tangent{true}; this->get_tangent(create_tangent); for (auto & mat : this->materials) { mat->compute_stresses_tangent(grad, this->P, this->K.value(), this->get_formulation()); } return std::tie(this->P, this->K.value()); } /* ---------------------------------------------------------------------- */ template typename CellBase::StressField_t & CellBase::directional_stiffness(const TangentField_t & K, const StrainField_t & delF, StressField_t & delP) { for (auto && tup : akantu::zip(K.get_map(), delF.get_map(), delP.get_map())) { auto & k = std::get<0>(tup); auto & df = std::get<1>(tup); auto & dp = std::get<2>(tup); dp = Matrices::tensmult(k, df); } return this->project(delP); } /* ---------------------------------------------------------------------- */ template typename CellBase::Vector_ref CellBase::directional_stiffness_vec( const Eigen::Ref & delF) { if (!this->K) { throw std::runtime_error( "currently only implemented for cases where a stiffness matrix " "exists"); } if (delF.size() != this->get_nb_dof()) { std::stringstream err{}; err << "input should be of size ndof = ¶(" << this->subdomain_resolutions << ") × " << DimS << "² = " << this->get_nb_dof() << " but I got " << delF.size(); throw std::runtime_error(err.str()); } const std::string out_name{"temp output for directional stiffness"}; const std::string in_name{"temp input for directional stiffness"}; auto & out_tempref = this->get_managed_T2_field(out_name); auto & in_tempref = this->get_managed_T2_field(in_name); Vector_ref(in_tempref.data(), this->get_nb_dof()) = delF; this->directional_stiffness(this->K.value(), in_tempref, out_tempref); return Vector_ref(out_tempref.data(), this->get_nb_dof()); } /* ---------------------------------------------------------------------- */ template Eigen::ArrayXXd CellBase::directional_stiffness_with_copy( Eigen::Ref delF) { if (!this->K) { throw std::runtime_error( "currently only implemented for cases where a stiffness matrix " "exists"); } const std::string out_name{"temp output for directional stiffness"}; const std::string in_name{"temp input for directional stiffness"}; auto & out_tempref = this->get_managed_T2_field(out_name); auto & in_tempref = this->get_managed_T2_field(in_name); in_tempref.eigen() = delF; this->directional_stiffness(this->K.value(), in_tempref, out_tempref); return out_tempref.eigen(); } /* ---------------------------------------------------------------------- */ template typename CellBase::StressField_t & CellBase::project(StressField_t & field) { this->projection->apply_projection(field); return field; } /* ---------------------------------------------------------------------- */ template typename CellBase::StrainField_t & CellBase::get_strain() { if (this->initialised == false) { this->initialise(); } return this->F; } /* ---------------------------------------------------------------------- */ template const typename CellBase::StressField_t & CellBase::get_stress() const { return this->P; } /* ---------------------------------------------------------------------- */ template const typename CellBase::TangentField_t & CellBase::get_tangent(bool create) { if (!this->K) { if (create) { this->K = make_field("Tangent Stiffness", *this->fields); } else { throw std::runtime_error("K does not exist"); } } return this->K.value(); } /* ---------------------------------------------------------------------- */ template typename CellBase::StrainField_t & CellBase::get_managed_T2_field(std::string unique_name) { if (!this->fields->check_field_exists(unique_name)) { return make_field(unique_name, *this->fields); } else { return static_cast(this->fields->at(unique_name)); } } /* ---------------------------------------------------------------------- */ template auto CellBase::get_managed_real_field(std::string unique_name, size_t nb_components) -> Field_t & { if (!this->fields->check_field_exists(unique_name)) { return make_field>(unique_name, *this->fields, nb_components); } else { auto & ret_ref{Field_t::check_ref(this->fields->at(unique_name))}; if (ret_ref.get_nb_components() != nb_components) { std::stringstream err{}; err << "Field '" << unique_name << "' already exists and it has " << ret_ref.get_nb_components() << " components. You asked for a field " << "with " << nb_components << "components."; throw std::runtime_error(err.str()); } return ret_ref; } } /* ---------------------------------------------------------------------- */ template - auto CellBase::get_globalised_internal_real_field( - const std::string & unique_name) -> Field_t & { - using LField_t = typename Field_t::LocalField_t; + template + auto CellBase::globalised_field_helper( + const std::string & unique_name, int nb_steps_ago) -> Field_t & { + using LField_t = const typename Field_t::LocalField_t; // start by checking that the field exists at least once, and that // it always has th same number of components std::set nb_component_categories{}; std::vector> local_fields; + auto check{[&unique_name](auto & coll) -> bool { + if (IsStateField) { + return coll.check_statefield_exists(unique_name); + } else { + return coll.check_field_exists(unique_name); + } + }}; + auto get = [&unique_name, &nb_steps_ago ]( + auto & coll) -> const typename LField_t::Base & { + if (IsStateField) { + using Coll_t = typename Material_t::MFieldCollection_t; + using TypedStateField_t = TypedStateField; + auto & statefield{coll.get_statefield(unique_name)}; + auto & typed_statefield{TypedStateField_t::check_ref(statefield)}; + const typename LField_t::Base & f1{ + typed_statefield.get_old_field(nb_steps_ago)}; + const typename LField_t::Base & f2{ + typed_statefield.get_current_field()}; + return (nb_steps_ago ? f1 : f2); + } else { + return coll[unique_name]; + } + }; + for (auto & mat : this->materials) { auto & coll = mat->get_collection(); - if (coll.check_field_exists(unique_name)) { - auto & field{LField_t::check_ref(coll[unique_name])}; + if (check(coll)) { + const auto & field{LField_t::check_ref(get(coll))}; local_fields.emplace_back(field); nb_component_categories.insert(field.get_nb_components()); } } if (nb_component_categories.size() != 1) { const auto & nb_match{nb_component_categories.size()}; std::stringstream err_str{}; if (nb_match > 1) { - err_str << "The fields named '" << unique_name << "' do not have the " - << "same number of components in every material, which is a " - << "requirement for globalising them! The following values " - << "were found by material:" << std::endl; + err_str + << "The fields named '" << unique_name << "' do not have the " + << "same number of components in every material, which is a " + << "requirement for globalising them! The following values were " + << "found by material:" << std::endl; for (auto & mat : this->materials) { auto & coll = mat->get_collection(); if (coll.check_field_exists(unique_name)) { auto & field{LField_t::check_ref(coll[unique_name])}; err_str << field.get_nb_components() << " components in material '" << mat->get_name() << "'" << std::endl; } } } else { - err_str << "The field named '" << unique_name << "' does not exist in " + err_str << "The " << (IsStateField ? "state" : "") << "field named '" + << unique_name << "' does not exist in " << "any of the materials and can therefore not be globalised!"; } throw std::runtime_error(err_str.str()); } const Dim_t nb_components{*nb_component_categories.begin()}; // get and prepare the field auto & field{this->get_managed_real_field(unique_name, nb_components)}; field.set_zero(); // fill it with local internal values for (auto & local_field : local_fields) { field.fill_from_local(local_field); } return field; } + /* ---------------------------------------------------------------------- */ + template + auto CellBase::get_globalised_internal_real_field( + const std::string & unique_name) -> Field_t & { + constexpr bool IsStateField{false}; + return this->template globalised_field_helper( + unique_name, -1); // the -1 is a moot argument + } + + /* ---------------------------------------------------------------------- */ + template + auto CellBase::get_globalised_current_real_field( + const std::string & unique_name) -> Field_t & { + constexpr bool IsStateField{true}; + return this->template globalised_field_helper( + unique_name, 0); + } + + /* ---------------------------------------------------------------------- */ + template + auto CellBase::get_globalised_old_real_field( + const std::string & unique_name, int nb_steps_ago) -> Field_t & { + constexpr bool IsStateField{true}; + return this->template globalised_field_helper( + unique_name, nb_steps_ago); + } + /* ---------------------------------------------------------------------- */ template auto CellBase::get_managed_real_array(std::string unique_name, size_t nb_components) -> Array_ref { auto & field{this->get_managed_real_field(unique_name, nb_components)}; return Array_ref{field.data(), Dim_t(nb_components), Dim_t(field.size())}; } /* ---------------------------------------------------------------------- */ template auto CellBase::get_globalised_internal_real_array( const std::string & unique_name) -> Array_ref { auto & field{this->get_globalised_internal_real_field(unique_name)}; return Array_ref{field.data(), Dim_t(field.get_nb_components()), Dim_t(field.size())}; } + /* ---------------------------------------------------------------------- */ + template + auto CellBase::get_globalised_current_real_array( + const std::string & unique_prefix) -> Array_ref { + auto & field{this->get_globalised_current_real_field(unique_prefix)}; + return Array_ref{field.data(), Dim_t(field.get_nb_components()), + Dim_t(field.size())}; + } + + /* ---------------------------------------------------------------------- */ + template + auto CellBase::get_globalised_old_real_array( + const std::string & unique_prefix, int nb_steps_ago) -> Array_ref { + auto & field{ + this->get_globalised_old_real_field(unique_prefix, nb_steps_ago)}; + return Array_ref{field.data(), Dim_t(field.get_nb_components()), + Dim_t(field.size())}; + } + /* ---------------------------------------------------------------------- */ template void CellBase::initialise(FFT_PlanFlags flags) { // check that all pixels have been assigned exactly one material this->check_material_coverage(); for (auto && mat : this->materials) { mat->initialise(); } // initialise the projection and compute the fft plan this->projection->initialise(flags); this->initialised = true; } /* ---------------------------------------------------------------------- */ template void CellBase::save_history_variables() { for (auto && mat : this->materials) { mat->save_history_variables(); } } /* ---------------------------------------------------------------------- */ template typename CellBase::iterator CellBase::begin() { return this->pixels.begin(); } /* ---------------------------------------------------------------------- */ template typename CellBase::iterator CellBase::end() { return this->pixels.end(); } /* ---------------------------------------------------------------------- */ template auto CellBase::get_adaptor() -> Adaptor { return Adaptor(*this); } /* ---------------------------------------------------------------------- */ template void CellBase::check_material_coverage() { auto nb_pixels = CcoordOps::get_size(this->subdomain_resolutions); std::vector *> assignments(nb_pixels, nullptr); for (auto & mat : this->materials) { for (auto & pixel : *mat) { auto index = CcoordOps::get_index(this->subdomain_resolutions, this->subdomain_locations, pixel); auto & assignment{assignments.at(index)}; if (assignment != nullptr) { std::stringstream err{}; err << "Pixel " << pixel << "is already assigned to material '" << assignment->get_name() << "' and cannot be reassigned to material '" << mat->get_name(); throw std::runtime_error(err.str()); } else { assignments[index] = mat.get(); } } } // find and identify unassigned pixels std::vector unassigned_pixels; for (size_t i = 0; i < assignments.size(); ++i) { if (assignments[i] == nullptr) { unassigned_pixels.push_back(CcoordOps::get_ccoord( this->subdomain_resolutions, this->subdomain_locations, i)); } } if (unassigned_pixels.size() != 0) { std::stringstream err{}; err << "The following pixels have were not assigned a material: "; for (auto & pixel : unassigned_pixels) { err << pixel << ", "; } err << "and that cannot be handled"; throw std::runtime_error(err.str()); } } template class CellBase; template class CellBase; } // namespace muSpectre diff --git a/src/cell/cell_base.hh b/src/cell/cell_base.hh index 369a050..f72f350 100644 --- a/src/cell/cell_base.hh +++ b/src/cell/cell_base.hh @@ -1,596 +1,679 @@ /** * @file cell_base.hh * * @author Till Junge * * @date 01 Nov 2017 * * @brief Base class representing a unit cell cell with single * projection operator * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_CELL_CELL_BASE_HH_ #define SRC_CELL_CELL_BASE_HH_ #include "common/common.hh" #include "common/ccoord_operations.hh" #include "common/field.hh" #include "common/utilities.hh" #include "materials/material_base.hh" #include "fft/projection_base.hh" #include "cell/cell_traits.hh" #include #include #include #include #include namespace muSpectre { /** * Cell adaptors implement the matrix-vector multiplication and * allow the system to be used like a sparse matrix in * conjugate-gradient-type solvers */ template class CellAdaptor; /** * Base class for cells that is not templated and therefore can be * in solvers that see cells as runtime-polymorphic objects. This * allows the use of standard * (i.e. spectral-method-implementation-agnostic) solvers, as for * instance the scipy solvers */ class Cell { public: //! sparse matrix emulation using Adaptor = CellAdaptor; //! dynamic vector type for interactions with numpy/scipy/solvers etc. using Vector_t = Eigen::Matrix; //! dynamic matrix type for setting strains using Matrix_t = Eigen::Matrix; //! dynamic generic array type for interaction with numpy, i/o, etc template using Array_t = Eigen::Array; //! ref to dynamic generic array template using Array_ref = Eigen::Map>; //! ref to constant vector using ConstVector_ref = Eigen::Map; //! output vector reference for solvers using Vector_ref = Eigen::Map; //! Default constructor Cell() = default; //! Copy constructor Cell(const Cell & other) = default; //! Move constructor Cell(Cell && other) = default; //! Destructor virtual ~Cell() = default; //! Copy assignment operator Cell & operator=(const Cell & other) = default; //! Move assignment operator Cell & operator=(Cell && other) = default; //! for handling double initialisations right bool is_initialised() const { return this->initialised; } //! returns the number of degrees of freedom in the cell virtual Dim_t get_nb_dof() const = 0; //! number of pixels in the cell virtual size_t size() const = 0; //! return the communicator object virtual const Communicator & get_communicator() const = 0; /** * formulation is hard set by the choice of the projection class */ virtual const Formulation & get_formulation() const = 0; /** * returns the material dimension of the problem */ virtual Dim_t get_material_dim() const = 0; /** * returns the number of rows and cols for the strain matrix type * (for full storage, the strain is stored in material_dim × * material_dim matrices, but in symmetriy storage, it is a column * vector) */ virtual std::array get_strain_shape() const = 0; /** * returns a writable map onto the strain field of this cell. This * corresponds to the unknowns in a typical solve cycle. */ virtual Vector_ref get_strain_vector() = 0; /** * returns a read-only map onto the stress field of this * cell. This corresponds to the intermediate (and finally, total) * solution in a typical solve cycle */ virtual ConstVector_ref get_stress_vector() const = 0; /** * evaluates and returns the stress for the currently set strain */ virtual ConstVector_ref evaluate_stress() = 0; /** * evaluates and returns the stress and stiffness for the currently set * strain */ virtual std::array evaluate_stress_tangent() = 0; /** * applies the projection operator in-place on the input vector */ virtual void apply_projection(Eigen::Ref vec) = 0; + /** + * evaluates the projection of the input field (this corresponds + * to G:P in de Geus 2017, + * http://dx.doi.org/10.1016/j.cma.2016.12.032). The first time, + * this allocates the memory for the return value, and reuses it + * on subsequent calls + */ + virtual Vector_ref evaluate_projection(Eigen::Ref P) = 0; + /** * freezes all the history variables of the materials */ virtual void save_history_variables() = 0; /** * evaluates the directional and projected stiffness (this * corresponds to G:K:δF in de Geus 2017, * http://dx.doi.org/10.1016/j.cma.2016.12.032). It seems that * this operation needs to be implemented with a copy in oder to * be compatible with scipy and EigenCG etc (At the very least, * the copy is only made once) */ virtual Vector_ref evaluate_projected_directional_stiffness( Eigen::Ref delF) = 0; /** * returns a ref to a field named 'unique_name" of real values * managed by the cell. If the field does not yet exist, it is * created. * * @param unique_name name of the field. If the field already * exists, an array ref mapped onto it is returned. Else, a new * field with that name is created and returned- * * @param nb_components number of components to be stored *per * pixel*. For new fields any positive number can be chosen. When * accessing an existing field, this must correspond to the * existing field size, and a `std::runtime_error` is thrown if * this is not satisfied */ virtual Array_ref get_managed_real_array(std::string unique_name, size_t nb_components) = 0; /** * Convenience function to copy local (internal) fields of * materials into a global field. At least one of the materials in * the cell needs to contain an internal field named * `unique_name`. If multiple materials contain such a field, they * all need to be of same scalar type and same number of * components. This does not work for split pixel cells or * laminate pixel cells, as they can have multiple entries for the * same pixel. Pixels for which no field named `unique_name` * exists get an array of zeros. * * @param unique_name fieldname to fill the global field with. At * least one material must have such a field, or a * `std::runtime_error` is thrown */ virtual Array_ref get_globalised_internal_real_array(const std::string & unique_name) = 0; + /** + * Convenience function to copy local (internal) state fields + * (current state) of materials into a global field. At least one + * of the materials in the cell needs to contain an internal field + * named `unique_name`. If multiple materials contain such a + * field, they all need to be of same scalar type and same number + * of components. This does not work for split pixel cells or + * laminate pixel cells, as they can have multiple entries for the + * same pixel. Pixels for which no field named `unique_name` + * exists get an array of zeros. + * + * @param unique_name fieldname to fill the global field with. At + * least one material must have such a field, or a + * `std::runtime_error` is thrown + */ + virtual Array_ref + get_globalised_current_real_array(const std::string & unique_name) = 0; + + /** + * Convenience function to copy local (internal) state fields + * (old state) of materials into a global field. At least one + * of the materials in the cell needs to contain an internal field + * named `unique_name`. If multiple materials contain such a + * field, they all need to be of same scalar type and same number + * of components. This does not work for split pixel cells or + * laminate pixel cells, as they can have multiple entries for the + * same pixel. Pixels for which no field named `unique_name` + * exists get an array of zeros. + * + * @param unique_name fieldname to fill the global field with. At + * least one material must have such a field, or a + * `std::runtime_error` is thrown + */ + virtual Array_ref + get_globalised_old_real_array(const std::string & unique_name, + int nb_steps_ago = 1) = 0; + /** * set uniform strain (typically used to initialise problems */ virtual void set_uniform_strain(const Eigen::Ref &) = 0; //! get a sparse matrix view on the cell virtual Adaptor get_adaptor() = 0; protected: bool initialised{false}; //!< to handle double initialisation right private: }; //! DimS spatial dimension (dimension of problem //! DimM material_dimension (dimension of constitutive law) template class CellBase : public Cell { public: using Parent = Cell; using Ccoord = Ccoord_t; //!< cell coordinates type using Rcoord = Rcoord_t; //!< physical coordinates type //! global field collection using FieldCollection_t = GlobalFieldCollection; //! the collection is handled in a `std::unique_ptr` using Collection_ptr = std::unique_ptr; //! polymorphic base material type using Material_t = MaterialBase; //! materials handled through `std::unique_ptr`s using Material_ptr = std::unique_ptr; //! polymorphic base projection type using Projection_t = ProjectionBase; //! projections handled through `std::unique_ptr`s using Projection_ptr = std::unique_ptr; //! dynamic global fields template using Field_t = TypedField; //! expected type for strain fields using StrainField_t = TensorField; //! expected type for stress fields using StressField_t = TensorField; //! expected type for tangent stiffness fields using TangentField_t = TensorField; //! combined stress and tangent field using FullResponse_t = std::tuple; //! iterator type over all cell pixel's using iterator = typename CcoordOps::Pixels::iterator; //! dynamic vector type for interactions with numpy/scipy/solvers etc. using Vector_t = typename Parent::Vector_t; //! ref to constant vector using ConstVector_ref = typename Parent::ConstVector_ref; //! output vector reference for solvers using Vector_ref = typename Parent::Vector_ref; //! dynamic array type for interactions with numpy/scipy/solvers, etc. template using Array_t = typename Parent::Array_t; //! dynamic array type for interactions with numpy/scipy/solvers, etc. template using Array_ref = typename Parent::Array_ref; //! sparse matrix emulation using Adaptor = Parent::Adaptor; //! Default constructor CellBase() = delete; //! constructor using sizes and resolution explicit CellBase(Projection_ptr projection); //! Copy constructor CellBase(const CellBase & other) = delete; //! Move constructor CellBase(CellBase && other); //! Destructor virtual ~CellBase() = default; //! Copy assignment operator CellBase & operator=(const CellBase & other) = delete; //! Move assignment operator CellBase & operator=(CellBase && other) = default; /** * Materials can only be moved. This is to assure exclusive * ownership of any material by this cell */ Material_t & add_material(Material_ptr mat); /** * returns a writable map onto the strain field of this cell. This * corresponds to the unknowns in a typical solve cycle. */ Vector_ref get_strain_vector() override; /** * returns a read-only map onto the stress field of this * cell. This corresponds to the intermediate (and finally, total) * solution in a typical solve cycle */ ConstVector_ref get_stress_vector() const override; /** * evaluates and returns the stress for the currently set strain */ ConstVector_ref evaluate_stress() override; /** * evaluates and returns the stress and stiffness for the currently set * strain */ std::array evaluate_stress_tangent() override; + /** + * evaluates the projection of the input field (this corresponds + * do G:P in de Geus 2017, + * http://dx.doi.org/10.1016/j.cma.2016.12.032). The first time, + * this allocates the memory for the return value, and reuses it + * on subsequent calls + */ + Vector_ref + evaluate_projection(Eigen::Ref P) override; + /** * evaluates the directional and projected stiffness (this * corresponds to G:K:δF in de Geus 2017, * http://dx.doi.org/10.1016/j.cma.2016.12.032). It seems that * this operation needs to be implemented with a copy in oder to * be compatible with scipy and EigenCG etc. (At the very least, * the copy is only made once) */ Vector_ref evaluate_projected_directional_stiffness( Eigen::Ref delF) override; //! return the template param DimM (required for polymorphic use of `Cell` Dim_t get_material_dim() const final { return DimM; } /** * returns the number of rows and cols for the strain matrix type * (for full storage, the strain is stored in material_dim × * material_dim matrices, but in symmetriy storage, it is a column * vector) */ std::array get_strain_shape() const final; /** * applies the projection operator in-place on the input vector */ void apply_projection(Eigen::Ref vec) final; /** * set uniform strain (typically used to initialise problems */ void set_uniform_strain(const Eigen::Ref &) override; /** * evaluates all materials */ FullResponse_t evaluate_stress_tangent(StrainField_t & F); /** * evaluate directional stiffness (i.e. G:K:δF or G:K:δε) */ StressField_t & directional_stiffness(const TangentField_t & K, const StrainField_t & delF, StressField_t & delP); /** * vectorized version for eigen solvers, no copy, but only works * when fields have ArrayStore=false */ Vector_ref directional_stiffness_vec(const Eigen::Ref & delF); /** * Evaluate directional stiffness into a temporary array and * return a copy. This is a costly and wasteful interface to * directional_stiffness and should only be used for debugging or * in the python interface */ Eigen::ArrayXXd directional_stiffness_with_copy(Eigen::Ref delF); /** * Convenience function circumventing the neeed to use the * underlying projection */ StressField_t & project(StressField_t & field); //! returns a ref to the cell's strain field StrainField_t & get_strain(); //! returns a ref to the cell's stress field const StressField_t & get_stress() const; //! returns a ref to the cell's tangent stiffness field const TangentField_t & get_tangent(bool create = false); //! returns a ref to a temporary field managed by the cell StrainField_t & get_managed_T2_field(std::string unique_name); //! returns a ref to a temporary field of real values managed by the cell Field_t & get_managed_real_field(std::string unique_name, size_t nb_components); /** * returns a Array ref to a temporary field of real values managed by the * cell */ Array_ref get_managed_real_array(std::string unique_name, size_t nb_components) final; /** * returns a global field filled from local (internal) fields of - * the materials. see `Cell::get_globalised_internal_array` for + * the materials. see `Cell::get_globalised_internal_real_array` for * details. */ Field_t & get_globalised_internal_real_field(const std::string & unique_name); - //! see `Cell::get_globalised_internal_array` for details + /** + * returns a global field filled from local (internal) statefields of + * the materials. see `Cell::get_globalised_current_real_array` for + * details. + */ + Field_t & + get_globalised_current_real_field(const std::string & unique_name); + + /** + * returns a global field filled from local (internal) statefields of + * the materials. see `Cell::get_globalised_old_real_array` for + * details. + */ + Field_t & + get_globalised_old_real_field(const std::string & unique_name, + int nb_steps_ago = 1); + + //! see `Cell::get_globalised_internal_real_array` for details + Array_ref get_globalised_internal_real_array( + const std::string & unique_name) final; + //! see `Cell::get_globalised_current_reald_array` for details + Array_ref get_globalised_current_real_array( + const std::string & unique_name) final; + //! see `Cell::get_globalised_old_real_array` for details Array_ref - get_globalised_internal_real_array(const std::string & unique_name) final; + get_globalised_old_real_array(const std::string & unique_name, + int nb_steps_ago = 1) final; /** * general initialisation; initialises the projection and * fft_engine (i.e. infrastructure) but not the materials. These * need to be initialised separately */ void initialise(FFT_PlanFlags flags = FFT_PlanFlags::estimate); /** * for materials with state variables, these typically need to be * saved/updated an the end of each load increment, this function * calls this update for each material in the cell */ void save_history_variables() final; iterator begin(); //!< iterator to the first pixel iterator end(); //!< iterator past the last pixel //! number of pixels in the cell size_t size() const final { return pixels.size(); } //! return the subdomain resolutions of the cell const Ccoord & get_subdomain_resolutions() const { return this->subdomain_resolutions; } //! return the subdomain locations of the cell const Ccoord & get_subdomain_locations() const { return this->subdomain_locations; } //! return the domain resolutions of the cell const Ccoord & get_domain_resolutions() const { return this->domain_resolutions; } //! return the sizes of the cell const Rcoord & get_domain_lengths() const { return this->domain_lengths; } /** * formulation is hard set by the choice of the projection class */ const Formulation & get_formulation() const final { return this->projection->get_formulation(); } /** * get a reference to the projection object. should only be * required for debugging */ Eigen::Map get_projection() { return this->projection->get_operator(); } //! returns the spatial size constexpr static Dim_t get_sdim() { return DimS; } //! return a sparse matrix adaptor to the cell Adaptor get_adaptor() override; //! returns the number of degrees of freedom in the cell Dim_t get_nb_dof() const override { return this->size() * ipow(DimS, 2); }; //! return the communicator object const Communicator & get_communicator() const override { return this->projection->get_communicator(); } protected: + template + Field_t & globalised_field_helper(const std::string & unique_name, + int nb_steps_ago); //! make sure that every pixel is assigned to one and only one material void check_material_coverage(); const Ccoord & subdomain_resolutions; //!< the cell's subdomain resolutions const Ccoord & subdomain_locations; //!< the cell's subdomain resolutions const Ccoord & domain_resolutions; //!< the cell's domain resolutions CcoordOps::Pixels pixels; //!< helper to iterate over the pixels const Rcoord & domain_lengths; //!< the cell's lengths Collection_ptr fields; //!< handle for the global fields of the cell StrainField_t & F; //!< ref to strain field StressField_t & P; //!< ref to stress field //! Tangent field might not even be required; so this is an //! optional ref_wrapper instead of a ref optional> K{}; //! container of the materials present in the cell std::vector materials{}; Projection_ptr projection; //!< handle for the projection operator private: }; /** * lightweight resource handle wrapping a `muSpectre::Cell` or * a subclass thereof into `Eigen::EigenBase`, so it can be * interpreted as a sparse matrix by Eigen solvers */ template class CellAdaptor : public Eigen::EigenBase> { public: using Scalar = double; //!< sparse matrix traits using RealScalar = double; //!< sparse matrix traits using StorageIndex = int; //!< sparse matrix traits enum { ColsAtCompileTime = Eigen::Dynamic, MaxColsAtCompileTime = Eigen::Dynamic, RowsAtCompileTime = Eigen::Dynamic, MaxRowsAtCompileTime = Eigen::Dynamic, IsRowMajor = false }; //! constructor explicit CellAdaptor(Cell & cell) : cell{cell} {} //! returns the number of logical rows Eigen::Index rows() const { return this->cell.get_nb_dof(); } //! returns the number of logical columns Eigen::Index cols() const { return this->rows(); } //! implementation of the evaluation template Eigen::Product operator*(const Eigen::MatrixBase & x) const { return Eigen::Product( *this, x.derived()); } Cell & cell; //!< ref to the cell }; } // namespace muSpectre namespace Eigen { namespace internal { //! Implementation of `muSpectre::CellAdaptor` * `Eigen::DenseVector` //! through a specialization of `Eigen::internal::generic_product_impl`: template // GEMV stands for matrix-vector struct generic_product_impl : generic_product_impl_base> { //! undocumented typedef typename Product::Scalar Scalar; //! undocumented template static void scaleAndAddTo(Dest & dst, const CellAdaptor & lhs, const Rhs & rhs, const Scalar & /*alpha*/) { // This method should implement "dst += alpha * lhs * rhs" inplace, // however, for iterative solvers, alpha is always equal to 1, so // let's not bother about it. // Here we could simply call dst.noalias() += lhs.my_matrix() * rhs, dst.noalias() += const_cast(lhs) .cell.evaluate_projected_directional_stiffness(rhs); } }; } // namespace internal } // namespace Eigen #endif // SRC_CELL_CELL_BASE_HH_ diff --git a/src/common/common.hh b/src/common/common.hh index c99bcbd..bfd5dc7 100644 --- a/src/common/common.hh +++ b/src/common/common.hh @@ -1,315 +1,322 @@ /** * @file common.hh * * @author Till Junge * * @date 01 May 2017 * * @brief Small definitions of commonly used types throughout µSpectre * * @section LICENSE * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #include #include #include #include #include #include #ifndef SRC_COMMON_COMMON_HH_ #define SRC_COMMON_COMMON_HH_ namespace muSpectre { /** * Eigen uses signed integers for dimensions. For consistency, µSpectre uses them througout the code. needs to represent -1 for eigen */ using Dim_t = int; constexpr Dim_t oneD{1}; //!< constant for a one-dimensional problem constexpr Dim_t twoD{2}; //!< constant for a two-dimensional problem constexpr Dim_t threeD{3}; //!< constant for a three-dimensional problem constexpr Dim_t firstOrder{1}; //!< constant for vectors constexpr Dim_t secondOrder{2}; //!< constant second-order tensors constexpr Dim_t fourthOrder{4}; //!< constant fourth-order tensors //@{ //! @anchor scalars //! Scalar types used for mathematical calculations using Uint = unsigned int; using Int = int; using Real = double; using Complex = std::complex; //@} //! Ccoord_t are cell coordinates, i.e. integer coordinates template using Ccoord_t = std::array; //! Real space coordinates template using Rcoord_t = std::array; /** * Allows inserting `muSpectre::Ccoord_t` and `muSpectre::Rcoord_t` * into `std::ostream`s */ template std::ostream & operator<<(std::ostream & os, const std::array & index) { os << "("; for (size_t i = 0; i < dim - 1; ++i) { os << index[i] << ", "; } os << index.back() << ")"; return os; } //! element-wise division template Rcoord_t operator/(const Rcoord_t & a, const Rcoord_t & b) { Rcoord_t retval{a}; for (size_t i = 0; i < dim; ++i) { retval[i] /= b[i]; } return retval; } //! element-wise division template Rcoord_t operator/(const Rcoord_t & a, const Ccoord_t & b) { Rcoord_t retval{a}; for (size_t i = 0; i < dim; ++i) { retval[i] /= b[i]; } return retval; } //! convenience definitions constexpr Real pi{3.1415926535897932384626433}; //! compile-time potentiation required for field-size computations template constexpr R ipow(R base, I exponent) { static_assert(std::is_integral::value, "Type must be integer"); R retval{1}; for (I i = 0; i < exponent; ++i) { retval *= base; } return retval; } /** * Copyright banner to be printed to the terminal by executables * Arguments are the executable's name, year of writing and the name * + address of the copyright holder */ void banner(std::string name, Uint year, std::string cpy_holder); /** * Planner flags for FFT (follows FFTW, hopefully this choice will * be compatible with alternative FFT implementations) * @enum muSpectre::FFT_PlanFlags */ enum class FFT_PlanFlags { estimate, //!< cheapest plan for slowest execution measure, //!< more expensive plan for fast execution patient //!< very expensive plan for fastest execution }; //! continuum mechanics flags enum class Formulation { finite_strain, //!< causes evaluation in PK1(F) small_strain, //!< causes evaluation in σ(ε) small_strain_sym //!< symmetric storage as vector ε }; + //! finite differences flags + enum class FiniteDiff { + forward, //!< ∂f/∂x ≈ (f(x+Δx) - f(x))/Δx + backward, //!< ∂f/∂x ≈ (f(x) - f(x-Δx))/Δx + centred //!< ∂f/∂x ≈ (f(x+Δx) - f(x-Δx))/2Δx + }; + /** * compile time computation of voigt vector */ template constexpr Dim_t vsize(Dim_t dim) { if (sym) { return (dim * (dim - 1) / 2 + dim); } else { return dim * dim; } } //! compute the number of degrees of freedom to store for the strain //! tenor given dimension dim constexpr Dim_t dof_for_formulation(const Formulation form, const Dim_t dim) { switch (form) { case Formulation::small_strain_sym: { return vsize(dim); break; } default: return ipow(dim, 2); break; } } //! inserts `muSpectre::Formulation`s into `std::ostream`s std::ostream & operator<<(std::ostream & os, Formulation f); /* ---------------------------------------------------------------------- */ //! Material laws can declare which type of stress measure they provide, //! and µSpectre will handle conversions enum class StressMeasure { Cauchy, //!< Cauchy stress σ PK1, //!< First Piola-Kirchhoff stress PK2, //!< Second Piola-Kirchhoff stress Kirchhoff, //!< Kirchhoff stress τ Biot, //!< Biot stress Mandel, //!< Mandel stress no_stress_ //!< only for triggering static_asserts }; //! inserts `muSpectre::StressMeasure`s into `std::ostream`s std::ostream & operator<<(std::ostream & os, StressMeasure s); /* ---------------------------------------------------------------------- */ //! Material laws can declare which type of strain measure they require and //! µSpectre will provide it enum class StrainMeasure { Gradient, //!< placement gradient (δy/δx) Infinitesimal, //!< small strain tensor .5(∇u + ∇uᵀ) GreenLagrange, //!< Green-Lagrange strain .5(Fᵀ·F - I) Biot, //!< Biot strain Log, //!< logarithmic strain Almansi, //!< Almansi strain RCauchyGreen, //!< Right Cauchy-Green tensor LCauchyGreen, //!< Left Cauchy-Green tensor no_strain_ //!< only for triggering static_assert }; //! inserts `muSpectre::StrainMeasure`s into `std::ostream`s std::ostream & operator<<(std::ostream & os, StrainMeasure s); /* ---------------------------------------------------------------------- */ /** * all isotropic elastic moduli to identify conversions, such as E * = µ(3λ + 2µ)/(λ+µ). For the full description, see * https://en.wikipedia.org/wiki/Lam%C3%A9_parameters * Not all the conversions are implemented, so please add as needed */ enum class ElasticModulus { Bulk, //!< Bulk modulus K K = Bulk, //!< alias for ``ElasticModulus::Bulk`` Young, //!< Young's modulus E E = Young, //!< alias for ``ElasticModulus::Young`` lambda, //!< Lamé's first parameter λ Shear, //!< Shear modulus G or µ G = Shear, //!< alias for ``ElasticModulus::Shear`` mu = Shear, //!< alias for ``ElasticModulus::Shear`` Poisson, //!< Poisson's ratio ν nu = Poisson, //!< alias for ``ElasticModulus::Poisson`` Pwave, //!< P-wave modulus M M = Pwave, //!< alias for ``ElasticModulus::Pwave`` no_modulus_ }; //!< only for triggering static_asserts /** * define comparison in order to exploit that moduli can be * expressed in terms of any two other moduli in any order (e.g. K * = K(E, ν) = K(ν, E) */ constexpr inline bool operator<(ElasticModulus A, ElasticModulus B) { return static_cast(A) < static_cast(B); } /* ---------------------------------------------------------------------- */ /** Compile-time function to g strain measure stored by muSpectre depending on the formulation **/ constexpr StrainMeasure get_stored_strain_type(Formulation form) { switch (form) { case Formulation::finite_strain: { return StrainMeasure::Gradient; break; } case Formulation::small_strain: { return StrainMeasure::Infinitesimal; break; } default: return StrainMeasure::no_strain_; break; } } /** Compile-time function to g stress measure stored by muSpectre depending on the formulation **/ constexpr StressMeasure get_stored_stress_type(Formulation form) { switch (form) { case Formulation::finite_strain: { return StressMeasure::PK1; break; } case Formulation::small_strain: { return StressMeasure::Cauchy; break; } default: return StressMeasure::no_stress_; break; } } /* ---------------------------------------------------------------------- */ /** Compile-time functions to get the stress and strain measures after they may have been modified by choosing a formulation. For instance, a law that expecs a Green-Lagrange strain as input will get the infinitesimal strain tensor instead in a small strain computation **/ constexpr StrainMeasure get_formulation_strain_type(Formulation form, StrainMeasure expected) { switch (form) { case Formulation::finite_strain: { return expected; break; } case Formulation::small_strain: { return get_stored_strain_type(form); break; } default: return StrainMeasure::no_strain_; break; } } } // namespace muSpectre #ifndef EXPLICITLY_TURNED_ON_CXX17 #include "common/utilities.hh" #endif #endif // SRC_COMMON_COMMON_HH_ diff --git a/src/common/eigen_tools.hh b/src/common/eigen_tools.hh index d72cc4a..da757d8 100644 --- a/src/common/eigen_tools.hh +++ b/src/common/eigen_tools.hh @@ -1,350 +1,450 @@ /** * @file eigen_tools.hh * * @author Till Junge * * @date 20 Sep 2017 * * @brief small tools to be used with Eigen * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_COMMON_EIGEN_TOOLS_HH_ #define SRC_COMMON_EIGEN_TOOLS_HH_ #include "common/common.hh" #include #include #include #include namespace muSpectre { /* ---------------------------------------------------------------------- */ namespace internal { //! Creates a Eigen::Sizes type for a Tensor defined by an order and dim template struct SizesByOrderHelper { //! type to use using Sizes = typename SizesByOrderHelper::Sizes; }; //! Creates a Eigen::Sizes type for a Tensor defined by an order and dim template struct SizesByOrderHelper<0, dim, dims...> { //! type to use using Sizes = Eigen::Sizes; }; } // namespace internal //! Creates a Eigen::Sizes type for a Tensor defined by an order and dim template struct SizesByOrder { static_assert(order > 0, "works only for order greater than zero"); //! `Eigen::Sizes` using Sizes = typename internal::SizesByOrderHelper::Sizes; }; /* ---------------------------------------------------------------------- */ namespace internal { /* ---------------------------------------------------------------------- */ //! Call a passed lambda with the unpacked sizes as arguments template struct CallSizesHelper { //! applies the call static decltype(auto) call(Fun_t && fun) { static_assert(order > 0, "can't handle empty sizes b)"); return CallSizesHelper::call(fun); } }; /* ---------------------------------------------------------------------- */ template //! Call a passed lambda with the unpacked sizes as arguments struct CallSizesHelper<0, Fun_t, dim, args...> { //! applies the call static decltype(auto) call(Fun_t && fun) { return fun(args...); } }; } // namespace internal /** * takes a lambda and calls it with the proper `Eigen::Sizes` * unpacked as arguments. Is used to call constructors of a * `Eigen::Tensor` or map thereof in a context where the spatial * dimension is templated */ template inline decltype(auto) call_sizes(Fun_t && fun) { static_assert(order > 1, "can't handle empty sizes"); return internal::CallSizesHelper::call( std::forward(fun)); } // compile-time square root static constexpr Dim_t ct_sqrt(Dim_t res, Dim_t l, Dim_t r) { if (l == r) { return r; } else { const auto mid = (r + l) / 2; if (mid * mid >= res) { return ct_sqrt(res, l, mid); } else { return ct_sqrt(res, mid + 1, r); } } } static constexpr Dim_t ct_sqrt(Dim_t res) { return ct_sqrt(res, 1, res); } namespace EigenCheck { /** * Structure to determine whether an expression can be evaluated * into a `Eigen::Matrix`, `Eigen::Array`, etc. and which helps * determine compile-time size */ template struct is_matrix { //! raw type for testing using T = std::remove_reference_t; //! evaluated test constexpr static bool value{ std::is_same::XprKind, Eigen::MatrixXpr>::value}; }; /** * Helper class to check whether an `Eigen::Array` or * `Eigen::Matrix` is statically sized */ template struct is_fixed { //! raw type for testing using T = std::remove_reference_t; //! evaluated test constexpr static bool value{T::SizeAtCompileTime != Eigen::Dynamic}; }; /** * Helper class to check whether an `Eigen::Array` or `Eigen::Matrix` is a * static-size and square. */ template struct is_square { //! raw type for testing using T = std::remove_reference_t; //! true if the object is square and statically sized constexpr static bool value{ (T::RowsAtCompileTime == T::ColsAtCompileTime) && is_fixed::value}; }; /** * computes the dimension from a second order tensor represented * square matrix or array */ template struct tensor_dim { //! raw type for testing using T = std::remove_reference_t; static_assert(is_matrix::value, "The type of t is not understood as an Eigen::Matrix"); static_assert(is_square::value, "t's matrix isn't square"); //! evaluated dimension constexpr static Dim_t value{T::RowsAtCompileTime}; }; //! computes the dimension from a fourth order tensor represented //! by a square matrix template struct tensor_4_dim { //! raw type for testing using T = std::remove_reference_t; static_assert(is_matrix::value, "The type of t is not understood as an Eigen::Matrix"); static_assert(is_square::value, "t's matrix isn't square"); //! evaluated dimension constexpr static Dim_t value{ct_sqrt(T::RowsAtCompileTime)}; static_assert(value * value == T::RowsAtCompileTime, "This is not a fourth-order tensor mapped on a square " "matrix"); }; - }; // namespace EigenCheck + namespace internal { + template + constexpr inline Dim_t get_rank() { + constexpr bool is_vec{(nb_row == dim) and (nb_col == 1)}; + constexpr bool is_mat{(nb_row == dim) and (nb_col == nb_row)}; + constexpr bool is_ten{(nb_row == dim * dim) and (nb_col == dim * dim)}; + static_assert(is_vec or is_mat or is_ten, + "can't understand the data type as a first-, second-, or " + "fourth-order tensor"); + if (is_vec) { + return firstOrder; + } else if (is_mat) { + return secondOrder; + } else if (is_ten) { + return fourthOrder; + } + } + + } // namespace internal + + /** + * computes the rank of a tensor given the spatial dimension + */ + template + struct tensor_rank { + using T = std::remove_reference_t; + static_assert(is_matrix::value, + "The type of t is not understood as an Eigen::Matrix"); + static constexpr Dim_t value{internal::get_rank()}; + }; + + } // namespace EigenCheck namespace log_comp { //! Matrix type used for logarithm evaluation template using Mat_t = Eigen::Matrix; //! Vector type used for logarithm evaluation template using Vec_t = Eigen::Matrix; //! This is a static implementation of the explicit determination //! of log(Tensor) following Jog, C.S. J Elasticity (2008) 93: //! 141. https://doi.org/10.1007/s10659-008-9169-x /* ---------------------------------------------------------------------- */ template struct Proj { //! wrapped function (raison d'être) static inline decltype(auto) compute(const Vec_t & eigs, const Mat_t & T) { static_assert(dim > 0, "only works for positive dimensions"); return 1. / (eigs(i) - eigs(j)) * (T - eigs(j) * Mat_t::Identity()) * Proj::compute(eigs, T); } }; //! catch the case when there's nothing to do template struct Proj { //! wrapped function (raison d'être) static inline decltype(auto) compute(const Vec_t & eigs, const Mat_t & T) { static_assert(dim > 0, "only works for positive dimensions"); return Proj::compute(eigs, T); } }; //! catch the normal tail case template struct Proj { static constexpr Dim_t j{0}; //!< short-hand //! wrapped function (raison d'être) static inline decltype(auto) compute(const Vec_t & eigs, const Mat_t & T) { static_assert(dim > 0, "only works for positive dimensions"); return 1. / (eigs(i) - eigs(j)) * (T - eigs(j) * Mat_t::Identity()); } }; //! catch the tail case when the last dimension is i template struct Proj { static constexpr Dim_t i{0}; //!< short-hand static constexpr Dim_t j{1}; //!< short-hand //! wrapped function (raison d'être) static inline decltype(auto) compute(const Vec_t & eigs, const Mat_t & T) { static_assert(dim > 0, "only works for positive dimensions"); return 1. / (eigs(i) - eigs(j)) * (T - eigs(j) * Mat_t::Identity()); } }; //! catch the general tail case template <> struct Proj<1, 0, 0> { static constexpr Dim_t dim{1}; //!< short-hand static constexpr Dim_t i{0}; //!< short-hand static constexpr Dim_t j{0}; //!< short-hand //! wrapped function (raison d'être) static inline decltype(auto) compute(const Vec_t & /*eigs*/, const Mat_t & /*T*/) { return Mat_t::Identity(); } }; //! Product term template inline decltype(auto) P(const Vec_t & eigs, const Mat_t & T) { return Proj::compute(eigs, T); } //! sum term template struct Summand { //! wrapped function (raison d'être) static inline decltype(auto) compute(const Vec_t & eigs, const Mat_t & T) { return std::log(eigs(i)) * P(eigs, T) + Summand::compute(eigs, T); } }; //! sum term template struct Summand { static constexpr Dim_t i{0}; //!< short-hand //! wrapped function (raison d'être) static inline decltype(auto) compute(const Vec_t & eigs, const Mat_t & T) { return std::log(eigs(i)) * P(eigs, T); } }; //! sum implementation template inline decltype(auto) Sum(const Vec_t & eigs, const Mat_t & T) { return Summand::compute(eigs, T); } } // namespace log_comp /** * computes the matrix logarithm efficiently for dim=1, 2, or 3 for * a diagonizable tensor. For larger tensors, better use the direct * eigenvalue/vector computation */ template inline decltype(auto) logm(const log_comp::Mat_t & mat) { using Mat = log_comp::Mat_t; Eigen::SelfAdjointEigenSolver Solver{}; Solver.computeDirect(mat, Eigen::EigenvaluesOnly); return Mat{log_comp::Sum(Solver.eigenvalues(), mat)}; } /** - * compute the matrix exponential. This may not be the most - * efficient way to do this + * compute the spectral decomposition */ - template - inline decltype(auto) expm(const log_comp::Mat_t & mat) { + + template + inline decltype(auto) + spectral_decomposition(const Eigen::MatrixBase & mat) { + static_assert(Derived::SizeAtCompileTime != Eigen::Dynamic, + "works only for static matrices"); + static_assert(Derived::RowsAtCompileTime == Derived::ColsAtCompileTime, + "works only for square matrices"); + constexpr Dim_t dim{Derived::RowsAtCompileTime}; + using Mat = log_comp::Mat_t; Eigen::SelfAdjointEigenSolver Solver{}; Solver.computeDirect(mat, Eigen::ComputeEigenvectors); + return Solver; + } + + /** + * compute the matrix log. This may not be the most + * efficient way to do this + */ + template + using Decomp_t = Eigen::SelfAdjointEigenSolver>; + + template + inline decltype(auto) logm_alt(const Decomp_t & spectral_decomp) { + using Mat = log_comp::Mat_t; + + Mat retval{Mat::Zero()}; + for (Dim_t i = 0; i < Dim; ++i) { + const Real & val = spectral_decomp.eigenvalues()(i); + auto & vec = spectral_decomp.eigenvectors().col(i); + retval += std::log(val) * vec * vec.transpose(); + } + return retval; + } + + template + inline decltype(auto) logm_alt(const Eigen::MatrixBase & mat) { + static_assert(Derived::SizeAtCompileTime != Eigen::Dynamic, + "works only for static matrices"); + static_assert(Derived::RowsAtCompileTime == Derived::ColsAtCompileTime, + "works only for square matrices"); + constexpr Dim_t dim{Derived::RowsAtCompileTime}; + using Mat = log_comp::Mat_t; + using Decomp_t = Eigen::SelfAdjointEigenSolver; + + Decomp_t decomp{spectral_decomposition(mat)}; + + return logm_alt(decomp); + } + + /** + * compute the matrix exponential. This may not be the most + * efficient way to do this + */ + template + inline decltype(auto) expm(const Decomp_t & spectral_decomp) { + using Mat = log_comp::Mat_t; + Mat retval{Mat::Zero()}; - for (Dim_t i = 0; i < dim; ++i) { - const Real & val = Solver.eigenvalues()(i); - auto & vec = Solver.eigenvectors().col(i); + for (Dim_t i = 0; i < Dim; ++i) { + const Real & val = spectral_decomp.eigenvalues()(i); + auto & vec = spectral_decomp.eigenvectors().col(i); retval += std::exp(val) * vec * vec.transpose(); } return retval; } + template + inline decltype(auto) expm(const Eigen::MatrixBase & mat) { + static_assert(Derived::SizeAtCompileTime != Eigen::Dynamic, + "works only for static matrices"); + static_assert(Derived::RowsAtCompileTime == Derived::ColsAtCompileTime, + "works only for square matrices"); + constexpr Dim_t Dim{Derived::RowsAtCompileTime}; + using Mat = log_comp::Mat_t; + using Decomp_t = Eigen::SelfAdjointEigenSolver; + + Decomp_t decomp{spectral_decomposition(mat)}; + + return expm(decomp); + } + } // namespace muSpectre #endif // SRC_COMMON_EIGEN_TOOLS_HH_ diff --git a/src/common/field_base.hh b/src/common/field_base.hh index 3dfc84c..effa348 100644 --- a/src/common/field_base.hh +++ b/src/common/field_base.hh @@ -1,211 +1,209 @@ /** * file field_base.hh * * @author Till Junge * * @date 10 Apr 2018 * * @brief Virtual base class for fields * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_COMMON_FIELD_BASE_HH_ #define SRC_COMMON_FIELD_BASE_HH_ #include #include namespace muSpectre { /* ---------------------------------------------------------------------- */ /** * base class for field collection-related exceptions */ class FieldCollectionError : public std::runtime_error { public: //! constructor explicit FieldCollectionError(const std::string & what) : std::runtime_error(what) {} //! constructor explicit FieldCollectionError(const char * what) : std::runtime_error(what) {} }; /// base class for field-related exceptions class FieldError : public FieldCollectionError { using Parent = FieldCollectionError; public: //! constructor explicit FieldError(const std::string & what) : Parent(what) {} //! constructor explicit FieldError(const char * what) : Parent(what) {} }; /** * Thrown when a associating a field map to and incompatible field * is attempted */ class FieldInterpretationError : public FieldError { public: //! constructor explicit FieldInterpretationError(const std::string & what) : FieldError(what) {} //! constructor explicit FieldInterpretationError(const char * what) : FieldError(what) {} }; namespace internal { /* ---------------------------------------------------------------------- */ /** * Virtual base class for all fields. A field represents * meta-information for the per-pixel storage for a scalar, vector * or tensor quantity and is therefore the abstract class defining * the field. It is used for type and size checking at runtime and * for storage of polymorphic pointers to fully typed and sized * fields. `FieldBase` (and its children) are templated with a * specific `FieldCollection` (derived from * `muSpectre::FieldCollectionBase`). A `FieldCollection` stores * multiple fields that all apply to the same set of * pixels. Addressing and managing the data for all pixels is * handled by the `FieldCollection`. Note that `FieldBase` does * not know anything about about mathematical operations on the * data or how to iterate over all pixels. Mapping the raw data * onto for instance Eigen maps and iterating over those is * handled by the `FieldMap`. */ template class FieldBase { protected: //! constructor //! unique name (whithin Collection) //! number of components //! collection to which this field belongs (eg, material, cell) FieldBase(std::string unique_name, size_t nb_components, FieldCollection & collection); public: using collection_t = FieldCollection; //!< for type checks //! Copy constructor FieldBase(const FieldBase & other) = delete; //! Move constructor FieldBase(FieldBase && other) = delete; //! Destructor virtual ~FieldBase() = default; //! Copy assignment operator FieldBase & operator=(const FieldBase & other) = delete; //! Move assignment operator FieldBase & operator=(FieldBase && other) = delete; /* ---------------------------------------------------------------------- */ //! Identifying accessors //! return field name inline const std::string & get_name() const; //! return field type // inline const Field_t & get_type() const; //! return my collection (for iterating) inline const FieldCollection & get_collection() const; //! return number of components (e.g., dimensions) of this field inline const size_t & get_nb_components() const; //! return type_id of stored type virtual const std::type_info & get_stored_typeid() const = 0; //! number of pixels in the field virtual size_t size() const = 0; //! add a pad region to the end of the field buffer; required for //! using this as e.g. an FFT workspace virtual void set_pad_size(size_t pad_size_) = 0; //! pad region size virtual size_t get_pad_size() const { return this->pad_size; } //! initialise field to zero (do more complicated initialisations through //! fully typed maps) virtual void set_zero() = 0; //! give access to collections friend FieldCollection; //! give access to collection's base class using FParent_t = typename FieldCollection::Parent; friend FParent_t; protected: /* ---------------------------------------------------------------------- */ //! allocate memory etc virtual void resize(size_t size) = 0; const std::string name; //!< the field's unique name const size_t nb_components; //!< number of components per entry //! reference to the collection this field belongs to FieldCollection & collection; size_t pad_size; //!< size of padding region at end of buffer - - private: }; /* ---------------------------------------------------------------------- */ // Implementations /* ---------------------------------------------------------------------- */ template FieldBase::FieldBase(std::string unique_name, size_t nb_components_, FieldCollection & collection_) : name(unique_name), nb_components(nb_components_), collection(collection_), pad_size{0} {} /* ---------------------------------------------------------------------- */ template inline const std::string & FieldBase::get_name() const { return this->name; } /* ---------------------------------------------------------------------- */ template inline const FieldCollection & FieldBase::get_collection() const { return this->collection; } /* ---------------------------------------------------------------------- */ template inline const size_t & FieldBase::get_nb_components() const { return this->nb_components; } } // namespace internal } // namespace muSpectre #endif // SRC_COMMON_FIELD_BASE_HH_ diff --git a/src/common/field_collection_base.hh b/src/common/field_collection_base.hh index c54fbe4..6392d00 100644 --- a/src/common/field_collection_base.hh +++ b/src/common/field_collection_base.hh @@ -1,359 +1,382 @@ /** * @file field_collection_base.hh * * @author Till Junge * * @date 05 Nov 2017 * * @brief Base class for field collections * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_COMMON_FIELD_COLLECTION_BASE_HH_ #define SRC_COMMON_FIELD_COLLECTION_BASE_HH_ #include "common/common.hh" #include "common/field.hh" #include "common/statefield.hh" #include #include namespace muSpectre { /* ---------------------------------------------------------------------- */ /** `FieldCollectionBase` is the base class for collections of fields. All * fields in a field collection have the same number of pixels. The field * collection is templated with @a DimS is the spatial dimension (i.e. * whether the simulation domain is one, two or three-dimensional). * All fields within a field collection have a unique string identifier. * A `FieldCollectionBase` is therefore comparable to a dictionary of fields * that live on the same grid. * `FieldCollectionBase` has the specialisations `GlobalFieldCollection` and * `LocalFieldCollection`. */ template class FieldCollectionBase { public: //! polymorphic base type to store using Field_t = internal::FieldBase; template using TypedField_t = TypedField; using Field_p = std::unique_ptr; //!< stored type using StateField_t = StateFieldBase; template using TypedStateField_t = TypedStateField; using StateField_p = std::unique_ptr; using Ccoord = Ccoord_t; //!< cell coordinates type //! Default constructor FieldCollectionBase(); //! Copy constructor FieldCollectionBase(const FieldCollectionBase & other) = delete; //! Move constructor FieldCollectionBase(FieldCollectionBase && other) = delete; //! Destructor virtual ~FieldCollectionBase() = default; //! Copy assignment operator FieldCollectionBase & operator=(const FieldCollectionBase & other) = delete; //! Move assignment operator FieldCollectionBase & operator=(FieldCollectionBase && other) = delete; //! Register a new field (fields need to be in heap, so I want to keep them //! as shared pointers void register_field(Field_p && field); //! Register a new field (fields need to be in heap, so I want to keep them //! as shared pointers void register_statefield(StateField_p && field); //! for return values of iterators constexpr inline static Dim_t spatial_dim(); //! for return values of iterators inline Dim_t get_spatial_dim() const; //! return names of all stored fields std::vector get_field_names() const { std::vector names{}; for (auto & tup : this->fields) { names.push_back(std::get<0>(tup)); } return names; } //! return names of all state fields std::vector get_statefield_names() const { std::vector names{}; for (auto & tup : this->statefields) { names.push_back(std::get<0>(tup)); } return names; } //! retrieve field by unique_name inline Field_t & operator[](std::string unique_name); //! retrieve field by unique_name with bounds checking inline Field_t & at(std::string unique_name); //! retrieve typed field by unique_name template inline TypedField_t & get_typed_field(std::string unique_name); //! retrieve state field by unique_prefix with bounds checking template inline TypedStateField_t & get_typed_statefield(std::string unique_prefix); //! retrieve state field by unique_prefix with bounds checking inline StateField_t & get_statefield(std::string unique_prefix) { return *(this->statefields.at(unique_prefix)); } //! retrieve state field by unique_prefix with bounds checking inline const StateField_t & get_statefield(std::string unique_prefix) const { return *(this->statefields.at(unique_prefix)); } /** * retrieve current value of typed state field by unique_prefix with * bounds checking */ template inline TypedField_t & get_current(std::string unique_prefix); /** * retrieve old value of typed state field by unique_prefix with * bounds checking */ template inline const TypedField_t & get_old(std::string unique_prefix, size_t nb_steps_ago = 1) const; //! returns size of collection, this refers to the number of pixels handled //! by the collection, not the number of fields inline size_t size() const { return this->size_; } //! check whether a field is present bool check_field_exists(const std::string & unique_name); + //! check whether a field is present + bool check_statefield_exists(const std::string & unique_prefix); + //! check whether the collection is initialised bool initialised() const { return this->is_initialised; } + /** + * list the names of all fields + */ + std::vector list_fields() const; + protected: std::map fields{}; //!< contains the field ptrs //! contains ptrs to state fields std::map statefields{}; bool is_initialised{false}; //!< to handle double initialisation correctly const Uint id; //!< unique identifier static Uint counter; //!< used to assign unique identifiers size_t size_{0}; //!< holds the number of pixels after initialisation - - private: }; /* ---------------------------------------------------------------------- */ template Uint FieldCollectionBase::counter{0}; /* ---------------------------------------------------------------------- */ template FieldCollectionBase::FieldCollectionBase() : id(counter++) {} /* ---------------------------------------------------------------------- */ template void FieldCollectionBase::register_field( Field_p && field) { - auto && search_it = this->fields.find(field->get_name()); - auto && does_exist = search_it != this->fields.end(); - if (does_exist) { + if (this->check_field_exists(field->get_name())) { std::stringstream err_str; err_str << "a field named '" << field->get_name() << "' is already registered in this field collection. " << "Currently registered fields: "; std::string prelude{""}; for (const auto & name_field_pair : this->fields) { err_str << prelude << '\'' << name_field_pair.first << '\''; prelude = ", "; } throw FieldCollectionError(err_str.str()); } if (this->is_initialised) { field->resize(this->size()); } this->fields[field->get_name()] = std::move(field); } /* ---------------------------------------------------------------------- */ template void FieldCollectionBase::register_statefield( StateField_p && field) { auto && search_it = this->statefields.find(field->get_prefix()); auto && does_exist = search_it != this->statefields.end(); if (does_exist) { std::stringstream err_str; err_str << "a state field named '" << field->get_prefix() << "' is already registered in this field collection. " << "Currently registered fields: "; std::string prelude{""}; for (const auto & name_field_pair : this->statefields) { err_str << prelude << '\'' << name_field_pair.first << '\''; prelude = ", "; } throw FieldCollectionError(err_str.str()); } this->statefields[field->get_prefix()] = std::move(field); } /* ---------------------------------------------------------------------- */ template constexpr Dim_t FieldCollectionBase::spatial_dim() { return DimS; } /* ---------------------------------------------------------------------- */ template Dim_t FieldCollectionBase::get_spatial_dim() const { return DimS; } /* ---------------------------------------------------------------------- */ template auto FieldCollectionBase:: operator[](std::string unique_name) -> Field_t & { return *(this->fields[unique_name]); } /* ---------------------------------------------------------------------- */ template auto FieldCollectionBase::at(std::string unique_name) -> Field_t & { return *(this->fields.at(unique_name)); } /* ---------------------------------------------------------------------- */ template bool FieldCollectionBase::check_field_exists( const std::string & unique_name) { return this->fields.find(unique_name) != this->fields.end(); } + /* ---------------------------------------------------------------------- */ + template + bool + FieldCollectionBase::check_statefield_exists( + const std::string & unique_prefix) { + return this->statefields.find(unique_prefix) != this->statefields.end(); + } + //! retrieve typed field by unique_name template template auto FieldCollectionBase::get_typed_field( std::string unique_name) -> TypedField_t & { auto & unqualified_field{this->at(unique_name)}; if (unqualified_field.get_stored_typeid().hash_code() != typeid(T).hash_code()) { std::stringstream err{}; err << "Field '" << unique_name << "' is of type " << unqualified_field.get_stored_typeid().name() << ", but should be of type " << typeid(T).name() << std::endl; throw FieldCollectionError(err.str()); } return static_cast &>(unqualified_field); } /* ---------------------------------------------------------------------- */ //! retrieve state field by unique_prefix with bounds checking template template auto FieldCollectionBase::get_typed_statefield( std::string unique_prefix) -> TypedStateField_t & { auto & unqualified_statefield{this->get_statefield(unique_prefix)}; if (unqualified_statefield.get_stored_typeid().hash_code() != typeid(T).hash_code()) { std::stringstream err{}; err << "Statefield '" << unique_prefix << "' is of type " << unqualified_statefield.get_stored_typeid().name() << ", but should be of type " << typeid(T).name() << std::endl; throw FieldCollectionError(err.str()); } return static_cast &>(unqualified_statefield); } + /* ---------------------------------------------------------------------- */ + template + std::vector + FieldCollectionBase::list_fields() const { + std::vector ret_val{}; + for (auto & key_val : this->fields) { + ret_val.push_back(key_val.first); + } + return ret_val; + } + /* ---------------------------------------------------------------------- */ template template auto FieldCollectionBase::get_current( std::string unique_prefix) -> TypedField_t & { auto & unqualified_statefield = this->get_statefield(unique_prefix); //! check for correct underlying fundamental type if (unqualified_statefield.get_stored_typeid().hash_code() != typeid(T).hash_code()) { std::stringstream err{}; err << "StateField '" << unique_prefix << "' is of type " << unqualified_statefield.get_stored_typeid().name() << ", but should be of type " << typeid(T).name() << std::endl; throw FieldCollectionError(err.str()); } using Typed_t = TypedStateField; auto & typed_field{static_cast(unqualified_statefield)}; return typed_field.get_current_field(); } /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ template template auto FieldCollectionBase::get_old( std::string unique_prefix, size_t nb_steps_ago) const -> const TypedField_t & { auto & unqualified_statefield = this->get_statefield(unique_prefix); //! check for correct underlying fundamental type if (unqualified_statefield.get_stored_typeid().hash_code() != typeid(T).hash_code()) { std::stringstream err{}; err << "StateField '" << unique_prefix << "' is of type " << unqualified_statefield.get_stored_typeid().name() << ", but should be of type " << typeid(T).name() << std::endl; throw FieldCollectionError(err.str()); } using Typed_t = TypedStateField; auto & typed_field{static_cast(unqualified_statefield)}; return typed_field.get_old_field(nb_steps_ago); } } // namespace muSpectre #endif // SRC_COMMON_FIELD_COLLECTION_BASE_HH_ diff --git a/src/common/field_map_base.hh b/src/common/field_map_base.hh index 3e6903c..e3ee1b6 100644 --- a/src/common/field_map_base.hh +++ b/src/common/field_map_base.hh @@ -1,891 +1,886 @@ /** * @file field_map.hh * * @author Till Junge * * @date 12 Sep 2017 * * @brief Defined a strongly defines proxy that iterates efficiently over a * field * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_COMMON_FIELD_MAP_BASE_HH_ #define SRC_COMMON_FIELD_MAP_BASE_HH_ -#include "common/common.hh" #include "common/field.hh" #include "field_collection_base.hh" #include #include #include #include namespace muSpectre { namespace internal { /** * Forward-declaration */ template class TypedSizedFieldBase; //! little helper to automate creation of const maps without duplication template struct const_corrector { //! non-const type using type = typename T::reference; }; //! specialisation for constant case template struct const_corrector { //! const type using type = typename T::const_reference; }; //! convenience alias template using const_corrector_t = typename const_corrector::type; //----------------------------------------------------------------------------// /** * `FieldMap` provides two central mechanisms: * - Map a field (that knows only about the size of the underlying object, * onto the mathematical object (reprensented by the respective Eigen class) * that provides linear algebra functionality. * - Provide an iterator that allows to iterate over all pixels. * A field is represented by `FieldBase` or a derived class. * `FieldMap` has the specialisations `MatrixLikeFieldMap`, * `ScalarFieldMap` and `TensorFieldMap`. */ template class FieldMap { static_assert((NbComponents != 0), "Fields with now components make no sense."); /* * Eigen::Dynamic is equal to -1, and is a legal value, hence * the following peculiar check */ static_assert( (NbComponents > -2), "Fields with a negative number of components make no sense."); public: //! Fundamental type stored using Scalar = T; //! number of scalars per entry constexpr static auto nb_components{NbComponents}; //! non-constant version of field using TypedField_nc = std::conditional_t< (NbComponents >= 1), TypedSizedFieldBase, TypedField>; //! field type as seen from iterator using TypedField_t = std::conditional_t; using Field = typename TypedField_nc::Base; //!< iterated field type //! const-correct field type using Field_c = std::conditional_t; using size_type = std::size_t; //!< stl conformance using pointer = std::conditional_t; //!< stl conformance //! Default constructor FieldMap() = delete; //! constructor explicit FieldMap(Field_c & field); //! constructor with run-time cost (for python and debugging) template explicit FieldMap(TypedSizedFieldBase & field); //! Copy constructor FieldMap(const FieldMap & other) = default; //! Move constructor FieldMap(FieldMap && other) = default; //! Destructor virtual ~FieldMap() = default; //! Copy assignment operator FieldMap & operator=(const FieldMap & other) = delete; //! Move assignment operator FieldMap & operator=(FieldMap && other) = delete; //! give human-readable field map type virtual std::string info_string() const = 0; //! return field name inline const std::string & get_name() const; //! return my collection (for iterating) inline const FieldCollection & get_collection() const; //! member access needs to be implemented by inheriting classes // inline value_type operator[](size_t index); // inline value_type operator[](Ccoord ccord); //! check compatibility (must be called by all inheriting classes at the //! end of their constructors inline void check_compatibility(); //! convenience call to collection's size method inline size_t size() const; //! compile-time compatibility check template struct is_compatible; /** * iterates over all pixels in the `muSpectre::FieldCollection` * and dereferences to an Eigen map to the currently used field. */ template class iterator; /** * Simple iterable proxy wrapper object around a FieldMap. When * iterated over, rather than dereferencing to the reference * type of iterator, it dereferences to a tuple of the pixel, * and the reference type of iterator */ template class enumerator; TypedField_t & get_field() { return this->field; } protected: //! raw pointer to entry (for Eigen Map) inline pointer get_ptr_to_entry(size_t index); //! raw pointer to entry (for Eigen Map) inline const T * get_ptr_to_entry(size_t index) const; const FieldCollection & collection; //!< collection holding Field TypedField_t & field; //!< mapped Field - - private: }; /** * iterates over all pixels in the `muSpectre::FieldCollection` * and dereferences to an Eigen map to the currently used field. */ template template class FieldMap::iterator { static_assert(!((ConstIter == false) && (ConstField == true)), "You can't have a non-const iterator over a const " "field"); public: //! for use by enumerator using FullyTypedFieldMap_t = FullyTypedFieldMap; //! stl conformance using value_type = const_corrector_t; //! stl conformance using const_value_type = const_corrector_t; //! stl conformance using pointer = typename FullyTypedFieldMap::pointer; //! stl conformance using difference_type = std::ptrdiff_t; //! stl conformance using iterator_category = std::random_access_iterator_tag; //! cell coordinates type using Ccoord = typename FieldCollection::Ccoord; //! stl conformance using reference = typename FullyTypedFieldMap::reference; //! fully typed reference as seen by the iterator using TypedMap_t = std::conditional_t; //! Default constructor iterator() = delete; //! constructor inline iterator(TypedMap_t & fieldmap, bool begin = true); //! constructor for random access inline iterator(TypedMap_t & fieldmap, size_t index); //! Move constructor iterator(iterator && other) = default; //! Destructor virtual ~iterator() = default; //! Copy assignment operator iterator & operator=(const iterator & other) = default; //! Move assignment operator iterator & operator=(iterator && other) = default; //! pre-increment inline iterator & operator++(); //! post-increment inline iterator operator++(int); //! dereference inline value_type operator*(); //! dereference inline const_value_type operator*() const; //! member of pointer inline pointer operator->(); //! pre-decrement inline iterator & operator--(); //! post-decrement inline iterator operator--(int); //! access subscripting inline value_type operator[](difference_type diff); //! access subscripting inline const_value_type operator[](const difference_type diff) const; //! equality inline bool operator==(const iterator & other) const; //! inequality inline bool operator!=(const iterator & other) const; //! div. comparisons inline bool operator<(const iterator & other) const; //! div. comparisons inline bool operator<=(const iterator & other) const; //! div. comparisons inline bool operator>(const iterator & other) const; //! div. comparisons inline bool operator>=(const iterator & other) const; //! additions, subtractions and corresponding assignments inline iterator operator+(difference_type diff) const; //! additions, subtractions and corresponding assignments inline iterator operator-(difference_type diff) const; //! additions, subtractions and corresponding assignments inline iterator & operator+=(difference_type diff); //! additions, subtractions and corresponding assignments inline iterator & operator-=(difference_type diff); //! get pixel coordinates inline Ccoord get_ccoord() const; //! ostream operator (mainly for debugging) friend std::ostream & operator<<(std::ostream & os, const iterator & it) { if (ConstIter) { os << "const "; } os << "iterator on field '" << it.fieldmap.get_name() << "', entry " << it.index; return os; } protected: //! Copy constructor iterator(const iterator & other) = default; const FieldCollection & collection; //!< collection of the field TypedMap_t & fieldmap; //!< ref to the field itself size_t index; //!< index of currently pointed-to pixel - - private: }; /* ---------------------------------------------------------------------- */ template template class FieldMap::enumerator { public: //! fully typed reference as seen by the iterator using TypedMap_t = typename Iterator::TypedMap_t; //! Default constructor enumerator() = delete; //! constructor with field mapped enumerator(TypedMap_t & field_map) : field_map{field_map} {} /** * similar to iterators of the field map, but dereferences to a * tuple containing the cell coordinates and teh corresponding * entry */ class iterator; iterator begin() { return iterator(this->field_map); } iterator end() { return iterator(this->field_map, false); } protected: TypedMap_t & field_map; }; /* ---------------------------------------------------------------------- */ template template class FieldMap::enumerator::iterator { public: //! cell coordinates type using Ccoord = typename FieldCollection::Ccoord; //! stl conformance using value_type = std::tuple; //! stl conformance using const_value_type = std::tuple; //! stl conformance using difference_type = std::ptrdiff_t; //! stl conformance using iterator_category = std::random_access_iterator_tag; //! stl conformance using reference = std::tuple; //! Default constructor iterator() = delete; //! constructor for begin/end iterator(TypedMap_t & fieldmap, bool begin = true) : it{fieldmap, begin} {} //! constructor for random access iterator(TypedMap_t & fieldmap, size_t index) : it{fieldmap, index} {} //! constructor from iterator iterator(const SimpleIterator & it) : it{it} {} //! Copy constructor iterator(const iterator & other) = default; //! Move constructor iterator(iterator && other) = default; //! Destructor virtual ~iterator() = default; //! Copy assignment operator iterator & operator=(const iterator & other) = default; //! Move assignment operator iterator & operator=(iterator && other) = default; //! pre-increment iterator & operator++() { ++(this->it); return *this; } //! post-increment iterator operator++(int) { iterator current = *this; ++(this->it); return current; } //! dereference value_type operator*() { return value_type{it.get_ccoord(), *this->it}; } //! dereference const_value_type operator*() const { return const_value_type{it.get_ccoord(), *this->it}; } //! pre-decrement iterator & operator--() { --(this->it); return *this; } //! post-decrement iterator operator--(int) { iterator current = *this; --(this->it); return current; } //! access subscripting value_type operator[](difference_type diff) { SimpleIterator accessed{this->it + diff}; return *accessed; } //! access subscripting const_value_type operator[](const difference_type diff) const { SimpleIterator accessed{this->it + diff}; return *accessed; } //! equality bool operator==(const iterator & other) const { return this->it == other.it; } //! inequality bool operator!=(const iterator & other) const { return this->it != other.it; } //! div. comparisons bool operator<(const iterator & other) const { return this->it < other.it; } //! div. comparisons bool operator<=(const iterator & other) const { return this->it <= other.it; } //! div. comparisons bool operator>(const iterator & other) const { return this->it > other.it; } //! div. comparisons bool operator>=(const iterator & other) const { return this->it >= other.it; } //! additions, subtractions and corresponding assignments iterator operator+(difference_type diff) const { return iterator{this->it + diff}; } //! additions, subtractions and corresponding assignments iterator operator-(difference_type diff) const { return iterator{this->it - diff}; } //! additions, subtractions and corresponding assignments iterator & operator+=(difference_type diff) { this->it += diff; } //! additions, subtractions and corresponding assignments iterator & operator-=(difference_type diff) { this->it -= diff; } protected: SimpleIterator it; private: }; } // namespace internal namespace internal { /* ---------------------------------------------------------------------- */ template FieldMap::FieldMap( Field_c & field) : collection(field.get_collection()), field(static_cast(field)) { static_assert((NbComponents > 0) or (NbComponents == Eigen::Dynamic), "Only fields with more than 0 components allowed"); } /* ---------------------------------------------------------------------- */ template template FieldMap::FieldMap( TypedSizedFieldBase & field) : collection(field.get_collection()), field(static_cast(field)) { static_assert( std::is_same::value, "The field does not have the expected FieldCollection type"); static_assert(std::is_same::value, "The field does not have the expected Scalar type"); static_assert( (NbC == NbComponents), "The field does not have the expected number of components"); } /* ---------------------------------------------------------------------- */ template void FieldMap::check_compatibility() { if (typeid(T).hash_code() != this->field.get_stored_typeid().hash_code()) { std::string err{"Cannot create a Map of type '" + this->info_string() + "' for field '" + this->field.get_name() + "' of type '" + this->field.get_stored_typeid().name() + "'"}; throw FieldInterpretationError(err); } // check size compatibility if ((NbComponents != Dim_t(this->field.get_nb_components())) and (NbComponents != Eigen::Dynamic)) { throw FieldInterpretationError( "Cannot create a Map of type '" + this->info_string() + "' for field '" + this->field.get_name() + "' with " + std::to_string(this->field.get_nb_components()) + " components"); } } /* ---------------------------------------------------------------------- */ template size_t FieldMap::size() const { return this->collection.size(); } /* ---------------------------------------------------------------------- */ template template struct FieldMap::is_compatible { //! creates a more readable compile error constexpr static bool explain() { static_assert( std::is_same::value, "The field does not have the expected FieldCollection type"); static_assert(std::is_same::value, "The // field does not have the expected Scalar type"); static_assert( (TypedField_t::nb_components == NbComponents), "The field does not have the expected number of components"); // The static asserts wouldn't pass in the incompatible case, so this is // it return true; } //! evaluated compatibility constexpr static bool value{ std::is_base_of::value}; }; /* ---------------------------------------------------------------------- */ template const std::string & FieldMap::get_name() const { return this->field.get_name(); } /* ---------------------------------------------------------------------- */ template const FieldCollection & FieldMap::get_collection() const { return this->collection; } /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ // Iterator implementations //! constructor template template FieldMap::iterator< FullyTypedFieldMap, ConstIter>::iterator(TypedMap_t & fieldmap, bool begin) : collection(fieldmap.get_collection()), fieldmap(fieldmap), index(begin ? 0 : fieldmap.field.size()) {} /* ---------------------------------------------------------------------- */ //! constructor for random access template template FieldMap::iterator< FullyTypedFieldMap, ConstIter>::iterator(TypedMap_t & fieldmap, size_t index) : collection(fieldmap.collection), fieldmap(fieldmap), index(index) {} /* ---------------------------------------------------------------------- */ //! pre-increment template template typename FieldMap:: template iterator & FieldMap::iterator:: operator++() { this->index++; return *this; } /* ---------------------------------------------------------------------- */ //! post-increment template template typename FieldMap:: template iterator FieldMap::iterator:: operator++(int) { iterator current = *this; this->index++; return current; } /* ---------------------------------------------------------------------- */ //! dereference template template typename FieldMap:: template iterator::value_type FieldMap::iterator:: operator*() { return this->fieldmap.operator[](this->index); } /* ---------------------------------------------------------------------- */ //! dereference template template typename FieldMap:: template iterator::const_value_type FieldMap::iterator:: operator*() const { return this->fieldmap.operator[](this->index); } /* ---------------------------------------------------------------------- */ //! member of pointer template template typename FullyTypedFieldMap::pointer FieldMap::iterator:: operator->() { return this->fieldmap.ptr_to_val_t(this->index); } /* ---------------------------------------------------------------------- */ //! pre-decrement template template typename FieldMap:: template iterator & FieldMap::iterator:: operator--() { this->index--; return *this; } /* ---------------------------------------------------------------------- */ //! post-decrement template template typename FieldMap:: template iterator FieldMap::iterator:: operator--(int) { iterator current = *this; this->index--; return current; } /* ---------------------------------------------------------------------- */ //! Access subscripting template template typename FieldMap:: template iterator::value_type FieldMap::iterator:: operator[](difference_type diff) { return this->fieldmap[this->index + diff]; } /* ---------------------------------------------------------------------- */ //! Access subscripting template template typename FieldMap:: template iterator::const_value_type FieldMap::iterator:: operator[](const difference_type diff) const { return this->fieldmap[this->index + diff]; } /* ---------------------------------------------------------------------- */ //! equality template template bool FieldMap::iterator:: operator==(const iterator & other) const { return (this->index == other.index); } /* ---------------------------------------------------------------------- */ //! inquality template template bool FieldMap::iterator:: operator!=(const iterator & other) const { return !(*this == other); } /* ---------------------------------------------------------------------- */ //! div. comparisons template template bool FieldMap::iterator:: operator<(const iterator & other) const { return (this->index < other.index); } template template bool FieldMap::iterator:: operator<=(const iterator & other) const { return (this->index <= other.index); } template template bool FieldMap::iterator:: operator>(const iterator & other) const { return (this->index > other.index); } template template bool FieldMap::iterator:: operator>=(const iterator & other) const { return (this->index >= other.index); } /* ---------------------------------------------------------------------- */ //! additions, subtractions and corresponding assignments template template typename FieldMap:: template iterator FieldMap::iterator:: operator+(difference_type diff) const { return iterator(this->fieldmap, this->index + diff); } template template typename FieldMap:: template iterator FieldMap::iterator:: operator-(difference_type diff) const { return iterator(this->fieldmap, this->index - diff); } template template typename FieldMap:: template iterator & FieldMap::iterator:: operator+=(difference_type diff) { this->index += diff; return *this; } template template typename FieldMap:: template iterator & FieldMap::iterator:: operator-=(difference_type diff) { this->index -= diff; return *this; } /* ---------------------------------------------------------------------- */ //! get pixel coordinates template template typename FieldCollection::Ccoord FieldMap::iterator< FullyTypedFieldMap, ConstIter>::get_ccoord() const { return this->collection.get_ccoord(this->index); } ////----------------------------------------------------------------------------// // template std::ostream & operator << (std::ostream &os, // const typename FieldMap:: // template iterator & it) { // os << "iterator on field '" // << it.field.get_name() // << "', entry " << it.index; // return os; //} /* ---------------------------------------------------------------------- */ template typename FieldMap::pointer FieldMap::get_ptr_to_entry( size_t index) { return this->field.get_ptr_to_entry(std::move(index)); } /* ---------------------------------------------------------------------- */ template const T * FieldMap::get_ptr_to_entry( size_t index) const { return this->field.get_ptr_to_entry(std::move(index)); } } // namespace internal } // namespace muSpectre #endif // SRC_COMMON_FIELD_MAP_BASE_HH_ diff --git a/src/common/geometry.hh b/src/common/geometry.hh new file mode 100644 index 0000000..1a93223 --- /dev/null +++ b/src/common/geometry.hh @@ -0,0 +1,263 @@ +/** + * @file geometry.hh + * + * @author Till Junge + * + * @date 18 Apr 2018 + * + * @brief Geometric calculation helpers + * + * Copyright © 2018 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Emacs; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "common/common.hh" +#include "common/tensor_algebra.hh" +#include "common/eigen_tools.hh" + +#include +#include + +#include + +#ifndef SRC_COMMON_GEOMETRY_HH_ +#define SRC_COMMON_GEOMETRY_HH_ + +namespace muSpectre { + + /** + * The rotation matrices depend on the order in which we rotate + * around different axes. See [[ + * https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix ]] to + * find the matrices + */ + enum class RotationOrder { + Z, + XZXEuler, + XYXEuler, + YXYEuler, + YZYEuler, + ZYZEuler, + ZXZEuler, + XZYTaitBryan, + XYZTaitBryan, + YXZTaitBryan, + YZXTaitBryan, + ZYXTaitBryan, + ZXYTaitBryan + }; + + namespace internal { + + template + struct DefaultOrder { + constexpr static RotationOrder value{RotationOrder::ZXYTaitBryan}; + }; + + template <> + struct DefaultOrder { + constexpr static RotationOrder value{RotationOrder::Z}; + }; + + } // namespace internal + + template ::value> + class Rotator { + public: + static_assert(((Dim == twoD) and (Order == RotationOrder::Z)) or + ((Dim == threeD) and (Order != RotationOrder::Z)), + "In 2d, only order 'Z' makes sense. In 3d, it doesn't"); + using Angles_t = Eigen::Matrix; + using RotMat_t = Eigen::Matrix; + + //! Default constructor + Rotator() = delete; + + explicit Rotator(const Eigen::Ref & angles) + : angles{angles}, rot_mat{this->compute_rotation_matrix()} {} + + //! Copy constructor + Rotator(const Rotator & other) = default; + + //! Move constructor + Rotator(Rotator && other) = default; + + //! Destructor + virtual ~Rotator() = default; + + //! Copy assignment operator + Rotator & operator=(const Rotator & other) = default; + + //! Move assignment operator + Rotator & operator=(Rotator && other) = default; + + /** + * Applies the rotation into the frame defined by the rotation + * matrix + * + * @param input is a first-, second-, or fourth-rank tensor + * (column vector, square matrix, or T4Matrix, or a Eigen::Map of + * either of these, or an expression that evaluates into any of + * these) + */ + template + inline decltype(auto) rotate(In_t && input); + + /** + * Applies the rotation back out from the frame defined by the + * rotation matrix + * + * @param input is a first-, second-, or fourth-rank tensor + * (column vector, square matrix, or T4Matrix, or a Eigen::Map of + * either of these, or an expression that evaluates into any of + * these) + */ + template + inline decltype(auto) rotate_back(In_t && input); + + const RotMat_t & get_rot_mat() const { return rot_mat; } + + protected: + inline RotMat_t compute_rotation_matrix(); + EIGEN_MAKE_ALIGNED_OPERATOR_NEW; + Angles_t angles; + RotMat_t rot_mat; + + private: + }; + + namespace internal { + + template + struct RotationMatrixComputer {}; + + template + struct RotationMatrixComputer { + constexpr static Dim_t Dim{twoD}; + using RotMat_t = typename Rotator::RotMat_t; + using Angles_t = typename Rotator::Angles_t; + + inline static decltype(auto) + compute(const Eigen::Ref & angles) { + static_assert(Order == RotationOrder::Z, + "Two-d rotations can only be around the z axis"); + return RotMat_t(Eigen::Rotation2Dd(angles(0))); + } + }; + + template + struct RotationMatrixComputer { + constexpr static Dim_t Dim{threeD}; + using RotMat_t = typename Rotator::RotMat_t; + using Angles_t = typename Rotator::Angles_t; + + inline static decltype(auto) + compute(const Eigen::Ref & angles) { + static_assert(Order != RotationOrder::Z, + "three-d rotations cannot only be around the z axis"); + + switch (Order) { + case RotationOrder::ZXZEuler: { + return RotMat_t( + (Eigen::AngleAxisd(angles(0), Eigen::Vector3d::UnitZ()) * + Eigen::AngleAxisd(angles(1), Eigen::Vector3d::UnitX()) * + Eigen::AngleAxisd(angles(2), Eigen::Vector3d::UnitZ()))); + break; + } + case RotationOrder::ZXYTaitBryan: { + return RotMat_t( + (Eigen::AngleAxisd(angles(0), Eigen::Vector3d::UnitZ()) * + Eigen::AngleAxisd(angles(1), Eigen::Vector3d::UnitX()) * + Eigen::AngleAxisd(angles(2), Eigen::Vector3d::UnitY()))); + } + default: { throw std::runtime_error("not yet implemented."); } + } + } + }; + + } // namespace internal + + /* ---------------------------------------------------------------------- */ + template + auto Rotator::compute_rotation_matrix() -> RotMat_t { + return internal::RotationMatrixComputer::compute(this->angles); + } + + namespace internal { + + template + struct RotationHelper {}; + + /* ---------------------------------------------------------------------- */ + template <> + struct RotationHelper { + template + inline static decltype(auto) rotate(In_t && input, Rot_t && R) { + return R * input; + } + }; + + /* ---------------------------------------------------------------------- */ + template <> + struct RotationHelper { + template + inline static decltype(auto) rotate(In_t && input, Rot_t && R) { + return R * input * R.transpose(); + } + }; + + /* ---------------------------------------------------------------------- */ + template <> + struct RotationHelper { + template + inline static decltype(auto) rotate(In_t && input, Rot_t && R) { + constexpr Dim_t Dim{EigenCheck::tensor_dim::value}; + auto && rotator_forward{ + Matrices::outer_under(R.transpose(), R.transpose())}; + auto && rotator_back = Matrices::outer_under(R, R); + + // Clarification. When I return this value as an + // expression, clang segfaults or returns an uninitialised + // tensor, hence the explicit cast into a T4Mat. + return T4Mat(rotator_back * input * rotator_forward); + } + }; + } // namespace internal + + /* ---------------------------------------------------------------------- */ + template + template + auto Rotator::rotate(In_t && input) -> decltype(auto) { + constexpr Dim_t tensor_rank{EigenCheck::tensor_rank::value}; + + return internal::RotationHelper::rotate( + std::forward(input), this->rot_mat); + } + + /* ---------------------------------------------------------------------- */ + template + template + auto Rotator::rotate_back(In_t && input) -> decltype(auto) { + constexpr Dim_t tensor_rank{EigenCheck::tensor_rank::value}; + + return internal::RotationHelper::rotate( + std::forward(input), this->rot_mat.transpose()); + } + +} // namespace muSpectre + +#endif // SRC_COMMON_GEOMETRY_HH_ diff --git a/src/common/iterators.hh b/src/common/iterators.hh index dc59ed1..dec4a59 100644 --- a/src/common/iterators.hh +++ b/src/common/iterators.hh @@ -1,366 +1,367 @@ /** * @file iterators.hh * * @author Nicolas Richart * * @date creation Wed Jul 19 2017 * * @brief iterator interfaces * * Copyright (©) 2010-2011 EPFL (Ecole Polytechnique Fédérale de Lausanne) * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) * * Akantu is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * Akantu 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 Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with Akantu. If not, see . * * Above block was left intact as in akantu. µSpectre exercises the * right to redistribute and modify the code below * */ /* -------------------------------------------------------------------------- */ #include #include /* -------------------------------------------------------------------------- */ #ifndef SRC_COMMON_ITERATORS_HH_ #define SRC_COMMON_ITERATORS_HH_ namespace akantu { namespace tuple { /* ------------------------------------------------------------------------ */ namespace details { //! static for loop template struct Foreach { //! undocumented template static inline bool not_equal(Tuple && a, Tuple && b) { if (std::get(std::forward(a)) == std::get(std::forward(b))) return false; return Foreach::not_equal(std::forward(a), std::forward(b)); } }; /* ---------------------------------------------------------------------- */ //! static comparison template <> struct Foreach<0> { //! undocumented template static inline bool not_equal(Tuple && a, Tuple && b) { return std::get<0>(std::forward(a)) != std::get<0>(std::forward(b)); } }; //! eats up a bunch of arguments and returns them packed in a tuple template decltype(auto) make_tuple_no_decay(Ts &&... args) { return std::tuple(std::forward(args)...); } //! helper for static for loop template void foreach_impl(F && func, Tuple && tuple, std::index_sequence &&) { (void)std::initializer_list{ (std::forward(func)(std::get(std::forward(tuple))), 0)...}; } //! detail template decltype(auto) transform_impl(F && func, Tuple && tuple, std::index_sequence &&) { return make_tuple_no_decay( std::forward(func)(std::get(std::forward(tuple)))...); } }; // namespace details /* ------------------------------------------------------------------------ */ //! detail template bool are_not_equal(Tuple && a, Tuple && b) { return details::Foreach>::value>:: not_equal(std::forward(a), std::forward(b)); } //! detail template void foreach_(F && func, Tuple && tuple) { return details::foreach_impl( std::forward(func), std::forward(tuple), std::make_index_sequence< std::tuple_size>::value>{}); } //! detail template decltype(auto) transform(F && func, Tuple && tuple) { return details::transform_impl( std::forward(func), std::forward(tuple), std::make_index_sequence< std::tuple_size>::value>{}); } } // namespace tuple /* -------------------------------------------------------------------------- */ namespace iterators { //! iterator for emulation of python zip template class ZipIterator { private: using tuple_t = std::tuple; public: //! undocumented explicit ZipIterator(tuple_t iterators) : iterators(std::move(iterators)) {} //! undocumented decltype(auto) operator*() { return tuple::transform( [](auto && it) -> decltype(auto) { return *it; }, iterators); } //! undocumented ZipIterator & operator++() { tuple::foreach_([](auto && it) { ++it; }, iterators); return *this; } //! undocumented bool operator==(const ZipIterator & other) const { return not tuple::are_not_equal(iterators, other.iterators); } //! undocumented bool operator!=(const ZipIterator & other) const { return tuple::are_not_equal(iterators, other.iterators); } private: tuple_t iterators; }; } // namespace iterators /* -------------------------------------------------------------------------- */ //! emulates python zip() template decltype(auto) zip_iterator(std::tuple && iterators_tuple) { auto zip = iterators::ZipIterator( std::forward(iterators_tuple)); return zip; } /* -------------------------------------------------------------------------- */ namespace containers { //! helper for the emulation of python zip template class ZipContainer { using containers_t = std::tuple; public: //! undocumented explicit ZipContainer(Containers &&... containers) : containers(std::forward(containers)...) {} //! undocumented decltype(auto) begin() const { return zip_iterator( tuple::transform([](auto && c) { return c.begin(); }, std::forward(containers))); } //! undocumented decltype(auto) end() const { return zip_iterator( tuple::transform([](auto && c) { return c.end(); }, std::forward(containers))); } //! undocumented decltype(auto) begin() { return zip_iterator( tuple::transform([](auto && c) { return c.begin(); }, std::forward(containers))); } //! undocumented decltype(auto) end() { return zip_iterator( tuple::transform([](auto && c) { return c.end(); }, std::forward(containers))); } private: containers_t containers; }; } // namespace containers /* -------------------------------------------------------------------------- */ /** * emulates python's zip() */ template decltype(auto) zip(Containers &&... conts) { return containers::ZipContainer( std::forward(conts)...); } /* -------------------------------------------------------------------------- */ /* Arange */ /* -------------------------------------------------------------------------- */ namespace iterators { /** * emulates python's range iterator */ template class ArangeIterator { public: //! undocumented using value_type = T; //! undocumented using pointer = T *; //! undocumented using reference = T &; //! undocumented using iterator_category = std::input_iterator_tag; //! undocumented constexpr ArangeIterator(T value, T step) : value(value), step(step) {} //! undocumented constexpr ArangeIterator(const ArangeIterator &) = default; //! undocumented constexpr ArangeIterator & operator++() { value += step; return *this; } //! undocumented constexpr const T & operator*() const { return value; } //! undocumented constexpr bool operator==(const ArangeIterator & other) const { return (value == other.value) and (step == other.step); } //! undocumented constexpr bool operator!=(const ArangeIterator & other) const { return not operator==(other); } private: T value{0}; const T step{1}; }; } // namespace iterators namespace containers { //! helper class to generate range iterators template class ArangeContainer { public: //! undocumented using iterator = iterators::ArangeIterator; //! undocumented constexpr ArangeContainer(T start, T stop, T step = 1) : start(start), stop((stop - start) % step == 0 ? stop : start + (1 + (stop - start) / step) * step), step(step) {} //! undocumented - constexpr ArangeContainer(T stop) : ArangeContainer(0, stop, 1) {} + explicit constexpr ArangeContainer(T stop) + : ArangeContainer(0, stop, 1) {} //! undocumented constexpr T operator[](size_t i) { T val = start + i * step; assert(val < stop && "i is out of range"); return val; } //! undocumented constexpr T size() { return (stop - start) / step; } //! undocumented constexpr iterator begin() { return iterator(start, step); } //! undocumented constexpr iterator end() { return iterator(stop, step); } private: const T start{0}, stop{0}, step{1}; }; } // namespace containers /** * emulates python's range() */ template >::value>> inline decltype(auto) arange(const T & stop) { return containers::ArangeContainer(stop); } /** * emulates python's range() */ template >::value>> inline constexpr decltype(auto) arange(const T1 & start, const T2 & stop) { return containers::ArangeContainer>(start, stop); } /** * emulates python's range() */ template >::value>> inline constexpr decltype(auto) arange(const T1 & start, const T2 & stop, const T3 & step) { return containers::ArangeContainer>( start, stop, step); } /* -------------------------------------------------------------------------- */ /** * emulates python's enumerate */ template inline constexpr decltype(auto) enumerate(Container && container, size_t start_ = 0) { auto stop = std::forward(container).size(); decltype(stop) start = start_; return zip(arange(start, stop), std::forward(container)); } } // namespace akantu #endif // SRC_COMMON_ITERATORS_HH_ diff --git a/src/common/ref_array.hh b/src/common/ref_array.hh new file mode 100644 index 0000000..c9e11df --- /dev/null +++ b/src/common/ref_array.hh @@ -0,0 +1,100 @@ +/** + * file ref_array.hh + * + * @author Till Junge + * + * @date 04 Dec 2018 + * + * @brief convenience class to simulate an array of references + * + * Copyright © 2018 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GNU Emacs; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Additional permission under GNU GPL version 3 section 7 + * + * If you modify this Program, or any covered work, by linking or combining it + * with proprietary FFT implementations or numerical libraries, containing parts + * covered by the terms of those libraries' licenses, the licensors of this + * Program grant you additional permission to convey the resulting work. + */ + +#ifndef SRC_COMMON_REF_ARRAY_HH_ +#define SRC_COMMON_REF_ARRAY_HH_ + +#include +#include +#include "common/iterators.hh" + +namespace muSpectre { + namespace internal { + + template + struct TypeChecker { + constexpr static bool value{ + std::is_same>::value and + TypeChecker::value}; + }; + + template + struct TypeChecker { + constexpr static bool value{ + std::is_same>::value}; + }; + + } // namespace internal + + template + class RefArray { + public: + //! Default constructor + RefArray() = delete; + + template + explicit RefArray(Vals &... vals) : values{&vals...} { + static_assert(internal::TypeChecker::value, + "Only refs to type T allowed"); + } + + //! Copy constructor + RefArray(const RefArray & other) = default; + + //! Move constructor + RefArray(RefArray && other) = default; + + //! Destructor + virtual ~RefArray() = default; + + //! Copy assignment operator + RefArray & operator=(const RefArray & other) = default; + + //! Move assignment operator + RefArray & operator=(RefArray && other) = delete; + + T & operator[](size_t index) { return *this->values[index]; } + constexpr T & operator[](size_t index) const { + return *this->values[index]; + } + + protected: + std::array values{}; + + private: + }; + +} // namespace muSpectre + +#endif // SRC_COMMON_REF_ARRAY_HH_ diff --git a/src/common/statefield.hh b/src/common/statefield.hh index ef48163..243d908 100644 --- a/src/common/statefield.hh +++ b/src/common/statefield.hh @@ -1,659 +1,676 @@ /** * file statefield.hh * * @author Till Junge * * @date 28 Feb 2018 * * @brief A state field is an abstraction of a field that can hold * current, as well as a chosen number of previous values. This is * useful for instance for internal state variables in plastic laws, * where a current, new, or trial state is computed based on its * previous state, and at convergence, this new state gets cycled into * the old, the old into the old-1 etc. The state field abstraction * helps doing this safely (i.e. only const references to the old * states are available, while the current state can be assigned * to/modified), and efficiently (i.e., no need to copy values from * new to old, we just cycle the labels). This file implements the * state field as well as state maps using the Field, FieldCollection * and FieldMap abstractions of µSpectre * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_COMMON_STATEFIELD_HH_ #define SRC_COMMON_STATEFIELD_HH_ -#include "common/field.hh" #include "common/field_helpers.hh" -#include "common/utilities.hh" +#include "common/field.hh" +#include "common/ref_array.hh" #include #include #include namespace muSpectre { /** * Forward-declaration */ template class TypedField; /** * Base class for state fields, useful for storing polymorphic references */ template class StateFieldBase { public: //! get naming prefix const std::string & get_prefix() const { return this->prefix; } //! get a ref to the `StateField` 's field collection const FieldCollection & get_collection() const { return this->collection; } virtual ~StateFieldBase() = default; /** * returns number of old states that are stored */ size_t get_nb_memory() const { return this->nb_memory; } //! return type_id of stored type virtual const std::type_info & get_stored_typeid() const = 0; /** * cycle the fields (current becomes old, old becomes older, * oldest becomes current) */ virtual void cycle() = 0; protected: //! constructor StateFieldBase(std::string unique_prefix, const FieldCollection & collection, size_t nb_memory = 1) : prefix{unique_prefix}, nb_memory{nb_memory}, collection{collection} {} /** * the unique prefix is used as the first part of the unique name * of the subfields belonging to this state field */ std::string prefix; /** * number of old states to store, defaults to 1 */ const size_t nb_memory; //! reference to the collection this statefield belongs to const FieldCollection & collection; }; /* ---------------------------------------------------------------------- */ template class TypedStateField : public StateFieldBase { public: //! Parent class using Parent = StateFieldBase; //! Typed field using TypedField_t = TypedField; //! returns a TypedField ref to the current value of this state field virtual TypedField_t & get_current_field() = 0; //! returns a const TypedField ref to an old value of this state field virtual const TypedField_t & get_old_field(size_t nb_steps_ago = 1) const = 0; + //! returns a `StateField` reference if `other is a compatible state field + inline static TypedStateField & check_ref(Parent & other) { + // the following triggers and exception if the fields are incompatible + if (typeid(T).hash_code() != other.get_stored_typeid().hash_code()) { + std::stringstream err_str{}; + err_str << "Cannot create a rerference of requested type " + << "for statefield '" << other.get_prefix() << "' of type '" + << other.get_stored_typeid().name() << "'"; + throw std::runtime_error(err_str.str()); + } + return static_cast(other); + } + //! return type_id of stored type const std::type_info & get_stored_typeid() const final { return typeid(T); }; virtual ~TypedStateField() = default; protected: //! constructor TypedStateField(const std::string & unique_prefix, const FieldCollection & collection, size_t nb_memory) : Parent{unique_prefix, collection, nb_memory} {} }; /* ---------------------------------------------------------------------- */ template class TypedSizedStateField : public TypedStateField { public: //! Parent class using Parent = TypedStateField; //! the current (historically accurate) ordering of the fields using index_t = std::array; //! get the current ordering of the fields inline const index_t & get_indices() const { return this->indices; } //! destructor virtual ~TypedSizedStateField() = default; protected: //! constructor TypedSizedStateField(std::string unique_prefix, const FieldCollection & collection, index_t indices) : Parent{unique_prefix, collection, nb_memory}, indices{indices} {}; index_t indices; ///< these are cycled through }; //! early declaration template class StateFieldMap; namespace internal { template inline decltype(auto) build_fields_helper(std::string prefix, typename Field::Base::collection_t & collection, std::index_sequence) { auto get_field{[&prefix, &collection](size_t i) -> Field & { std::stringstream name_stream{}; name_stream << prefix << ", sub_field index " << i; return make_field(name_stream.str(), collection); }}; - return std::tie(get_field(I)...); + return RefArray(get_field(I)...); } /* ---------------------------------------------------------------------- */ template inline decltype(auto) build_indices(std::index_sequence) { return std::array{(size - I) % size...}; } } // namespace internal /** * A statefield is an abstraction around a Field that can hold a * current and `nb_memory` previous values. There are useful for * history variables, for instance. */ template class StateField : public TypedSizedStateField { public: //! the underlying field's collection type using FieldCollection_t = typename Field_t::Base::collection_t; //! base type for fields using Scalar = typename Field_t::Scalar; //! Base class for all state fields of same memory using Base = TypedSizedStateField; /** * storage of field refs (can't be a `std::array`, because arrays * of refs are explicitely forbidden */ - using Fields_t = tuple_array; + using Fields_t = RefArray; //! Typed field using TypedField_t = TypedField; //! Default constructor StateField() = delete; //! Copy constructor StateField(const StateField & other) = delete; //! Move constructor StateField(StateField && other) = delete; //! Destructor virtual ~StateField() = default; //! Copy assignment operator StateField & operator=(const StateField & other) = delete; //! Move assignment operator StateField & operator=(StateField && other) = delete; //! get (modifiable) current field inline Field_t & current() { return this->fields[this->indices[0]]; } //! get (constant) previous field template inline const Field_t & old() { static_assert(nb_steps_ago <= nb_memory, "you can't go that far inte the past"); static_assert(nb_steps_ago > 0, "Did you mean to call current()?"); return this->fields[this->indices.at(nb_steps_ago)]; } //! returns a TypedField ref to the current value of this state field TypedField_t & get_current_field() final { return this->current(); } //! returns a const TypedField ref to an old value of this state field const TypedField_t & get_old_field(size_t nb_steps_ago = 1) const final { return this->fields[this->indices.at(nb_steps_ago)]; } //! factory function template friend StateFieldType & make_statefield(const std::string & unique_prefix, CollectionType & collection); //! returns a `StateField` reference if `other is a compatible state field inline static StateField & check_ref(Base & other) { // the following triggers and exception if the fields are incompatible Field_t::check_ref(other.fields[0]); return static_cast(other); } //! returns a const `StateField` reference if `other` is a compatible state //! field inline static const StateField & check_ref(const Base & other) { // the following triggers and exception if the fields are incompatible Field_t::check_ref(other.fields[0]); return static_cast(other); } //! get a ref to the `StateField` 's fields Fields_t & get_fields() { return this->fields; } /** * Pure convenience functions to get a MatrixFieldMap of * appropriate dimensions mapped to this field. You can also * create other types of maps, as long as they have the right * fundamental type (T), the correct size (nbComponents), and * memory (nb_memory). */ inline decltype(auto) get_map() { - using FieldMap = decltype(std::get<0>(this->fields).get_map()); + using FieldMap = decltype(this->fields[0].get_map()); return StateFieldMap(*this); } /** * Pure convenience functions to get a MatrixFieldMap of * appropriate dimensions mapped to this field. You can also * create other types of maps, as long as they have the right * fundamental type (T), the correct size (nbComponents), and * memory (nb_memory). */ inline decltype(auto) get_const_map() { - using FieldMap = decltype(std::get<0>(this->fields).get_const_map()); + using FieldMap = decltype(this->fields[0].get_const_map()); return StateFieldMap(*this); } /** * cycle the fields (current becomes old, old becomes older, * oldest becomes current) */ inline void cycle() final { for (auto & val : this->indices) { val = (val + 1) % (nb_memory + 1); } } protected: /** * Constructor. @param unique_prefix is used to create the names * of the fields that this abstraction creates in the background * @param collection is the field collection in which the * subfields will be stored */ inline StateField(const std::string & unique_prefix, FieldCollection_t & collection) : Base{unique_prefix, collection, internal::build_indices( std::make_index_sequence{})}, fields{internal::build_fields_helper( unique_prefix, collection, std::make_index_sequence{})} {} Fields_t fields; //!< container for the states private: }; namespace internal { template inline decltype(auto) build_maps_helper(Fields & fields, std::index_sequence) { - return std::array{FieldMap(std::get(fields))...}; + return std::array{FieldMap(fields[I])...}; } } // namespace internal /* ---------------------------------------------------------------------- */ template inline StateFieldType & make_statefield(const std::string & unique_prefix, CollectionType & collection) { std::unique_ptr ptr{ new StateFieldType(unique_prefix, collection)}; auto & retref{*ptr}; collection.register_statefield(std::move(ptr)); return retref; } /** * extends the StateField <-> Field equivalence to StateFieldMap <-> FieldMap */ template class StateFieldMap { public: /** * iterates over all pixels in the `muSpectre::FieldCollection` and * dereferences to a proxy giving access to the appropriate iterates * of the underlying `FieldMap` type. */ class iterator; //! stl conformance using reference = typename iterator::reference; //! stl conformance using value_type = typename iterator::value_type; //! stl conformance using size_type = typename iterator::size_type; //! field collection type where this state field can be stored using FieldCollection_t = typename FieldMap::Field::collection_t; //! Fundamental type stored using Scalar = typename FieldMap::Scalar; //! base class (must be at least sized) using TypedSizedStateField_t = TypedSizedStateField; //! for traits access using FieldMap_t = FieldMap; //! for traits access using ConstFieldMap_t = typename FieldMap::ConstMap; //! Default constructor StateFieldMap() = delete; //! constructor using a StateField template explicit StateFieldMap(StateField & statefield) : collection{statefield.get_collection()}, statefield{statefield}, maps{internal::build_maps_helper( statefield.get_fields(), std::make_index_sequence{})}, const_maps{ internal::build_maps_helper( statefield.get_fields(), std::make_index_sequence{})} { static_assert(std::is_base_of::value, "Not the right type of StateField ref"); } //! Copy constructor StateFieldMap(const StateFieldMap & other) = delete; //! Move constructor StateFieldMap(StateFieldMap && other) = default; //! Destructor virtual ~StateFieldMap() = default; //! Copy assignment operator StateFieldMap & operator=(const StateFieldMap & other) = delete; //! Move assignment operator StateFieldMap & operator=(StateFieldMap && other) = delete; //! access the wrapper to a given pixel directly value_type operator[](size_type index) { return *iterator(*this, index); } /** * return a ref to the current field map. useful for instance for * initialisations of `StateField` instances */ FieldMap & current() { return this->maps[this->statefield.get_indices()[0]]; } //! stl conformance iterator begin() { return iterator(*this, 0); } //! stl conformance iterator end() { return iterator(*this, this->collection.size()); } protected: const FieldCollection_t & collection; //!< collection holding the field TypedSizedStateField_t & statefield; //!< ref to the field itself std::array maps; //!< refs to the addressable maps; //! const refs to the addressable maps; std::array const_maps; private: }; /** * Iterator class used by the `StateFieldMap` */ template class StateFieldMap::iterator { public: class StateWrapper; using Ccoord = typename FieldMap::Ccoord; //!< cell coordinates type using value_type = StateWrapper; //!< stl conformance using const_value_type = value_type; //!< stl conformance using pointer_type = value_type *; //!< stl conformance using difference_type = std::ptrdiff_t; //!< stl conformance using size_type = size_t; //!< stl conformance using iterator_category = std::random_access_iterator_tag; //!< stl conformance using reference = StateWrapper; //!< stl conformance //! Default constructor iterator() = delete; //! constructor iterator(StateFieldMap & map, size_t index = 0) : index{index}, map{map} {}; //! Copy constructor iterator(const iterator & other) = default; //! Move constructor iterator(iterator && other) = default; //! Destructor virtual ~iterator() = default; //! Copy assignment operator iterator & operator=(const iterator & other) = default; //! Move assignment operator iterator & operator=(iterator && other) = default; //! pre-increment inline iterator & operator++() { this->index++; return *this; } //! post-increment inline iterator operator++(int) { iterator curr{*this}; this->index++; return curr; } //! dereference inline value_type operator*() { return value_type(*this); } //! pre-decrement inline iterator & operator--() { this->index--; return *this; } //! post-decrement inline iterator operator--(int) { iterator curr{*this}; this->index--; return curr; } //! access subscripting inline value_type operator[](difference_type diff) { return value_type{iterator{this->map, this->index + diff}}; } //! equality inline bool operator==(const iterator & other) const { return this->index == other.index; } //! inequality inline bool operator!=(const iterator & other) const { return this->index != other.index; } //! div. comparisons inline bool operator<(const iterator & other) const { return this->index < other.index; } //! div. comparisons inline bool operator<=(const iterator & other) const { return this->index <= other.index; } //! div. comparisons inline bool operator>(const iterator & other) const { return this->index > other.index; } //! div. comparisons inline bool operator>=(const iterator & other) const { return this->index >= other.index; } //! additions, subtractions and corresponding assignments inline iterator operator+(difference_type diff) const { return iterator{this->map, this - index + diff}; } //! additions, subtractions and corresponding assignments inline iterator operator-(difference_type diff) const { return iterator{this->map, this - index - diff}; } //! additions, subtractions and corresponding assignments inline iterator & operator+=(difference_type diff) { this->index += diff; return *this; } //! additions, subtractions and corresponding assignments inline iterator & operator-=(difference_type diff) { this->index -= diff; return *this; } //! get pixel coordinates inline Ccoord get_ccoord() const { return this->map.collection.get_ccoord(this->index); } //! access the index inline const size_t & get_index() const { return this->index; } protected: size_t index; //!< current pixel this iterator refers to StateFieldMap & map; //!< map over with `this` iterates - - private: }; namespace internal { //! FieldMap is an `Eigen::Map` or `Eigen::TensorMap` here - template - inline decltype(auto) build_old_vals_helper(iterator & it, maps_t & maps, - indices_t & indices, - std::index_sequence) { - return tuple_array( - std::forward_as_tuple(maps[indices[I + 1]][it.get_index()]...)); + template + inline Array build_old_vals_helper(iterator & it, maps_t & maps, + indices_t & indices, + std::index_sequence) { + return Array{maps[indices[I + 1]][it.get_index()]...}; } - template - inline decltype(auto) build_old_vals(iterator & it, maps_t & maps, - indices_t & indices) { - return tuple_array{build_old_vals_helper( - it, maps, indices, std::make_index_sequence{})}; + inline Array build_old_vals(iterator & it, maps_t & maps, + indices_t & indices) { + return build_old_vals_helper(it, maps, indices, + std::make_index_sequence{}); } } // namespace internal /** * Light-weight resource-handle representing the current and old * values of a field at a given pixel identified by an iterator * pointing to it */ template class StateFieldMap::iterator::StateWrapper { public: //! short-hand using iterator = typename StateFieldMap::iterator; //! short-hand using Ccoord = typename iterator::Ccoord; //! short-hand using Map = typename FieldMap::reference; //! short-hand using ConstMap = typename FieldMap::const_reference; + /** + * storage type differs depending on whether Map is a Reference type (in the + * C++ sense) or not, because arrays of references are forbidden + */ + using Array_t = std::conditional_t< + std::is_reference::value, + RefArray, nb_memory>, + std::array>; + //! Default constructor StateWrapper() = delete; //! Copy constructor StateWrapper(const StateWrapper & other) = default; //! Move constructor StateWrapper(StateWrapper && other) = default; //! construct with `StateFieldMap::iterator` StateWrapper(iterator & it) : it{it}, current_val{ it.map.maps[it.map.statefield.get_indices()[0]][it.index]}, - old_vals(internal::build_old_vals( + old_vals(internal::build_old_vals( it, it.map.const_maps, it.map.statefield.get_indices())) {} //! Destructor virtual ~StateWrapper() = default; //! Copy assignment operator StateWrapper & operator=(const StateWrapper & other) = default; //! Move assignment operator StateWrapper & operator=(StateWrapper && other) = default; //! returns reference to the currectly mapped value inline Map & current() { return this->current_val; } //! recurnts reference the the value that was current `nb_steps_ago` ago template inline const ConstMap & old() const { static_assert(nb_steps_ago <= nb_memory, "You have not stored that time step"); static_assert(nb_steps_ago > 0, "Did you mean to access the current value? If so, use " "current()"); - return std::get(this->old_vals); + return this->old_vals[nb_steps_ago - 1]; } //! read the coordinates of the current pixel inline Ccoord get_ccoord() const { return this->it.get_ccoord(); } protected: - iterator & it; //!< ref to the iterator that dereferences to `this` - Map current_val; //!< current value - tuple_array old_vals; //!< all stored old values - - private: + iterator & it; //!< ref to the iterator that dereferences to `this` + Map current_val; //!< current value + Array_t old_vals; //!< all stored old values }; } // namespace muSpectre #endif // SRC_COMMON_STATEFIELD_HH_ diff --git a/src/common/tensor_algebra.hh b/src/common/tensor_algebra.hh index 5d6db26..cf12935 100644 --- a/src/common/tensor_algebra.hh +++ b/src/common/tensor_algebra.hh @@ -1,287 +1,380 @@ /** * @file tensor_algebra.hh * * @author Till Junge * * @date 05 Nov 2017 * * @brief collection of compile-time quantities and algrebraic functions for * tensor operations * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_COMMON_TENSOR_ALGEBRA_HH_ #define SRC_COMMON_TENSOR_ALGEBRA_HH_ #include "common/T4_map_proxy.hh" #include "common/common.hh" #include "common/eigen_tools.hh" #include #include #include namespace muSpectre { namespace Tensors { //! second-order tensor representation template using Tens2_t = Eigen::TensorFixedSize>; //! fourth-order tensor representation template using Tens4_t = Eigen::TensorFixedSize>; //----------------------------------------------------------------------------// //! compile-time second-order identity template constexpr inline Tens2_t I2() { Tens2_t T; using Mat_t = Eigen::Matrix; Eigen::Map(&T(0, 0)) = Mat_t::Identity(); return T; } /* ---------------------------------------------------------------------- */ //! Check whether a given expression represents a Tensor specified order template struct is_tensor { //! evaluated test constexpr static bool value = (std::is_convertible>::value || std::is_convertible>::value || std::is_convertible>::value); }; /* ---------------------------------------------------------------------- */ /** compile-time outer tensor product as defined by Curnier * R_ijkl = A_ij.B_klxx * 0123 01 23 */ template constexpr inline decltype(auto) outer(T1 && A, T2 && B) { // Just make sure that the right type of parameters have been given constexpr Dim_t order{2}; static_assert(is_tensor::value, "T1 needs to be convertible to a second order Tensor"); static_assert(is_tensor::value, "T2 needs to be convertible to a second order Tensor"); // actual function std::array, 0> dims{}; return A.contract(B, dims); } /* ---------------------------------------------------------------------- */ /** compile-time underlined outer tensor product as defined by Curnier * R_ijkl = A_ik.B_jlxx * 0123 02 13 * 0213 01 23 <- this defines the shuffle order */ template constexpr inline decltype(auto) outer_under(T1 && A, T2 && B) { constexpr size_t order{4}; return outer(A, B).shuffle(std::array{{0, 2, 1, 3}}); } /* ---------------------------------------------------------------------- */ /** compile-time overlined outer tensor product as defined by Curnier * R_ijkl = A_il.B_jkxx * 0123 03 12 * 0231 01 23 <- this defines the shuffle order */ template constexpr inline decltype(auto) outer_over(T1 && A, T2 && B) { constexpr size_t order{4}; return outer(A, B).shuffle(std::array{{0, 2, 3, 1}}); } //! compile-time fourth-order symmetrising identity template constexpr inline Tens4_t I4S() { auto I = I2(); return 0.5 * (outer_under(I, I) + outer_over(I, I)); } } // namespace Tensors namespace Matrices { //! second-order tensor representation template using Tens2_t = Eigen::Matrix; //! fourth-order tensor representation template using Tens4_t = T4Mat; //----------------------------------------------------------------------------// //! compile-time second-order identity template constexpr inline Tens2_t I2() { return Tens2_t::Identity(); } /* ---------------------------------------------------------------------- */ /** compile-time outer tensor product as defined by Curnier * R_ijkl = A_ij.B_klxx * 0123 01 23 */ template constexpr inline decltype(auto) outer(T1 && A, T2 && B) { // Just make sure that the right type of parameters have been given constexpr Dim_t dim{EigenCheck::tensor_dim::value}; static_assert((dim == EigenCheck::tensor_dim::value), "A and B do not have the same dimension"); Tens4_t product; for (Dim_t i = 0; i < dim; ++i) { for (Dim_t j = 0; j < dim; ++j) { for (Dim_t k = 0; k < dim; ++k) { for (Dim_t l = 0; l < dim; ++l) { get(product, i, j, k, l) = A(i, j) * B(k, l); } } } } return product; } /* ---------------------------------------------------------------------- */ /** compile-time underlined outer tensor product as defined by Curnier * R_ijkl = A_ik.B_jlxx * 0123 02 13 * 0213 01 23 <- this defines the shuffle order */ template constexpr inline decltype(auto) outer_under(T1 && A, T2 && B) { // Just make sure that the right type of parameters have been given constexpr Dim_t dim{EigenCheck::tensor_dim::value}; static_assert((dim == EigenCheck::tensor_dim::value), "A and B do not have the same dimension"); Tens4_t product; for (Dim_t i = 0; i < dim; ++i) { for (Dim_t j = 0; j < dim; ++j) { for (Dim_t k = 0; k < dim; ++k) { for (Dim_t l = 0; l < dim; ++l) { get(product, i, j, k, l) = A(i, k) * B(j, l); } } } } return product; } /* ---------------------------------------------------------------------- */ /** compile-time overlined outer tensor product as defined by Curnier * R_ijkl = A_il.B_jkxx * 0123 03 12 * 0231 01 23 <- this defines the shuffle order */ template constexpr inline decltype(auto) outer_over(T1 && A, T2 && B) { // Just make sure that the right type of parameters have been given constexpr Dim_t dim{EigenCheck::tensor_dim::value}; static_assert((dim == EigenCheck::tensor_dim::value), "A and B do not have the same dimension"); Tens4_t product; for (Dim_t i = 0; i < dim; ++i) { for (Dim_t j = 0; j < dim; ++j) { for (Dim_t k = 0; k < dim; ++k) { for (Dim_t l = 0; l < dim; ++l) { get(product, i, j, k, l) = A(i, l) * B(j, k); } } } } return product; } /** * Standart tensor multiplication */ template constexpr inline decltype(auto) tensmult(const Eigen::MatrixBase & A, const Eigen::MatrixBase & B) { constexpr Dim_t dim{T2::RowsAtCompileTime}; static_assert(dim == T2::ColsAtCompileTime, "B is not square"); static_assert(dim != Eigen::Dynamic, "B not statically sized"); static_assert(dim * dim == T4::RowsAtCompileTime, "A and B not compatible"); static_assert(T4::RowsAtCompileTime == T4::ColsAtCompileTime, "A is not square"); Tens2_t result; result.setZero(); for (Dim_t i = 0; i < dim; ++i) { for (Dim_t j = 0; j < dim; ++j) { for (Dim_t k = 0; k < dim; ++k) { for (Dim_t l = 0; l < dim; ++l) { result(i, j) += get(A, i, j, k, l) * B(k, l); } } } } return result; } //! compile-time fourth-order tracer template constexpr inline Tens4_t Itrac() { auto I = I2(); return outer(I, I); } //! compile-time fourth-order identity template constexpr inline Tens4_t Iiden() { auto I = I2(); return outer_under(I, I); } //! compile-time fourth-order transposer template constexpr inline Tens4_t Itrns() { auto I = I2(); return outer_over(I, I); } //! compile-time fourth-order symmetriser template constexpr inline Tens4_t Isymm() { auto I = I2(); return 0.5 * (outer_under(I, I) + outer_over(I, I)); } + namespace internal { + + /* ---------------------------------------------------------------------- + */ + template + struct Dotter {}; + + /* ---------------------------------------------------------------------- + */ + template + struct Dotter { + template + static constexpr decltype(auto) dot(T1 && t1, T2 && t2) { + using T4_t = T4Mat::Scalar, Dim>; + T4_t ret_val{T4_t::Zero()}; + for (Int i = 0; i < Dim; ++i) { + for (Int a = 0; a < Dim; ++a) { + for (Int j = 0; j < Dim; ++j) { + for (Int k = 0; k < Dim; ++k) { + for (Int l = 0; l < Dim; ++l) { + get(ret_val, i, j, k, l) += t1(i, a) * get(t2, a, j, k, l); + } + } + } + } + } + return ret_val; + } + }; + + /* ---------------------------------------------------------------------- + */ + template + struct Dotter { + template + static constexpr decltype(auto) dot(T4 && t4, T2 && t2) { + using T4_t = T4Mat::Scalar, Dim>; + T4_t ret_val{T4_t::Zero()}; + for (Int i = 0; i < Dim; ++i) { + for (Int j = 0; j < Dim; ++j) { + for (Int k = 0; k < Dim; ++k) { + for (Int a = 0; a < Dim; ++a) { + for (Int l = 0; l < Dim; ++l) { + get(ret_val, i, j, k, l) += get(t4, i, j, k, a) * t2(a, l); + } + } + } + } + } + return ret_val; + } + }; + + /* ---------------------------------------------------------------------- + */ + template + struct Dotter { + template + static constexpr decltype(auto) ddot(T1 && t1, T2 && t2) { + return t1 * t2; + } + }; + + /* ---------------------------------------------------------------------- + */ + template + struct Dotter { + template + static constexpr decltype(auto) ddot(T1 && t1, T2 && t2) { + return (t1 * t2.transpose()).trace(); + } + }; + + } // namespace internal + + /* ---------------------------------------------------------------------- */ + template + decltype(auto) dot(T1 && t1, T2 && t2) { + constexpr Dim_t rank1{EigenCheck::tensor_rank::value}; + constexpr Dim_t rank2{EigenCheck::tensor_rank::value}; + return internal::Dotter::dot(std::forward(t1), + std::forward(t2)); + } + + /* ---------------------------------------------------------------------- */ + template + decltype(auto) ddot(T1 && t1, T2 && t2) { + constexpr Dim_t rank1{EigenCheck::tensor_rank::value}; + constexpr Dim_t rank2{EigenCheck::tensor_rank::value}; + return internal::Dotter::ddot(std::forward(t1), + std::forward(t2)); + } + } // namespace Matrices } // namespace muSpectre #endif // SRC_COMMON_TENSOR_ALGEBRA_HH_ diff --git a/src/common/utilities.hh b/src/common/utilities.hh index ecad671..57ad212 100644 --- a/src/common/utilities.hh +++ b/src/common/utilities.hh @@ -1,310 +1,185 @@ /** * @file utilities.hh * * @author Till Junge * * @date 17 Nov 2017 * * @brief additions to the standard name space to anticipate C++17 features * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_COMMON_UTILITIES_HH_ #define SRC_COMMON_UTILITIES_HH_ #include #ifdef NO_EXPERIMENTAL #include #else #include #endif namespace std_replacement { namespace detail { template struct is_reference_wrapper : std::false_type {}; template struct is_reference_wrapper> : std::true_type {}; //! from cppreference template auto INVOKE(T Base::*pmf, Derived && ref, Args &&... args) noexcept(noexcept( (std::forward(ref).*pmf)(std::forward(args)...))) -> std::enable_if_t< std::is_function::value && std::is_base_of>::value, decltype((std::forward(ref).* pmf)(std::forward(args)...))> { return (std::forward(ref).*pmf)(std::forward(args)...); } //! from cppreference template auto INVOKE(T Base::*pmf, RefWrap && ref, Args &&... args) noexcept( noexcept((ref.get().*pmf)(std::forward(args)...))) -> std::enable_if_t< std::is_function::value && is_reference_wrapper>::value, decltype((ref.get().*pmf)(std::forward(args)...))> { return (ref.get().*pmf)(std::forward(args)...); } //! from cppreference template auto INVOKE(T Base::*pmf, Pointer && ptr, Args &&... args) noexcept(noexcept( ((*std::forward(ptr)).*pmf)(std::forward(args)...))) -> std::enable_if_t< std::is_function::value && !is_reference_wrapper>::value && !std::is_base_of>::value, decltype(((*std::forward(ptr)).* pmf)(std::forward(args)...))> { return ((*std::forward(ptr)).*pmf)(std::forward(args)...); } //! from cppreference template auto INVOKE(T Base::*pmd, Derived && ref) noexcept(noexcept(std::forward(ref).*pmd)) -> std::enable_if_t< !std::is_function::value && std::is_base_of>::value, decltype(std::forward(ref).*pmd)> { return std::forward(ref).*pmd; } //! from cppreference template auto INVOKE(T Base::*pmd, RefWrap && ref) noexcept(noexcept(ref.get().*pmd)) -> std::enable_if_t< !std::is_function::value && is_reference_wrapper>::value, decltype(ref.get().*pmd)> { return ref.get().*pmd; } //! from cppreference template auto INVOKE(T Base::*pmd, Pointer && ptr) noexcept( noexcept((*std::forward(ptr)).*pmd)) -> std::enable_if_t< !std::is_function::value && !is_reference_wrapper>::value && !std::is_base_of>::value, decltype((*std::forward(ptr)).*pmd)> { return (*std::forward(ptr)).*pmd; } //! from cppreference template auto INVOKE(F && f, Args &&... args) noexcept( noexcept(std::forward(f)(std::forward(args)...))) -> std::enable_if_t< !std::is_member_pointer>::value, decltype(std::forward(f)(std::forward(args)...))> { return std::forward(f)(std::forward(args)...); } } // namespace detail //! from cppreference template auto invoke(F && f, ArgTypes &&... args) // exception specification for QoI noexcept(noexcept(detail::INVOKE(std::forward(f), std::forward(args)...))) -> decltype(detail::INVOKE(std::forward(f), std::forward(args)...)) { return detail::INVOKE(std::forward(f), std::forward(args)...); } namespace detail { //! from cppreference template constexpr decltype(auto) apply_impl(F && f, Tuple && t, std::index_sequence) { return std_replacement::invoke(std::forward(f), std::get(std::forward(t))...); } } // namespace detail //! from cppreference template constexpr decltype(auto) apply(F && f, Tuple && t) { return detail::apply_impl( std::forward(f), std::forward(t), std::make_index_sequence< std::tuple_size>::value>{}); } } // namespace std_replacement namespace muSpectre { - namespace internal { - - /** - * helper struct template to compute the type of a tuple with a - * given number of entries of the same type - */ - template - struct tuple_array_helper { - //! underlying tuple - using type = typename tuple_array_helper::type; - }; - - /** - * helper struct template to compute the type of a tuple with a - * given number of entries of the same type - */ - template - struct tuple_array_helper<0, T, tail...> { - //! underlying tuple - using type = std::tuple; - }; - - /** - * helper struct for runtime index access to - * tuples. RecursionLevel indicates how much more we can recurse - * down - */ - template - struct Accessor { - using Stored_t = typename TupArr::Stored_t; - - inline static Stored_t get(const size_t & index, TupArr & container) { - if (index == Index) { - return std::get(container); - } else { - return Accessor::get( - index, container); - } - } - inline static const Stored_t get(const size_t & index, - const TupArr & container) { - if (index == Index) { - return std::get(container); - } else { - return Accessor::get( - index, container); - } - } - }; - - /** - * specialisation for recursion end - */ - template - struct Accessor { - using Stored_t = typename TupArr::Stored_t; - - inline static Stored_t get(const size_t & index, TupArr & container) { - if (index == Index) { - return std::get(container); - } else { - std::stringstream err{}; - err << "Index " << index << "is out of range."; - throw std::runtime_error(err.str()); - } - } - - inline static const Stored_t get(const size_t & index, - const TupArr & container) { - if (index == Index) { - return std::get(container); - } else { - std::stringstream err{}; - err << "Index " << index << "is out of range."; - throw std::runtime_error(err.str()); - } - } - }; - - /** - * helper struct that provides the tuple_array. - */ - template - struct tuple_array_provider { - //! tuple type that can be used (almost) like an `std::array` - class type : public tuple_array_helper::type { - public: - //! short-hand - using Parent = typename tuple_array_helper::type; - using Stored_t = T; - constexpr static size_t Size{size}; - - //! constructor - explicit inline type(Parent && parent) : Parent{parent} {} - - //! element access - T operator[](const size_t & index) { - return Accessor::get(index, *this); - } - - //! element access - const T operator[](const size_t & index) const { - return Accessor::get(index, *this); - } - - protected: - }; - }; - } // namespace internal - - /** - * This is a convenience structure to create a tuple of `nb_elem` - * entries of type `T`. It is named tuple_array, because it is - * somewhat similar to an `std::array`. The reason for - * this structure is that the `std::array` is not allowed by the - * standard to store references (8.3.2 References, paragraph 5: - * "There shall be no references to references, no arrays of - * references, and no pointers to references.") use this, if you - * want to have a statically known number of references to store, - * and you wish to do so efficiently. - */ - template - using tuple_array = typename internal::tuple_array_provider::type; - using std_replacement::apply; /** * emulation `std::optional` (a C++17 feature) */ template #ifdef NO_EXPERIMENTAL using optional = typename boost::optional; #else using optional = typename std::experimental::optional; #endif } // namespace muSpectre #endif // SRC_COMMON_UTILITIES_HH_ diff --git a/src/fft/fftw_engine.hh b/src/fft/fftw_engine.hh index 91cb2b8..e2d482e 100644 --- a/src/fft/fftw_engine.hh +++ b/src/fft/fftw_engine.hh @@ -1,98 +1,96 @@ /** * @file fftw_engine.hh * * @author Till Junge * * @date 03 Dec 2017 * * @brief FFT engine using FFTW * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_FFT_FFTW_ENGINE_HH_ #define SRC_FFT_FFTW_ENGINE_HH_ #include "fft/fft_engine_base.hh" #include namespace muSpectre { /** * implements the `muSpectre::FftEngine_Base` interface using the * FFTW library */ template class FFTWEngine : public FFTEngineBase { public: using Parent = FFTEngineBase; //!< base class using Ccoord = typename Parent::Ccoord; //!< cell coordinates type //! field for Fourier transform of second-order tensor using Workspace_t = typename Parent::Workspace_t; //! real-valued second-order tensor using Field_t = typename Parent::Field_t; //! Default constructor FFTWEngine() = delete; //! Constructor with cell resolutions FFTWEngine(Ccoord resolutions, Dim_t nb_components, Communicator comm = Communicator()); //! Copy constructor FFTWEngine(const FFTWEngine & other) = delete; //! Move constructor FFTWEngine(FFTWEngine && other) = default; //! Destructor virtual ~FFTWEngine() noexcept; //! Copy assignment operator FFTWEngine & operator=(const FFTWEngine & other) = delete; //! Move assignment operator FFTWEngine & operator=(FFTWEngine && other) = default; // compute the plan, etc void initialise(FFT_PlanFlags plan_flags) override; //! forward transform Workspace_t & fft(Field_t & field) override; //! inverse transform void ifft(Field_t & field) const override; protected: fftw_plan plan_fft{}; //!< holds the plan for forward fourier transform fftw_plan plan_ifft{}; //!< holds the plan for inverse fourier transform bool initialised{false}; //!< to prevent double initialisation - - private: }; } // namespace muSpectre #endif // SRC_FFT_FFTW_ENGINE_HH_ diff --git a/src/fft/fftwmpi_engine.hh b/src/fft/fftwmpi_engine.hh index 14a2256..aecb4cd 100644 --- a/src/fft/fftwmpi_engine.hh +++ b/src/fft/fftwmpi_engine.hh @@ -1,104 +1,102 @@ /** * @file fftwmpi_engine.hh * * @author Lars Pastewka * * @date 06 Mar 2017 * * @brief FFT engine using MPI-parallel FFTW * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_FFT_FFTWMPI_ENGINE_HH_ #define SRC_FFT_FFTWMPI_ENGINE_HH_ #include "fft/fft_engine_base.hh" #include namespace muSpectre { /** * implements the `muSpectre::FFTEngineBase` interface using the * FFTW library */ template class FFTWMPIEngine : public FFTEngineBase { public: using Parent = FFTEngineBase; //!< base class using Ccoord = typename Parent::Ccoord; //!< cell coordinates type //! field for Fourier transform of second-order tensor using Workspace_t = typename Parent::Workspace_t; //! real-valued second-order tensor using Field_t = typename Parent::Field_t; //! Default constructor FFTWMPIEngine() = delete; //! Constructor with system resolutions FFTWMPIEngine(Ccoord resolutions, Dim_t nb_components, Communicator comm = Communicator()); //! Copy constructor FFTWMPIEngine(const FFTWMPIEngine & other) = delete; //! Move constructor FFTWMPIEngine(FFTWMPIEngine && other) = default; //! Destructor virtual ~FFTWMPIEngine() noexcept; //! Copy assignment operator FFTWMPIEngine & operator=(const FFTWMPIEngine & other) = delete; //! Move assignment operator FFTWMPIEngine & operator=(FFTWMPIEngine && other) = default; // compute the plan, etc void initialise(FFT_PlanFlags plan_flags) override; //! forward transform Workspace_t & fft(Field_t & field) override; //! inverse transform void ifft(Field_t & field) const override; protected: static int nb_engines; //!< number of times this engine has been instatiated fftw_plan plan_fft{}; //!< holds the plan for forward fourier transform fftw_plan plan_ifft{}; //!< holds the plan for inverse fourier transform ptrdiff_t workspace_size{}; //!< size of workspace buffer returned by planner Real * real_workspace{}; //!< temporary real workspace that is correctly //!< padded bool initialised{false}; //!< to prevent double initialisation - - private: }; } // namespace muSpectre #endif // SRC_FFT_FFTWMPI_ENGINE_HH_ diff --git a/src/fft/pfft_engine.hh b/src/fft/pfft_engine.hh index d3fdc68..d893469 100644 --- a/src/fft/pfft_engine.hh +++ b/src/fft/pfft_engine.hh @@ -1,107 +1,105 @@ /** * @file pfft_engine.hh * * @author Lars Pastewka * * @date 06 Mar 2017 * * @brief FFT engine using MPI-parallel PFFT * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_FFT_PFFT_ENGINE_HH_ #define SRC_FFT_PFFT_ENGINE_HH_ #include "common/communicator.hh" #include "fft/fft_engine_base.hh" #include namespace muSpectre { /** * implements the `muSpectre::FFTEngineBase` interface using the * FFTW library */ template class PFFTEngine : public FFTEngineBase { public: using Parent = FFTEngineBase; //!< base class using Ccoord = typename Parent::Ccoord; //!< cell coordinates type //! field for Fourier transform of second-order tensor using Workspace_t = typename Parent::Workspace_t; //! real-valued second-order tensor using Field_t = typename Parent::Field_t; //! Default constructor PFFTEngine() = delete; //! Constructor with system resolutions PFFTEngine(Ccoord resolutions, Dim_t nb_components, Communicator comm = Communicator()); //! Copy constructor PFFTEngine(const PFFTEngine & other) = delete; //! Move constructor PFFTEngine(PFFTEngine && other) = default; //! Destructor virtual ~PFFTEngine() noexcept; //! Copy assignment operator PFFTEngine & operator=(const PFFTEngine & other) = delete; //! Move assignment operator PFFTEngine & operator=(PFFTEngine && other) = default; // compute the plan, etc void initialise(FFT_PlanFlags plan_flags) override; //! forward transform Workspace_t & fft(Field_t & field) override; //! inverse transform void ifft(Field_t & field) const override; protected: MPI_Comm mpi_comm; //! < MPI communicator static int nb_engines; //!< number of times this engine has been instatiated pfft_plan plan_fft{}; //!< holds the plan for forward fourier transform pfft_plan plan_ifft{}; //!< holds the plan for inverse fourier transform ptrdiff_t workspace_size{}; //!< size of workspace buffer returned by planner Real * real_workspace{}; //!< temporary real workspace that is correctly //!< padded bool initialised{false}; //!< to prevent double initialisation - - private: }; } // namespace muSpectre #endif // SRC_FFT_PFFT_ENGINE_HH_ diff --git a/src/fft/projection_default.hh b/src/fft/projection_default.hh index e106cfb..ce036c4 100644 --- a/src/fft/projection_default.hh +++ b/src/fft/projection_default.hh @@ -1,117 +1,115 @@ /** * @file projection_default.hh * * @author Till Junge * * @date 14 Jan 2018 * * @brief virtual base class for default projection implementation, where the * projection operator is stored as a full fourth-order tensor per * k-space point (as opposed to 'smart' faster implementations, such as * ProjectionFiniteStrainFast * * Copyright (C) 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_FFT_PROJECTION_DEFAULT_HH_ #define SRC_FFT_PROJECTION_DEFAULT_HH_ #include "fft/projection_base.hh" namespace muSpectre { /** * base class to inherit from if one implements a projection * operator that is stored in form of a fourth-order tensor of real * values per k-grid point */ template class ProjectionDefault : public ProjectionBase { public: using Parent = ProjectionBase; //!< base class using Vector_t = typename Parent::Vector_t; //!< to represent fields //! polymorphic FFT pointer type using FFTEngine_ptr = typename Parent::FFTEngine_ptr; using Ccoord = typename Parent::Ccoord; //!< cell coordinates type using Rcoord = typename Parent::Rcoord; //!< spatial coordinates type //! global field collection using GFieldCollection_t = GlobalFieldCollection; //! local field collection for Fourier-space fields using LFieldCollection_t = LocalFieldCollection; //! Real space second order tensor fields (to be projected) using Field_t = TypedField; //! Fourier-space field containing the projection operator itself using Proj_t = TensorField; //! iterable form of the operator using Proj_map = T4MatrixFieldMap; //! vectorized version of the Fourier-space second-order tensor field using Vector_map = MatrixFieldMap; //! Default constructor ProjectionDefault() = delete; //! Constructor with cell sizes and formulation ProjectionDefault(FFTEngine_ptr engine, Rcoord lengths, Formulation form); //! Copy constructor ProjectionDefault(const ProjectionDefault & other) = delete; //! Move constructor ProjectionDefault(ProjectionDefault && other) = default; //! Destructor virtual ~ProjectionDefault() = default; //! Copy assignment operator ProjectionDefault & operator=(const ProjectionDefault & other) = delete; //! Move assignment operator ProjectionDefault & operator=(ProjectionDefault && other) = delete; //! apply the projection operator to a field void apply_projection(Field_t & field) final; Eigen::Map get_operator() final; /** * returns the number of rows and cols for the strain matrix type * (for full storage, the strain is stored in material_dim × * material_dim matrices, but in symmetriy storage, it is a column * vector) */ std::array get_strain_shape() const final; constexpr static Dim_t NbComponents() { return ipow(DimM, 2); } protected: Proj_t & Gfield; //!< field holding the operator Proj_map Ghat; //!< iterable version of operator - - private: }; } // namespace muSpectre #endif // SRC_FFT_PROJECTION_DEFAULT_HH_ diff --git a/src/fft/projection_finite_strain_fast.hh b/src/fft/projection_finite_strain_fast.hh index cfd7aa4..f88d10b 100644 --- a/src/fft/projection_finite_strain_fast.hh +++ b/src/fft/projection_finite_strain_fast.hh @@ -1,123 +1,121 @@ /** * @file projection_finite_strain_fast.hh * * @author Till Junge * * @date 12 Dec 2017 * * @brief Faster alternative to ProjectionFinitestrain * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_FFT_PROJECTION_FINITE_STRAIN_FAST_HH_ #define SRC_FFT_PROJECTION_FINITE_STRAIN_FAST_HH_ #include "fft/projection_base.hh" #include "common/common.hh" #include "common/field_collection.hh" #include "common/field_map.hh" namespace muSpectre { /** * replaces `muSpectre::ProjectionFiniteStrain` with a faster and * less memory-hungry alternative formulation. Use this if you don't * have a very good reason not to (and tell me (author) about it, * I'd be interested to hear it). */ template class ProjectionFiniteStrainFast : public ProjectionBase { public: using Parent = ProjectionBase; //!< base class //! polymorphic pointer to FFT engines using FFTEngine_ptr = typename Parent::FFTEngine_ptr; using Ccoord = typename Parent::Ccoord; //!< cell coordinates type using Rcoord = typename Parent::Rcoord; //!< spatial coordinates type //! global field collection (for real-space representations) using GFieldCollection_t = GlobalFieldCollection; //! local field collection (for Fourier-space representations) using LFieldCollection_t = LocalFieldCollection; //! Real space second order tensor fields (to be projected) using Field_t = TypedField; //! Fourier-space field containing the projection operator itself using Proj_t = TensorField; //! iterable form of the operator using Proj_map = MatrixFieldMap; //! iterable Fourier-space second-order tensor field using Grad_map = MatrixFieldMap; //! Default constructor ProjectionFiniteStrainFast() = delete; //! Constructor with fft_engine ProjectionFiniteStrainFast(FFTEngine_ptr engine, Rcoord lengths); //! Copy constructor ProjectionFiniteStrainFast(const ProjectionFiniteStrainFast & other) = delete; //! Move constructor ProjectionFiniteStrainFast(ProjectionFiniteStrainFast && other) = default; //! Destructor virtual ~ProjectionFiniteStrainFast() = default; //! Copy assignment operator ProjectionFiniteStrainFast & operator=(const ProjectionFiniteStrainFast & other) = delete; //! Move assignment operator ProjectionFiniteStrainFast & operator=(ProjectionFiniteStrainFast && other) = default; //! initialises the fft engine (plan the transform) void initialise(FFT_PlanFlags flags = FFT_PlanFlags::estimate) final; //! apply the projection operator to a field void apply_projection(Field_t & field) final; Eigen::Map get_operator() final; /** * returns the number of rows and cols for the strain matrix type * (for full storage, the strain is stored in material_dim × * material_dim matrices, but in symmetriy storage, it is a column * vector) */ std::array get_strain_shape() const final; constexpr static Dim_t NbComponents() { return ipow(DimM, 2); } protected: Proj_t & xiField; //!< field of normalised wave vectors Proj_map xis; //!< iterable normalised wave vectors - - private: }; } // namespace muSpectre #endif // SRC_FFT_PROJECTION_FINITE_STRAIN_FAST_HH_ diff --git a/src/materials/CMakeLists.txt b/src/materials/CMakeLists.txt index cd4dbee..e763fa4 100644 --- a/src/materials/CMakeLists.txt +++ b/src/materials/CMakeLists.txt @@ -1,50 +1,48 @@ # ============================================================================= # file CMakeLists.txt # # @author Till Junge # # @date 08 Jan 2018 # # @brief configuration for material laws # # @section LICENSE # # Copyright © 2018 Till Junge # # µSpectre is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation, either version 3, or (at # your option) any later version. # # µSpectre 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 # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with µSpectre; see the file COPYING. If not, write to the # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # # Additional permission under GNU GPL version 3 section 7 # # If you modify this Program, or any covered work, by linking or combining it # with proprietary FFT implementations or numerical libraries, containing parts # covered by the terms of those libraries' licenses, the licensors of this # Program grant you additional permission to convey the resulting work. # ============================================================================= set (materials_SRC ${CMAKE_CURRENT_SOURCE_DIR}/material_base.cc ${CMAKE_CURRENT_SOURCE_DIR}/material_linear_elastic1.cc ${CMAKE_CURRENT_SOURCE_DIR}/material_linear_elastic2.cc ${CMAKE_CURRENT_SOURCE_DIR}/material_linear_elastic3.cc ${CMAKE_CURRENT_SOURCE_DIR}/material_linear_elastic4.cc ${CMAKE_CURRENT_SOURCE_DIR}/material_linear_elastic_generic1.cc ${CMAKE_CURRENT_SOURCE_DIR}/material_linear_elastic_generic2.cc ${CMAKE_CURRENT_SOURCE_DIR}/material_hyper_elasto_plastic1.cc ) target_sources(muSpectre PRIVATE ${materials_SRC}) - - diff --git a/src/materials/material_base.cc b/src/materials/material_base.cc index 1fe263c..a6b91af 100644 --- a/src/materials/material_base.cc +++ b/src/materials/material_base.cc @@ -1,82 +1,108 @@ /** * @file material_base.cc * * @author Till Junge * * @date 01 Nov 2017 * * @brief implementation of materi * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #include "materials/material_base.hh" namespace muSpectre { //----------------------------------------------------------------------------// template MaterialBase::MaterialBase(std::string name) : name(name) { static_assert((DimM == oneD) || (DimM == twoD) || (DimM == threeD), "only 1, 2, or threeD supported"); } /* ---------------------------------------------------------------------- */ template const std::string & MaterialBase::get_name() const { return this->name; } /* ---------------------------------------------------------------------- */ template void MaterialBase::add_pixel(const Ccoord & ccoord) { this->internal_fields.add_pixel(ccoord); } /* ---------------------------------------------------------------------- */ template void MaterialBase::compute_stresses(const Field_t & F, Field_t & P, Formulation form) { this->compute_stresses(StrainField_t::check_ref(F), StressField_t::check_ref(P), form); } /* ---------------------------------------------------------------------- */ template void MaterialBase::compute_stresses_tangent(const Field_t & F, Field_t & P, Field_t & K, Formulation form) { this->compute_stresses_tangent(StrainField_t::check_ref(F), StressField_t::check_ref(P), TangentField_t::check_ref(K), form); } + //----------------------------------------------------------------------------// + template + auto MaterialBase::get_real_field(std::string field_name) + -> EigenMap { + if (not this->internal_fields.check_field_exists(field_name)) { + std::stringstream err{}; + err << "Field '" << field_name << "' does not exist in material '" + << this->name << "'."; + throw FieldCollectionError(err.str()); + } + auto & field{this->internal_fields[field_name]}; + if (field.get_stored_typeid().hash_code() != typeid(Real).hash_code()) { + std::stringstream err{}; + err << "Field '" << field_name << "' is not real-valued"; + throw FieldCollectionError(err.str()); + } + + return static_cast &>(field).eigen(); + } + + /* ---------------------------------------------------------------------- */ + template + std::vector MaterialBase::list_fields() const { + return this->internal_fields.list_fields(); + } + template class MaterialBase<2, 2>; template class MaterialBase<2, 3>; template class MaterialBase<3, 3>; } // namespace muSpectre diff --git a/src/materials/material_base.hh b/src/materials/material_base.hh index 635124c..4cafe07 100644 --- a/src/materials/material_base.hh +++ b/src/materials/material_base.hh @@ -1,164 +1,177 @@ /** * @file material_base.hh * * @author Till Junge * * @date 25 Oct 2017 * * @brief Base class for materials (constitutive models) * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_MATERIALS_MATERIAL_BASE_HH_ #define SRC_MATERIALS_MATERIAL_BASE_HH_ #include "common/common.hh" #include "common/field.hh" #include "common/field_collection.hh" #include namespace muSpectre { //! DimS spatial dimension (dimension of problem //! DimM material_dimension (dimension of constitutive law) and /** * @a DimM is the material dimension (i.e., the dimension of constitutive * law; even for e.g. two-dimensional problems the constitutive law could * live in three-dimensional space for e.g. plane strain or stress problems) */ template class MaterialBase { public: //! typedefs for data handled by this interface //! global field collection for cell-wide fields, like stress, strain, etc using GFieldCollection_t = GlobalFieldCollection; //! field collection for internal variables, such as eigen-strains, //! plastic strains, damage variables, etc, but also for managing which //! pixels the material is responsible for using MFieldCollection_t = LocalFieldCollection; using iterator = typename MFieldCollection_t::iterator; //!< pixel iterator //! polymorphic base class for fields only to be used for debugging using Field_t = internal::FieldBase; //! Full type for stress fields using StressField_t = TensorField; //! Full type for strain fields using StrainField_t = StressField_t; //! Full type for tangent stiffness fields fields using TangentField_t = TensorField; using Ccoord = Ccoord_t; //!< cell coordinates type //! Default constructor MaterialBase() = delete; //! Construct by name explicit MaterialBase(std::string name); //! Copy constructor MaterialBase(const MaterialBase & other) = delete; //! Move constructor MaterialBase(MaterialBase && other) = delete; //! Destructor virtual ~MaterialBase() = default; //! Copy assignment operator MaterialBase & operator=(const MaterialBase & other) = delete; //! Move assignment operator MaterialBase & operator=(MaterialBase && other) = delete; /** * take responsibility for a pixel identified by its cell coordinates * WARNING: this won't work for materials with additional info per pixel * (as, e.g. for eigenstrain), we need to pass more parameters. Materials * of this tye need to overload add_pixel */ virtual void add_pixel(const Ccoord & ccooord); //! allocate memory, etc, but also: wipe history variables! virtual void initialise() = 0; /** * for materials with state variables, these typically need to be * saved/updated an the end of each load increment, the virtual * base implementation does nothing, but materials with history * variables need to implement this */ virtual void save_history_variables() {} //! return the material's name const std::string & get_name() const; //! spatial dimension for static inheritance constexpr static Dim_t sdim() { return DimS; } //! material dimension for static inheritance constexpr static Dim_t mdim() { return DimM; } //! computes stress virtual void compute_stresses(const StrainField_t & F, StressField_t & P, Formulation form) = 0; /** * Convenience function to compute stresses, mostly for debugging and * testing. Has runtime-cost associated with compatibility-checking and * conversion of the Field_t arguments that can be avoided by using the * version with strongly typed field references */ void compute_stresses(const Field_t & F, Field_t & P, Formulation form); //! computes stress and tangent moduli virtual void compute_stresses_tangent(const StrainField_t & F, StressField_t & P, TangentField_t & K, Formulation form) = 0; /** * Convenience function to compute stresses and tangent moduli, mostly for * debugging and testing. Has runtime-cost associated with * compatibility-checking and conversion of the Field_t arguments that can * be avoided by using the version with strongly typed field references */ void compute_stresses_tangent(const Field_t & F, Field_t & P, Field_t & K, Formulation form); //! iterator to first pixel handled by this material inline iterator begin() { return this->internal_fields.begin(); } //! iterator past the last pixel handled by this material inline iterator end() { return this->internal_fields.end(); } //! number of pixels assigned to this material inline size_t size() const { return this->internal_fields.size(); } + //! type to return real-valued fields in + using EigenMap = + Eigen::Map>; + /** + * return an internal field identified by its name as an Eigen Array + */ + EigenMap get_real_field(std::string field_name); + + /** + * list the names of all internal fields + */ + std::vector list_fields() const; + //! gives access to internal fields inline MFieldCollection_t & get_collection() { return this->internal_fields; } protected: const std::string name; //!< material's name (for output and debugging) MFieldCollection_t internal_fields{}; //!< storage for internal variables }; } // namespace muSpectre #endif // SRC_MATERIALS_MATERIAL_BASE_HH_ diff --git a/src/materials/material_hyper_elasto_plastic1.cc b/src/materials/material_hyper_elasto_plastic1.cc index 0bb3331..1c6236a 100644 --- a/src/materials/material_hyper_elasto_plastic1.cc +++ b/src/materials/material_hyper_elasto_plastic1.cc @@ -1,86 +1,214 @@ /** * @file material_hyper_elasto_plastic1.cc * * @author Till Junge * * @date 21 Feb 2018 * * @brief implementation for MaterialHyperElastoPlastic1 * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License as + * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public License + * You should have received a copy of the GNU General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * * Boston, MA 02111-1307, USA. - * - * Additional permission under GNU GPL version 3 section 7 - * - * If you modify this Program, or any covered work, by linking or combining it - * with proprietary FFT implementations or numerical libraries, containing parts - * covered by the terms of those libraries' licenses, the licensors of this - * Program grant you additional permission to convey the resulting work. + * Boston, MA 02111-1307, USA. */ +#include "common/common.hh" +#include "common/T4_map_proxy.hh" +#include "materials/stress_transformations_Kirchhoff.hh" #include "materials/material_hyper_elasto_plastic1.hh" namespace muSpectre { - //----------------------------------------------------------------------------// + //--------------------------------------------------------------------------// template MaterialHyperElastoPlastic1::MaterialHyperElastoPlastic1( std::string name, Real young, Real poisson, Real tau_y0, Real H) : Parent{name}, plast_flow_field{ make_statefield>>( "cumulated plastic flow εₚ", this->internal_fields)}, F_prev_field{make_statefield< StateField>>( "Previous placement gradient Fᵗ", this->internal_fields)}, be_prev_field{make_statefield< StateField>>( "Previous left Cauchy-Green deformation bₑᵗ", this->internal_fields)}, young{young}, poisson{poisson}, lambda{Hooke::compute_lambda(young, poisson)}, mu{Hooke::compute_mu(young, poisson)}, K{Hooke::compute_K(young, poisson)}, tau_y0{tau_y0}, H{H}, // the factor .5 comes from equation (18) in Geers 2003 // (https://doi.org/10.1016/j.cma.2003.07.014) C{0.5 * Hooke::compute_C_T4(lambda, mu)}, internal_variables{F_prev_field.get_map(), be_prev_field.get_map(), plast_flow_field.get_map()} {} /* ---------------------------------------------------------------------- */ template void MaterialHyperElastoPlastic1::save_history_variables() { this->plast_flow_field.cycle(); this->F_prev_field.cycle(); this->be_prev_field.cycle(); } /* ---------------------------------------------------------------------- */ template void MaterialHyperElastoPlastic1::initialise() { Parent::initialise(); this->F_prev_field.get_map().current() = Eigen::Matrix::Identity(); this->be_prev_field.get_map().current() = Eigen::Matrix::Identity(); this->save_history_variables(); } + //--------------------------------------------------------------------------// + template + auto MaterialHyperElastoPlastic1::stress_n_internals_worker( + const T2_t & F, StrainStRef_t & F_prev, StrainStRef_t & be_prev, + FlowStRef_t & eps_p) -> Worker_t { + // the notation in this function follows Geers 2003 + // (https://doi.org/10.1016/j.cma.2003.07.014). + + // computation of trial state + using Mat_t = Eigen::Matrix; + Mat_t f{F * F_prev.old().inverse()}; + // trial elastic left Cauchy–Green deformation tensor + Mat_t be_star{f * be_prev.old() * f.transpose()}; + const Decomp_t spectral_decomp{spectral_decomposition(be_star)}; + Mat_t ln_be_star{logm_alt(spectral_decomp)}; + Mat_t tau_star{.5 * + Hooke::evaluate_stress(this->lambda, this->mu, ln_be_star)}; + // deviatoric part of Kirchhoff stress + Mat_t tau_d_star{tau_star - tau_star.trace() / DimM * tau_star.Identity()}; + Real tau_eq_star{std::sqrt( + 3 * .5 * (tau_d_star.array() * tau_d_star.transpose().array()).sum())}; + // tau_eq_star can only be zero if tau_d_star is identically zero, + // so the following is not an approximation; + Real division_safe_tau_eq_star{tau_eq_star + Real(tau_eq_star == 0.)}; + Mat_t N_star{3 * .5 * tau_d_star / division_safe_tau_eq_star}; + // this is eq (27), and the std::min enforces the Kuhn-Tucker relation (16) + Real phi_star{ + std::max(tau_eq_star - this->tau_y0 - this->H * eps_p.old(), 0.)}; + + // return mapping + Real Del_gamma{phi_star / (this->H + 3 * this->mu)}; + Mat_t tau{tau_star - 2 * Del_gamma * this->mu * N_star}; + + // update the previous values to the new ones + F_prev.current() = F; + be_prev.current() = expm(ln_be_star - 2 * Del_gamma * N_star); + eps_p.current() = eps_p.old() + Del_gamma; + + // transmit info whether this is a plastic step or not + bool is_plastic{phi_star > 0}; + return Worker_t(std::move(tau), std::move(tau_eq_star), + std::move(Del_gamma), std::move(N_star), + std::move(is_plastic), spectral_decomp); + } + + //--------------------------------------------------------------------------// + template + auto MaterialHyperElastoPlastic1::evaluate_stress( + const T2_t & F, StrainStRef_t F_prev, StrainStRef_t be_prev, + FlowStRef_t eps_p) -> T2_t { + Eigen::Matrix tau; + std::tie(tau, std::ignore, std::ignore, std::ignore, std::ignore, + std::ignore) = + this->stress_n_internals_worker(F, F_prev, be_prev, eps_p); + + return tau; + } + + //--------------------------------------------------------------------------// + template + auto MaterialHyperElastoPlastic1::evaluate_stress_tangent( + const T2_t & F, StrainStRef_t F_prev, StrainStRef_t be_prev, + FlowStRef_t eps_p) -> std::tuple { + //! after the stress computation, all internals are up to date + auto && vals{this->stress_n_internals_worker(F, F_prev, be_prev, eps_p)}; + auto & tau{std::get<0>(vals)}; + auto & tau_eq_star{std::get<1>(vals)}; + auto & Del_gamma{std::get<2>(vals)}; + auto & N_star{std::get<3>(vals)}; + auto & is_plastic{std::get<4>(vals)}; + auto & spec_decomp{std::get<5>(vals)}; + using Mat_t = Eigen::Matrix; + using Vec_t = Eigen::Matrix; + using T4_t = T4Mat; + + auto compute_C4ep = [&]() { + auto && a0 = Del_gamma * this->mu / tau_eq_star; + auto && a1 = this->mu / (this->H + 3 * this->mu); + return T4Mat{ + ((this->K / 2. - this->mu / 3 + a0 * this->mu) * + Matrices::Itrac() + + (1 - 3 * a0) * this->mu * Matrices::Isymm() + + 2 * this->mu * (a0 - a1) * Matrices::outer(N_star, N_star))}; + }; + + T4_t mat_tangent{is_plastic ? compute_C4ep() : this->C}; + + // compute derivative ∂ln(be_star)/∂be_star, see (77) through (80) + T4_t dlnbe_dbe{T4_t::Zero()}; + { + const Vec_t & eig_vals{spec_decomp.eigenvalues()}; + const Vec_t log_eig_vals{eig_vals.array().log().matrix()}; + const Mat_t & eig_vecs{spec_decomp.eigenvectors()}; + + Mat_t g_vals{}; + // see (78), (79) + for (int i{0}; i < DimM; ++i) { + g_vals(i, i) = 1 / eig_vals(i); + for (int j{i + 1}; j < DimM; ++j) { + if (std::abs((eig_vals(i) - eig_vals(j)) / eig_vals(i)) < 1e-12) { + g_vals(i, j) = g_vals(j, i) = g_vals(i, i); + } else { + g_vals(i, j) = g_vals(j, i) = ((log_eig_vals(j) - log_eig_vals(i)) / + (eig_vals(j) - eig_vals(i))); + } + } + } + + for (int i{0}; i < DimM; ++i) { + for (int j{0}; j < DimM; ++j) { + Mat_t dyad = eig_vecs.col(i) * eig_vecs.col(j).transpose(); + T4_t outerDyad = Matrices::outer(dyad, dyad.transpose()); + dlnbe_dbe += g_vals(i, j) * outerDyad; + } + } + } + + // compute variation δbe_star + T2_t I{Matrices::I2()}; + // computation of trial state + Mat_t f{F * F_prev.old().inverse()}; + // trial elastic left Cauchy–Green deformation tensor + Mat_t be_star{f * be_prev.old() * f.transpose()}; + + T4_t dbe_dF{Matrices::outer_under(I, be_star) + + Matrices::outer_over(be_star, I)}; + + // T4_t dtau_dbe{mat_tangent * dlnbe_dbe * dbe4s}; + T4_t dtau_dF{mat_tangent * dlnbe_dbe * dbe_dF}; + // return std::tuple(tau, dtau_dbe); + return std::tuple(tau, dtau_dF); + } template class MaterialHyperElastoPlastic1; template class MaterialHyperElastoPlastic1; template class MaterialHyperElastoPlastic1; } // namespace muSpectre diff --git a/src/materials/material_hyper_elasto_plastic1.hh b/src/materials/material_hyper_elasto_plastic1.hh index 1fdeb4e..81669b9 100644 --- a/src/materials/material_hyper_elasto_plastic1.hh +++ b/src/materials/material_hyper_elasto_plastic1.hh @@ -1,306 +1,247 @@ /** * @file material_hyper_elasto_plastic1.hh * * @author Till Junge * * @date 20 Feb 2018 * * @brief Material for logarithmic hyperelasto-plasticity, as defined in de * Geus 2017 (https://doi.org/10.1016/j.cma.2016.12.032) and further * explained in Geers 2003 (https://doi.org/10.1016/j.cma.2003.07.014) * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_MATERIALS_MATERIAL_HYPER_ELASTO_PLASTIC1_HH_ #define SRC_MATERIALS_MATERIAL_HYPER_ELASTO_PLASTIC1_HH_ #include "materials/material_muSpectre_base.hh" #include "materials/materials_toolbox.hh" #include "common/eigen_tools.hh" #include "common/statefield.hh" #include namespace muSpectre { template class MaterialHyperElastoPlastic1; /** * traits for hyper-elastoplastic material */ template struct MaterialMuSpectre_traits> { //! global field collection using GFieldCollection_t = typename MaterialBase::GFieldCollection_t; //! expected map type for strain fields using StrainMap_t = MatrixFieldMap; //! expected map type for stress fields using StressMap_t = MatrixFieldMap; //! expected map type for tangent stiffness fields using TangentMap_t = T4MatrixFieldMap; //! declare what type of strain measure your law takes as input constexpr static auto strain_measure{StrainMeasure::Gradient}; //! declare what type of stress measure your law yields as output constexpr static auto stress_measure{StressMeasure::Kirchhoff}; //! local field collection used for internals using LFieldColl_t = LocalFieldCollection; //! storage type for plastic flow measure (εₚ in the papers) using LScalarMap_t = StateFieldMap>; /** * storage type for for previous gradient Fᵗ and elastic left * Cauchy-Green deformation tensor bₑᵗ */ using LStrainMap_t = StateFieldMap>; /** * format in which to receive internals (previous gradient Fᵗ, * previous elastic lef Cauchy-Green deformation tensor bₑᵗ, and * the plastic flow measure εₚ */ using InternalVariables = std::tuple; }; /** - * Material implementation for hyper-elastoplastic constitutive law + * material implementation for hyper-elastoplastic constitutive law. Note for + * developpers: this law is tested against a reference python implementation + * in `py_comparison_test_material_hyper_elasto_plastic1.py` */ template class MaterialHyperElastoPlastic1 : public MaterialMuSpectre, DimS, DimM> { public: //! base class using Parent = MaterialMuSpectre, DimS, DimM>; + using T2_t = Eigen::Matrix; + using T4_t = T4Mat; /** * type used to determine whether the * `muSpectre::MaterialMuSpectre::iterable_proxy` evaluate only * stresses or also tangent stiffnesses */ using NeedTangent = typename Parent::NeedTangent; //! shortcut to traits using traits = MaterialMuSpectre_traits; //! Hooke's law implementation using Hooke = typename MatTB::Hooke; //! type in which the previous strain state is referenced using StrainStRef_t = typename traits::LStrainMap_t::reference; //! type in which the previous plastic flow is referenced using FlowStRef_t = typename traits::LScalarMap_t::reference; + //! Local FieldCollection type for field storage + using LColl_t = LocalFieldCollection; + //! Default constructor MaterialHyperElastoPlastic1() = delete; //! Constructor with name and material properties MaterialHyperElastoPlastic1(std::string name, Real young, Real poisson, Real tau_y0, Real H); //! Copy constructor MaterialHyperElastoPlastic1(const MaterialHyperElastoPlastic1 & other) = delete; //! Move constructor MaterialHyperElastoPlastic1(MaterialHyperElastoPlastic1 && other) = delete; //! Destructor virtual ~MaterialHyperElastoPlastic1() = default; //! Copy assignment operator MaterialHyperElastoPlastic1 & operator=(const MaterialHyperElastoPlastic1 & other) = delete; //! Move assignment operator MaterialHyperElastoPlastic1 & operator=(MaterialHyperElastoPlastic1 && other) = delete; /** * evaluates Kirchhoff stress given the current placement gradient * Fₜ, the previous Gradient Fₜ₋₁ and the cumulated plastic flow * εₚ */ - template - inline decltype(auto) evaluate_stress(grad_t && F, StrainStRef_t F_prev, - StrainStRef_t be_prev, - FlowStRef_t plast_flow); - + T2_t evaluate_stress(const T2_t & F, StrainStRef_t F_prev, + StrainStRef_t be_prev, FlowStRef_t plast_flow); /** * evaluates Kirchhoff stress and stiffness given the current placement * gradient Fₜ, the previous Gradient Fₜ₋₁ and the cumulated plastic flow εₚ */ - template - inline decltype(auto) - evaluate_stress_tangent(grad_t && F, StrainStRef_t F_prev, - StrainStRef_t be_prev, FlowStRef_t plast_flow); + std::tuple evaluate_stress_tangent(const T2_t & F, + StrainStRef_t F_prev, + StrainStRef_t be_prev, + FlowStRef_t plast_flow); /** * The statefields need to be cycled at the end of each load increment */ void save_history_variables() override; /** * set the previous gradients to identity */ void initialise() final; /** * return the internals tuple */ typename traits::InternalVariables & get_internals() { return this->internal_variables; } + //! getter for internal variable field εₚ + StateField> & get_plast_flow_field() { + return this->plast_flow_field; + } + + //! getter for previous gradient field Fᵗ + StateField> & + get_F_prev_field() { + return this->F_prev_field; + } + + //! getterfor elastic left Cauchy-Green deformation tensor bₑᵗ + StateField> & + get_be_prev_field() { + return this->be_prev_field; + } + + /** + * needed to accomodate the static-sized member variable C, see + * http://eigen.tuxfamily.org/dox-devel/group__TopicStructHavingEigenMembers.html + */ + EIGEN_MAKE_ALIGNED_OPERATOR_NEW; + protected: /** * worker function computing stresses and internal variables */ - template - inline decltype(auto) stress_n_internals_worker(grad_t && F, - StrainStRef_t & F_prev, - StrainStRef_t & be_prev, - FlowStRef_t & plast_flow); - //! Local FieldCollection type for field storage - using LColl_t = LocalFieldCollection; + using Worker_t = std::tuple>; + Worker_t stress_n_internals_worker(const T2_t & F, StrainStRef_t & F_prev, + StrainStRef_t & be_prev, + FlowStRef_t & plast_flow); //! storage for cumulated plastic flow εₚ StateField> & plast_flow_field; //! storage for previous gradient Fᵗ StateField> & F_prev_field; //! storage for elastic left Cauchy-Green deformation tensor bₑᵗ StateField> & be_prev_field; // material properties const Real young; //!< Young's modulus const Real poisson; //!< Poisson's ratio const Real lambda; //!< first Lamé constant const Real mu; //!< second Lamé constant (shear modulus) const Real K; //!< Bulk modulus const Real tau_y0; //!< initial yield stress const Real H; //!< hardening modulus const T4Mat C; //!< stiffness tensor //! Field maps and state field maps over internal fields typename traits::InternalVariables internal_variables; - - private: }; - //--------------------------------------------------------------------------// - template - template - auto MaterialHyperElastoPlastic1::stress_n_internals_worker( - grad_t && F, StrainStRef_t & F_prev, StrainStRef_t & be_prev, - FlowStRef_t & eps_p) -> decltype(auto) { - // the notation in this function follows Geers 2003 - // (https://doi.org/10.1016/j.cma.2003.07.014). - - // computation of trial state - using Mat_t = Eigen::Matrix; - auto && f{F * F_prev.old().inverse()}; - Mat_t be_star{f * be_prev.old() * f.transpose()}; - Mat_t ln_be_star{logm(std::move(be_star))}; - Mat_t tau_star{.5 * - Hooke::evaluate_stress(this->lambda, this->mu, ln_be_star)}; - // deviatoric part of Kirchhoff stress - Mat_t tau_d_star{tau_star - tau_star.trace() / DimM * tau_star.Identity()}; - auto && tau_eq_star{std::sqrt( - 3 * .5 * (tau_d_star.array() * tau_d_star.transpose().array()).sum())}; - Mat_t N_star{3 * .5 * tau_d_star / tau_eq_star}; - // this is eq (27), and the std::min enforces the Kuhn-Tucker relation (16) - Real phi_star{ - std::max(tau_eq_star - this->tau_y0 - this->H * eps_p.old(), 0.)}; - - // return mapping - Real Del_gamma{phi_star / (this->H + 3 * this->mu)}; - auto && tau{tau_star - 2 * Del_gamma * this->mu * N_star}; - - // update the previous values to the new ones - F_prev.current() = F; - ln_be_star -= 2 * Del_gamma * N_star; - be_prev.current() = expm(std::move(ln_be_star)); - eps_p.current() += Del_gamma; - - // transmit info whether this is a plastic step or not - bool is_plastic{phi_star > 0}; - return std::tuple( - std::move(tau), std::move(tau_eq_star), std::move(Del_gamma), - std::move(N_star), std::move(is_plastic)); - } - //----------------------------------------------------------------------------// - template - template - auto MaterialHyperElastoPlastic1::evaluate_stress( - grad_t && F, StrainStRef_t F_prev, StrainStRef_t be_prev, - FlowStRef_t eps_p) -> decltype(auto) { - auto retval(std::move(std::get<0>(this->stress_n_internals_worker( - std::forward(F), F_prev, be_prev, eps_p)))); - return retval; - } - //----------------------------------------------------------------------------// - template - template - auto MaterialHyperElastoPlastic1::evaluate_stress_tangent( - grad_t && F, StrainStRef_t F_prev, StrainStRef_t be_prev, - FlowStRef_t eps_p) -> decltype(auto) { - //! after the stress computation, all internals are up to date - auto && vals{this->stress_n_internals_worker(std::forward(F), - F_prev, be_prev, eps_p)}; - auto && tau{std::get<0>(vals)}; - auto && tau_eq_star{std::get<1>(vals)}; - auto && Del_gamma{std::get<2>(vals)}; - auto && N_star{std::get<3>(vals)}; - auto && is_plastic{std::get<4>(vals)}; - - if (is_plastic) { - auto && a0 = Del_gamma * this->mu / tau_eq_star; - auto && a1 = this->mu / (this->H + 3 * this->mu); - return std::make_tuple( - std::move(tau), - T4Mat{ - ((this->K / 2. - this->mu / 3 + a0 * this->mu) * - Matrices::Itrac() + - (1 - 3 * a0) * this->mu * Matrices::Isymm() + - 2 * this->mu * (a0 - a1) * Matrices::outer(N_star, N_star))}); - } else { - return std::make_tuple(std::move(tau), T4Mat{this->C}); - } - } - } // namespace muSpectre #endif // SRC_MATERIALS_MATERIAL_HYPER_ELASTO_PLASTIC1_HH_ diff --git a/src/materials/material_linear_elastic1.hh b/src/materials/material_linear_elastic1.hh index bb4953e..50642e7 100644 --- a/src/materials/material_linear_elastic1.hh +++ b/src/materials/material_linear_elastic1.hh @@ -1,194 +1,195 @@ /** * @file material_linear_elastic1.hh * * @author Till Junge * * @date 13 Nov 2017 * * @brief Implementation for linear elastic reference material like in de Geus * 2017. This follows the simplest and likely not most efficient * implementation (with exception of the Python law) * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_MATERIALS_MATERIAL_LINEAR_ELASTIC1_HH_ #define SRC_MATERIALS_MATERIAL_LINEAR_ELASTIC1_HH_ #include "common/common.hh" +#include "materials/stress_transformations_PK2.hh" #include "materials/material_muSpectre_base.hh" #include "materials/materials_toolbox.hh" namespace muSpectre { template class MaterialLinearElastic1; /** * traits for objective linear elasticity */ template struct MaterialMuSpectre_traits> { using Parent = MaterialMuSpectre_traits; //!< base for elasticity //! global field collection using GFieldCollection_t = typename MaterialBase::GFieldCollection_t; //! expected map type for strain fields using StrainMap_t = MatrixFieldMap; //! expected map type for stress fields using StressMap_t = MatrixFieldMap; //! expected map type for tangent stiffness fields using TangentMap_t = T4MatrixFieldMap; //! declare what type of strain measure your law takes as input constexpr static auto strain_measure{StrainMeasure::GreenLagrange}; //! declare what type of stress measure your law yields as output constexpr static auto stress_measure{StressMeasure::PK2}; //! elasticity without internal variables using InternalVariables = std::tuple<>; }; //! DimS spatial dimension (dimension of problem //! DimM material_dimension (dimension of constitutive law) /** * implements objective linear elasticity */ template class MaterialLinearElastic1 : public MaterialMuSpectre, DimS, DimM> { public: //! base class using Parent = MaterialMuSpectre; /** * type used to determine whether the * `muSpectre::MaterialMuSpectre::iterable_proxy` evaluate only * stresses or also tangent stiffnesses */ using NeedTangent = typename Parent::NeedTangent; //! global field collection using Stiffness_t = Eigen::TensorFixedSize>; //! traits of this material using traits = MaterialMuSpectre_traits; //! this law does not have any internal variables using InternalVariables = typename traits::InternalVariables; //! Hooke's law implementation using Hooke = typename MatTB::Hooke; //! Default constructor MaterialLinearElastic1() = delete; //! Copy constructor MaterialLinearElastic1(const MaterialLinearElastic1 & other) = delete; //! Construct by name, Young's modulus and Poisson's ratio MaterialLinearElastic1(std::string name, Real young, Real poisson); //! Move constructor MaterialLinearElastic1(MaterialLinearElastic1 && other) = delete; //! Destructor virtual ~MaterialLinearElastic1() = default; //! Copy assignment operator MaterialLinearElastic1 & operator=(const MaterialLinearElastic1 & other) = delete; //! Move assignment operator MaterialLinearElastic1 & operator=(MaterialLinearElastic1 && other) = delete; /** * evaluates second Piola-Kirchhoff stress given the Green-Lagrange * strain (or Cauchy stress if called with a small strain tensor) */ template inline decltype(auto) evaluate_stress(s_t && E); /** * evaluates both second Piola-Kirchhoff stress and stiffness given * the Green-Lagrange strain (or Cauchy stress and stiffness if * called with a small strain tensor) */ template inline decltype(auto) evaluate_stress_tangent(s_t && E); /** * return the empty internals tuple */ InternalVariables & get_internals() { return this->internal_variables; } protected: const Real young; //!< Young's modulus const Real poisson; //!< Poisson's ratio const Real lambda; //!< first Lamé constant const Real mu; //!< second Lamé constant (shear modulus) const Stiffness_t C; //!< stiffness tensor //! empty tuple InternalVariables internal_variables{}; private: }; /* ---------------------------------------------------------------------- */ template template auto MaterialLinearElastic1::evaluate_stress(s_t && E) -> decltype(auto) { return Hooke::evaluate_stress(this->lambda, this->mu, std::move(E)); } /* ---------------------------------------------------------------------- */ template template auto MaterialLinearElastic1::evaluate_stress_tangent(s_t && E) -> decltype(auto) { using Tangent_t = typename traits::TangentMap_t::reference; // using mat = Eigen::Matrix; // mat ecopy{E}; // std::cout << "E" << std::endl << ecopy << std::endl; // std::cout << "P1" << std::endl << mat{ // std::get<0>(Hooke::evaluate_stress(this->lambda, this->mu, // Tangent_t(const_cast(this->C.data())), // std::move(E)))} << std::endl; return Hooke::evaluate_stress( this->lambda, this->mu, Tangent_t(const_cast(this->C.data())), std::move(E)); } } // namespace muSpectre #endif // SRC_MATERIALS_MATERIAL_LINEAR_ELASTIC1_HH_ diff --git a/src/materials/material_linear_elastic_generic1.hh b/src/materials/material_linear_elastic_generic1.hh index eb779f6..37efcfb 100644 --- a/src/materials/material_linear_elastic_generic1.hh +++ b/src/materials/material_linear_elastic_generic1.hh @@ -1,185 +1,186 @@ /** * @file material_linear_elastic_generic1.hh * * @author Till Junge * * @date 21 Sep 2018 * * @brief Implementation fo a generic linear elastic material that * stores the full elastic stiffness tensor. Convenient but not the * most efficient * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_MATERIALS_MATERIAL_LINEAR_ELASTIC_GENERIC1_HH_ #define SRC_MATERIALS_MATERIAL_LINEAR_ELASTIC_GENERIC1_HH_ #include "common/common.hh" +#include "materials/stress_transformations_PK2.hh" #include "common/T4_map_proxy.hh" #include "materials/material_muSpectre_base.hh" #include "common/tensor_algebra.hh" namespace muSpectre { /** * forward declaration */ template class MaterialLinearElasticGeneric1; /** * traits for use by MaterialMuSpectre for crtp */ template struct MaterialMuSpectre_traits> { //! global field collection using GFieldCollection_t = typename MaterialBase::GFieldCollection_t; //! expected map type for strain fields using StrainMap_t = MatrixFieldMap; //! expected map type for stress fields using StressMap_t = MatrixFieldMap; //! expected map type for tangent stiffness fields using TangentMap_t = T4MatrixFieldMap; //! declare what type of strain measure your law takes as input constexpr static auto strain_measure{StrainMeasure::GreenLagrange}; //! declare what type of stress measure your law yields as output constexpr static auto stress_measure{StressMeasure::PK2}; //! elasticity without internal variables using InternalVariables = std::tuple<>; }; /** * Linear elastic law defined by a full stiffness tensor. Very * generic, but not most efficient */ template class MaterialLinearElasticGeneric1 : public MaterialMuSpectre, DimS, DimM> { public: //! parent type using Parent = MaterialMuSpectre, DimS, DimM>; //! generic input tolerant to python input using CInput_t = Eigen::Ref, 0, Eigen::Stride>; //! Default constructor MaterialLinearElasticGeneric1() = delete; /** * Constructor by name and stiffness tensor. * * @param name unique material name * @param C_voigt elastic tensor in Voigt notation */ MaterialLinearElasticGeneric1(const std::string & name, const CInput_t & C_voigt); //! Copy constructor MaterialLinearElasticGeneric1(const MaterialLinearElasticGeneric1 & other) = delete; //! Move constructor MaterialLinearElasticGeneric1(MaterialLinearElasticGeneric1 && other) = delete; //! Destructor virtual ~MaterialLinearElasticGeneric1() = default; //! Copy assignment operator MaterialLinearElasticGeneric1 & operator=(const MaterialLinearElasticGeneric1 & other) = delete; //! Move assignment operator MaterialLinearElasticGeneric1 & operator=(MaterialLinearElasticGeneric1 && other) = delete; //! see //! http://eigen.tuxfamily.org/dox/group__TopicStructHavingEigenMembers.html EIGEN_MAKE_ALIGNED_OPERATOR_NEW; /** * evaluates second Piola-Kirchhoff stress given the Green-Lagrange * strain (or Cauchy stress if called with a small strain tensor) */ template inline decltype(auto) evaluate_stress(const Eigen::MatrixBase & E); /** * evaluates both second Piola-Kirchhoff stress and stiffness given * the Green-Lagrange strain (or Cauchy stress and stiffness if * called with a small strain tensor) */ template inline decltype(auto) evaluate_stress_tangent(const Eigen::MatrixBase & E); /** * return the empty internals tuple */ std::tuple<> & get_internals() { return this->internal_variables; } /** * return a reference to the stiffness tensor */ const T4Mat & get_C() const { return this->C; } protected: T4Mat C{}; //! stiffness tensor //! empty tuple std::tuple<> internal_variables{}; }; /* ---------------------------------------------------------------------- */ template template auto MaterialLinearElasticGeneric1::evaluate_stress( const Eigen::MatrixBase & E) -> decltype(auto) { static_assert(Derived::ColsAtCompileTime == DimM, "wrong input size"); static_assert(Derived::RowsAtCompileTime == DimM, "wrong input size"); return Matrices::tensmult(this->C, E); } /* ---------------------------------------------------------------------- */ template template auto MaterialLinearElasticGeneric1::evaluate_stress_tangent( const Eigen::MatrixBase & E) -> decltype(auto) { using Stress_t = decltype(this->evaluate_stress(E)); using Stiffness_t = Eigen::Map>; using Ret_t = std::tuple; return Ret_t{this->evaluate_stress(E), Stiffness_t(this->C.data())}; } } // namespace muSpectre #endif // SRC_MATERIALS_MATERIAL_LINEAR_ELASTIC_GENERIC1_HH_ diff --git a/src/materials/material_muSpectre_base.hh b/src/materials/material_muSpectre_base.hh index 2d903d3..14620a5 100644 --- a/src/materials/material_muSpectre_base.hh +++ b/src/materials/material_muSpectre_base.hh @@ -1,701 +1,699 @@ /** * @file material_muSpectre_base.hh * * @author Till Junge * * @date 25 Oct 2017 * * @brief Base class for materials written for µSpectre specifically. These * can take full advantage of the configuration-change utilities of * µSpectre. The user can inherit from them to define new constitutive * laws and is merely required to provide the methods for computing the * second Piola-Kirchhoff stress and Tangent. This class uses the * "curiously recurring template parameter" to avoid virtual calls. * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_MATERIALS_MATERIAL_MUSPECTRE_BASE_HH_ #define SRC_MATERIALS_MATERIAL_MUSPECTRE_BASE_HH_ #include "common/common.hh" #include "materials/material_base.hh" #include "materials/materials_toolbox.hh" #include "common/field_collection.hh" #include "common/field.hh" #include "common//utilities.hh" #include #include #include #include namespace muSpectre { // Forward declaration for factory function template class CellBase; /** * material traits are used by `muSpectre::MaterialMuSpectre` to * break the circular dependence created by the curiously recurring * template parameter. These traits must define * - these `muSpectre::FieldMap`s: * - `StrainMap_t`: typically a `muSpectre::MatrixFieldMap` for a * constant second-order `muSpectre::TensorField` * - `StressMap_t`: typically a `muSpectre::MatrixFieldMap` for a * writable secord-order `muSpectre::TensorField` * - `TangentMap_t`: typically a `muSpectre::T4MatrixFieldMap` for a * writable fourth-order `muSpectre::TensorField` * - `strain_measure`: the expected strain type (will be replaced by the * small-strain tensor ε * `muspectre::StrainMeasure::Infinitesimal` in small * strain computations) * - `stress_measure`: the measure of the returned stress. Is used by * `muspectre::MaterialMuSpectre` to transform it into * Cauchy stress (`muspectre::StressMeasure::Cauchy`) in * small-strain computations and into first * Piola-Kirchhoff stress `muspectre::StressMeasure::PK1` * in finite-strain computations * - `InternalVariables`: a tuple of `muSpectre::FieldMap`s containing * internal variables */ template struct MaterialMuSpectre_traits {}; template class MaterialMuSpectre; /** * Base class for most convenient implementation of materials */ template class MaterialMuSpectre : public MaterialBase { public: /** * type used to determine whether the * `muSpectre::MaterialMuSpectre::iterable_proxy` evaluate only * stresses or also tangent stiffnesses */ using NeedTangent = MatTB::NeedTangent; using Parent = MaterialBase; //!< base class //! global field collection using GFieldCollection_t = typename Parent::GFieldCollection_t; //! expected type for stress fields using StressField_t = typename Parent::StressField_t; //! expected type for strain fields using StrainField_t = typename Parent::StrainField_t; //! expected type for tangent stiffness fields using TangentField_t = typename Parent::TangentField_t; //! traits for the CRTP subclass using traits = MaterialMuSpectre_traits; //! Default constructor MaterialMuSpectre() = delete; //! Construct by name explicit MaterialMuSpectre(std::string name); //! Copy constructor MaterialMuSpectre(const MaterialMuSpectre & other) = delete; //! Move constructor MaterialMuSpectre(MaterialMuSpectre && other) = delete; //! Destructor virtual ~MaterialMuSpectre() = default; //! Factory template static Material & make(CellBase & cell, ConstructorArgs &&... args); //! Copy assignment operator MaterialMuSpectre & operator=(const MaterialMuSpectre & other) = delete; //! Move assignment operator MaterialMuSpectre & operator=(MaterialMuSpectre && other) = delete; //! allocate memory, etc void initialise() override; using Parent::compute_stresses; using Parent::compute_stresses_tangent; //! computes stress void compute_stresses(const StrainField_t & F, StressField_t & P, Formulation form) final; //! computes stress and tangent modulus void compute_stresses_tangent(const StrainField_t & F, StressField_t & P, TangentField_t & K, Formulation form) final; protected: //! computes stress with the formulation available at compile time //! __attribute__ required by g++-6 and g++-7 because of this bug: //! https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80947 template inline void compute_stresses_worker(const StrainField_t & F, StressField_t & P) __attribute__((visibility("default"))); //! computes stress with the formulation available at compile time //! __attribute__ required by g++-6 and g++-7 because of this bug: //! https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80947 template inline void compute_stresses_worker(const StrainField_t & F, StressField_t & P, TangentField_t & K) __attribute__((visibility("default"))); //! this iterable class is a default for simple laws that just take a strain //! the iterable is just a templated wrapper to provide a range to iterate //! over that does or does not include tangent moduli template class iterable_proxy; /** * inheriting classes with internal variables need to overload this function */ typename traits::InternalVariables & get_internals() { return static_cast(*this).get_internals(); } bool is_initialised{false}; //!< to handle double initialisation right private: }; /* ---------------------------------------------------------------------- */ template MaterialMuSpectre::MaterialMuSpectre(std::string name) : Parent(name) { using stress_compatible = typename traits::StressMap_t::template is_compatible; using strain_compatible = typename traits::StrainMap_t::template is_compatible; using tangent_compatible = typename traits::TangentMap_t::template is_compatible; static_assert((stress_compatible::value && stress_compatible::explain()), "The material's declared stress map is not compatible " "with the stress field. More info in previously shown " "assert."); static_assert((strain_compatible::value && strain_compatible::explain()), "The material's declared strain map is not compatible " "with the strain field. More info in previously shown " "assert."); static_assert((tangent_compatible::value && tangent_compatible::explain()), "The material's declared tangent map is not compatible " "with the tangent field. More info in previously shown " "assert."); } /* ---------------------------------------------------------------------- */ template template Material & MaterialMuSpectre::make(CellBase & cell, ConstructorArgs &&... args) { auto mat = std::make_unique(args...); auto & mat_ref = *mat; cell.add_material(std::move(mat)); return mat_ref; } /* ---------------------------------------------------------------------- */ template void MaterialMuSpectre::initialise() { if (!this->is_initialised) { this->internal_fields.initialise(); this->is_initialised = true; } } /* ---------------------------------------------------------------------- */ template void MaterialMuSpectre::compute_stresses( const StrainField_t & F, StressField_t & P, Formulation form) { switch (form) { case Formulation::finite_strain: { this->template compute_stresses_worker(F, P); break; } case Formulation::small_strain: { this->template compute_stresses_worker(F, P); break; } default: throw std::runtime_error("Unknown formulation"); break; } } /* ---------------------------------------------------------------------- */ template void MaterialMuSpectre::compute_stresses_tangent( const StrainField_t & F, StressField_t & P, TangentField_t & K, Formulation form) { switch (form) { case Formulation::finite_strain: { this->template compute_stresses_worker(F, P, K); break; } case Formulation::small_strain: { this->template compute_stresses_worker(F, P, K); break; } default: throw std::runtime_error("Unknown formulation"); break; } } /* ---------------------------------------------------------------------- */ template template void MaterialMuSpectre::compute_stresses_worker( const StrainField_t & F, StressField_t & P, TangentField_t & K) { /* These lambdas are executed for every integration point. F contains the transformation gradient for finite strain calculations and the infinitesimal strain tensor in small strain problems The internal_variables tuple contains whatever internal variables Material declared (e.g., eigenstrain, strain rate, etc.) */ using Strains_t = std::tuple; using Stresses_t = std::tuple; auto constitutive_law_small_strain = [this](Strains_t Strains, Stresses_t Stresses, auto && internal_variables) { constexpr StrainMeasure stored_strain_m{get_stored_strain_type(Form)}; constexpr StrainMeasure expected_strain_m{ get_formulation_strain_type(Form, traits::strain_measure)}; auto & this_mat = static_cast(*this); // Transformation gradient is first in the strains tuple auto & F = std::get<0>(Strains); auto && strain = MatTB::convert_strain(F); // return value contains a tuple of rvalue_refs to both stress and tangent // moduli Stresses = apply( [&strain, &this_mat](auto &&... internals) { return this_mat.evaluate_stress_tangent(std::move(strain), internals...); }, internal_variables); }; auto constitutive_law_finite_strain = [this](Strains_t Strains, Stresses_t Stresses, auto && internal_variables) { constexpr StrainMeasure stored_strain_m{get_stored_strain_type(Form)}; constexpr StrainMeasure expected_strain_m{ get_formulation_strain_type(Form, traits::strain_measure)}; auto & this_mat = static_cast(*this); // Transformation gradient is first in the strains tuple auto & grad = std::get<0>(Strains); auto && strain = MatTB::convert_strain(grad); // TODO(junge): Figure this out: I can't std::move(internals...), // because if there are no internals, compilation fails with "no // matching function for call to ‘move()’'. These are tuples of // lvalue references, so it shouldn't be too bad, but still // irksome. // return value contains a tuple of rvalue_refs to both stress // and tangent moduli auto stress_tgt = apply( [&strain, &this_mat](auto &&... internals) { return this_mat.evaluate_stress_tangent(std::move(strain), internals...); }, internal_variables); auto & stress = std::get<0>(stress_tgt); auto & tangent = std::get<1>(stress_tgt); Stresses = MatTB::PK1_stress( std::move(grad), std::move(stress), std::move(tangent)); }; iterable_proxy fields{*this, F, P, K}; for (auto && arglist : fields) { /** * arglist is a tuple of three tuples containing only Lvalue * references (see value_tye in the class definition of * iterable_proxy::iterator). Tuples contain strains, stresses * and internal variables, respectively, */ // auto && stress_tgt = std::get<0>(tuples); // auto && inputs = std::get<1>(tuples);TODO:clean this static_assert(std::is_same( std::get<0>(arglist)))>>::value, "Type mismatch for strain reference, check iterator " "value_type"); static_assert(std::is_same( std::get<1>(arglist)))>>::value, "Type mismatch for stress reference, check iterator" "value_type"); static_assert(std::is_same( std::get<1>(arglist)))>>::value, "Type mismatch for tangent reference, check iterator" "value_type"); switch (Form) { case Formulation::small_strain: { apply(constitutive_law_small_strain, std::move(arglist)); break; } case Formulation::finite_strain: { apply(constitutive_law_finite_strain, std::move(arglist)); break; } } } } /* ---------------------------------------------------------------------- */ template template void MaterialMuSpectre::compute_stresses_worker( const StrainField_t & F, StressField_t & P) { /* These lambdas are executed for every integration point. F contains the transformation gradient for finite strain calculations and the infinitesimal strain tensor in small strain problems The internal_variables tuple contains whatever internal variables Material declared (e.g., eigenstrain, strain rate, etc.) */ using Strains_t = std::tuple; using Stresses_t = std::tuple; auto constitutive_law_small_strain = [this](Strains_t Strains, Stresses_t Stresses, auto && internal_variables) { constexpr StrainMeasure stored_strain_m{get_stored_strain_type(Form)}; constexpr StrainMeasure expected_strain_m{ get_formulation_strain_type(Form, traits::strain_measure)}; auto & this_mat = static_cast(*this); // Transformation gradient is first in the strains tuple auto & F = std::get<0>(Strains); auto && strain = MatTB::convert_strain(F); // return value contains a tuple of rvalue_refs to both stress and tangent // moduli auto & sigma = std::get<0>(Stresses); sigma = apply( [&strain, &this_mat](auto &&... internals) { return this_mat.evaluate_stress(std::move(strain), internals...); }, internal_variables); }; auto constitutive_law_finite_strain = [this](Strains_t Strains, Stresses_t && Stresses, auto && internal_variables) { constexpr StrainMeasure stored_strain_m{get_stored_strain_type(Form)}; constexpr StrainMeasure expected_strain_m{ get_formulation_strain_type(Form, traits::strain_measure)}; auto & this_mat = static_cast(*this); // Transformation gradient is first in the strains tuple auto & F = std::get<0>(Strains); auto && strain = MatTB::convert_strain(F); // TODO(junge): Figure this out: I can't std::move(internals...), // because if there are no internals, compilation fails with "no // matching function for call to ‘move()’'. These are tuples of // lvalue references, so it shouldn't be too bad, but still // irksome. // return value contains a tuple of rvalue_refs to both stress // and tangent moduli auto && stress = apply( [&strain, &this_mat](auto &&... internals) { return this_mat.evaluate_stress(std::move(strain), internals...); }, internal_variables); auto & P = get<0>(Stresses); P = MatTB::PK1_stress( F, stress); }; iterable_proxy fields{*this, F, P}; for (auto && arglist : fields) { /** * arglist is a tuple of three tuples containing only Lvalue * references (see value_tye in the class definition of * iterable_proxy::iterator). Tuples contain strains, stresses * and internal variables, respectively, */ // auto && stress_tgt = std::get<0>(tuples); // auto && inputs = std::get<1>(tuples);TODO:clean this static_assert(std::is_same( std::get<0>(arglist)))>>::value, "Type mismatch for strain reference, check iterator " "value_type"); static_assert(std::is_same( std::get<1>(arglist)))>>::value, "Type mismatch for stress reference, check iterator" "value_type"); switch (Form) { case Formulation::small_strain: { apply(constitutive_law_small_strain, std::move(arglist)); break; } case Formulation::finite_strain: { apply(constitutive_law_finite_strain, std::move(arglist)); break; } } } } /* ---------------------------------------------------------------------- */ //! this iterator class is a default for simple laws that just take a strain template template class MaterialMuSpectre::iterable_proxy { public: //! Default constructor iterable_proxy() = delete; /** * type used to determine whether the * `muSpectre::MaterialMuSpectre::iterable_proxy` evaluate only * stresses or also tangent stiffnesses */ using NeedTangent = typename MaterialMuSpectre::NeedTangent; /** Iterator uses the material's internal variables field collection to iterate selectively over the global fields (such as the transformation gradient F and first Piola-Kirchhoff stress P. **/ template iterable_proxy(MaterialMuSpectre & mat, const StrainField_t & F, StressField_t & P, std::enable_if_t & K) : material{mat}, strain_field{F}, stress_tup{P, K}, internals{material.get_internals()} {}; /** Iterator uses the material's internal variables field collection to iterate selectively over the global fields (such as the transformation gradient F and first Piola-Kirchhoff stress P. **/ template iterable_proxy(MaterialMuSpectre & mat, const StrainField_t & F, std::enable_if_t & P) : material{mat}, strain_field{F}, stress_tup{P}, internals{material.get_internals()} {}; //! Expected type for strain fields using StrainMap_t = typename traits::StrainMap_t; //! Expected type for stress fields using StressMap_t = typename traits::StressMap_t; //! Expected type for tangent stiffness fields using TangentMap_t = typename traits::TangentMap_t; //! expected type for strain values using Strain_t = typename traits::StrainMap_t::reference; //! expected type for stress values using Stress_t = typename traits::StressMap_t::reference; //! expected type for tangent stiffness values using Tangent_t = typename traits::TangentMap_t::reference; //! tuple of intervnal variables, depends on the material using InternalVariables = typename traits::InternalVariables; //! tuple containing a stress and possibly a tangent stiffness field using StressFieldTup = std::conditional_t<(NeedTgt == NeedTangent::yes), std::tuple, std::tuple>; //! tuple containing a stress and possibly a tangent stiffness field map using StressMapTup = std::conditional_t<(NeedTgt == NeedTangent::yes), std::tuple, std::tuple>; //! tuple containing a stress and possibly a tangent stiffness value ref using Stress_tTup = std::conditional_t<(NeedTgt == NeedTangent::yes), std::tuple, std::tuple>; //! Copy constructor iterable_proxy(const iterable_proxy & other) = default; //! Move constructor iterable_proxy(iterable_proxy && other) = default; //! Destructor virtual ~iterable_proxy() = default; //! Copy assignment operator iterable_proxy & operator=(const iterable_proxy & other) = default; //! Move assignment operator iterable_proxy & operator=(iterable_proxy && other) = default; /** * dereferences into a tuple containing strains, and internal * variables, as well as maps to the stress and potentially * stiffness maps where to write the response of a pixel */ class iterator { public: //! type to refer to internal variables owned by a CRTP material using InternalReferences = MatTB::ReferenceTuple_t; //! return type to be unpacked per pixel my the constitutive law using value_type = std::tuple, Stress_tTup, InternalReferences>; using iterator_category = std::forward_iterator_tag; //!< stl conformance //! Default constructor iterator() = delete; /** Iterator uses the material's internal variables field collection to iterate selectively over the global fields (such as the transformation gradient F and first Piola-Kirchhoff stress P. **/ explicit iterator(const iterable_proxy & it, bool begin = true) : it{it}, strain_map{it.strain_field}, stress_map(it.stress_tup), index{begin ? 0 : it.material.internal_fields.size()} {} //! Copy constructor iterator(const iterator & other) = default; //! Move constructor iterator(iterator && other) = default; //! Destructor virtual ~iterator() = default; //! Copy assignment operator iterator & operator=(const iterator & other) = default; //! Move assignment operator iterator & operator=(iterator && other) = default; //! pre-increment inline iterator & operator++(); //! dereference inline value_type operator*(); //! inequality inline bool operator!=(const iterator & other) const; protected: const iterable_proxy & it; //!< ref to the proxy StrainMap_t strain_map; //!< map onto the global strain field //! map onto the global stress field and possibly tangent stiffness StressMapTup stress_map; size_t index; //!< index or pixel currently referred to - - private: }; //! returns iterator to first pixel if this material iterator begin() { return std::move(iterator(*this)); } //! returns iterator past the last pixel in this material iterator end() { return std::move(iterator(*this, false)); } protected: MaterialMuSpectre & material; //!< reference to the proxied material const StrainField_t & strain_field; //!< cell's global strain field //! references to the global stress field and perhaps tangent stiffness StressFieldTup stress_tup; //! references to the internal variables InternalVariables & internals; private: }; /* ---------------------------------------------------------------------- */ template template bool MaterialMuSpectre::iterable_proxy::iterator:: operator!=(const iterator & other) const { return (this->index != other.index); } /* ---------------------------------------------------------------------- */ template template typename MaterialMuSpectre::template iterable_proxy::iterator & MaterialMuSpectre::iterable_proxy::iterator:: operator++() { this->index++; return *this; } /* ---------------------------------------------------------------------- */ template template typename MaterialMuSpectre::template iterable_proxy< NeedTgT>::iterator::value_type MaterialMuSpectre::iterable_proxy::iterator::operator*() { const Ccoord_t pixel{ this->it.material.internal_fields.get_ccoord(this->index)}; auto && strain = std::make_tuple(this->strain_map[pixel]); auto && stresses = apply( [&pixel](auto &&... stress_tgt) { return std::make_tuple(stress_tgt[pixel]...); }, this->stress_map); auto && internal = this->it.material.get_internals(); const auto id{this->index}; auto && internals = apply( [id](auto &&... internals_) { return InternalReferences{internals_[id]...}; }, internal); return std::make_tuple(std::move(strain), std::move(stresses), std::move(internals)); } } // namespace muSpectre #endif // SRC_MATERIALS_MATERIAL_MUSPECTRE_BASE_HH_ diff --git a/src/materials/materials_toolbox.hh b/src/materials/materials_toolbox.hh index 2f1f83c..474c477 100644 --- a/src/materials/materials_toolbox.hh +++ b/src/materials/materials_toolbox.hh @@ -1,673 +1,577 @@ /** * @file materials_toolbox.hh * * @author Till Junge * * @date 02 Nov 2017 * * @brief collection of common continuum mechanics tools * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_MATERIALS_MATERIALS_TOOLBOX_HH_ #define SRC_MATERIALS_MATERIALS_TOOLBOX_HH_ #include "common/common.hh" #include "common/tensor_algebra.hh" #include "common/eigen_tools.hh" #include "common/T4_map_proxy.hh" #include #include #include #include #include #include #include namespace muSpectre { namespace MatTB { /** * thrown when generic materials-related runtime errors occur * (mostly continuum mechanics problems) */ class MaterialsToolboxError : public std::runtime_error { public: //! constructor explicit MaterialsToolboxError(const std::string & what) : std::runtime_error(what) {} //! constructor explicit MaterialsToolboxError(const char * what) : std::runtime_error(what) {} }; /* ---------------------------------------------------------------------- */ /** * Flag used to designate whether the material should compute both stress * and tangent moduli or only stress */ enum class NeedTangent { yes, //!< compute both stress and tangent moduli no //!< compute only stress }; /** * struct used to determine the exact type of a tuple of references obtained * when a bunch of iterators over fiel_maps are dereferenced and their * results are concatenated into a tuple */ template struct ReferenceTuple { //! use this type using type = std::tuple; }; /** * specialisation for tuples */ // template <> template struct ReferenceTuple> { //! use this type using type = typename ReferenceTuple::type; }; /** * helper type for ReferenceTuple */ template using ReferenceTuple_t = typename ReferenceTuple::type; /* ---------------------------------------------------------------------- */ namespace internal { /** Structure for functions returning one strain measure as a * function of another **/ template struct ConvertStrain { static_assert((In == StrainMeasure::Gradient) || (In == StrainMeasure::Infinitesimal), "This situation makes me suspect that you are not using " "MatTb as intended. Disable this assert only if you are " "sure about what you are doing."); //! returns the converted strain template inline static decltype(auto) compute(Strain_t && input) { // transparent case, in which no conversion is required: // just a perfect forwarding static_assert((In == Out), "This particular strain conversion is not implemented"); return std::forward(input); } }; /* ---------------------------------------------------------------------- */ /** Specialisation for getting Green-Lagrange strain from the transformation gradient E = ¹/₂ (C - I) = ¹/₂ (Fᵀ·F - I) **/ template <> struct ConvertStrain { //! returns the converted strain template inline static decltype(auto) compute(Strain_t && F) { return .5 * (F.transpose() * F - Strain_t::PlainObject::Identity()); } }; /* ---------------------------------------------------------------------- */ /** Specialisation for getting Left Cauchy-Green strain from the transformation gradient B = F·Fᵀ = V² **/ template <> struct ConvertStrain { //! returns the converted strain template inline static decltype(auto) compute(Strain_t && F) { return F * F.transpose(); } }; /* ---------------------------------------------------------------------- */ /** Specialisation for getting Right Cauchy-Green strain from the transformation gradient C = Fᵀ·F = U² **/ template <> struct ConvertStrain { //! returns the converted strain template inline static decltype(auto) compute(Strain_t && F) { return F.transpose() * F; } }; /* ---------------------------------------------------------------------- */ /** Specialisation for getting logarithmic (Hencky) strain from the transformation gradient E₀ = ¹/₂ ln C = ¹/₂ ln (Fᵀ·F) **/ template <> struct ConvertStrain { //! returns the converted strain template inline static decltype(auto) compute(Strain_t && F) { constexpr Dim_t dim{EigenCheck::tensor_dim::value}; return (.5 * logm(Eigen::Matrix{F.transpose() * F})) .eval(); } }; } // namespace internal /* ---------------------------------------------------------------------- */ //! set of functions returning one strain measure as a function of //! another template decltype(auto) convert_strain(Strain_t && strain) { return internal::ConvertStrain::compute(std::move(strain)); } - /* ---------------------------------------------------------------------- */ - namespace internal { - - /** Structure for functions returning PK1 stress from other stress - *measures - **/ - template - struct PK1_stress { - //! returns the converted stress - template - inline static decltype(auto) compute(Strain_t && /*strain*/, - Stress_t && /*stress*/) { - // the following test always fails to generate a compile-time error - static_assert((StressM == StressMeasure::Cauchy) && - (StressM == StressMeasure::PK1), - "The requested Stress conversion is not implemented. " - "You either made a programming mistake or need to " - "implement it as a specialisation of this function. " - "See PK2stress for an example."); - } - - //! returns the converted stress and stiffness - template - inline static decltype(auto) compute(Strain_t && /*strain*/, - Stress_t && /*stress*/, - Tangent_t && /*stiffness*/) { - // the following test always fails to generate a compile-time error - static_assert((StressM == StressMeasure::Cauchy) && - (StressM == StressMeasure::PK1), - "The requested Stress conversion is not implemented. " - "You either made a programming mistake or need to " - "implement it as a specialisation of this function. " - "See PK2stress for an example."); - } - }; - - /* ---------------------------------------------------------------------- - */ - /** Specialisation for the transparent case, where we already - have PK1 stress - **/ - template - struct PK1_stress - : public PK1_stress { - //! returns the converted stress - template - inline static decltype(auto) compute(Strain_t && /*dummy*/, - Stress_t && P) { - return std::forward(P); - } - }; - - /* ---------------------------------------------------------------------- - */ - /** Specialisation for the transparent case, where we already have PK1 - stress *and* stiffness is given with respect to the transformation - gradient - **/ - template - struct PK1_stress - : public PK1_stress { - //! base class - using Parent = - PK1_stress; - using Parent::compute; - - //! returns the converted stress and stiffness - template - inline static decltype(auto) compute(Strain_t && /*dummy*/, - Stress_t && P, Tangent_t && K) { - return std::make_tuple(std::forward(P), - std::forward(K)); - } - }; - - /* ---------------------------------------------------------------------- - */ - /** - * Specialisation for the case where we get material stress (PK2) - */ - template - struct PK1_stress - : public PK1_stress { - //! returns the converted stress - template - inline static decltype(auto) compute(Strain_t && F, Stress_t && S) { - return F * S; - } - }; - - /* ---------------------------------------------------------------------- - */ - /** - * Specialisation for the case where we get material stress (PK2) derived - * with respect to Green-Lagrange strain - */ - template - struct PK1_stress - : public PK1_stress { - //! base class - using Parent = - PK1_stress; - using Parent::compute; - - //! returns the converted stress and stiffness - template - inline static decltype(auto) compute(Strain_t && F, Stress_t && S, - Tangent_t && C) { - using T4 = typename std::remove_reference_t::PlainObject; - using Tmap = T4MatMap; - T4 K; - Tmap Kmap{K.data()}; - K.setZero(); - - for (int i = 0; i < Dim; ++i) { - for (int m = 0; m < Dim; ++m) { - for (int n = 0; n < Dim; ++n) { - get(Kmap, i, m, i, n) += S(m, n); - for (int j = 0; j < Dim; ++j) { - for (int r = 0; r < Dim; ++r) { - for (int s = 0; s < Dim; ++s) { - get(Kmap, i, m, j, n) += - F(i, r) * get(C, r, m, n, s) * (F(j, s)); - } - } - } - } - } - } - auto && P = - compute(std::forward(F), std::forward(S)); - return std::make_tuple(std::move(P), std::move(K)); - } - }; - - /* ---------------------------------------------------------------------- - */ - /** - * Specialisation for the case where we get Kirchhoff stress (τ) - */ - template - struct PK1_stress - : public PK1_stress { - //! returns the converted stress - template - inline static decltype(auto) compute(Strain_t && F, Stress_t && tau) { - return tau * F.inverse().transpose(); - } - }; - - /* ---------------------------------------------------------------------- - */ - /** - * Specialisation for the case where we get Kirchhoff stress (τ) derived - * with respect to Gradient - */ - template - struct PK1_stress - : public PK1_stress { - //! short-hand - using Parent = PK1_stress; - using Parent::compute; - - //! returns the converted stress and stiffness - template - inline static decltype(auto) compute(Strain_t && F, Stress_t && tau, - Tangent_t && C) { - using T4 = typename std::remove_reference_t::PlainObject; - using Tmap = T4MatMap; - T4 K; - Tmap Kmap{K.data()}; - K.setZero(); - auto && F_inv{F.inverse()}; - for (int i = 0; i < Dim; ++i) { - for (int m = 0; m < Dim; ++m) { - for (int n = 0; n < Dim; ++n) { - for (int j = 0; j < Dim; ++j) { - for (int r = 0; r < Dim; ++r) { - for (int s = 0; s < Dim; ++s) { - get(Kmap, i, m, j, n) += F_inv(i, r) * get(C, r, m, n, s); - } - } - } - } - } - } - auto && P = tau * F_inv.transpose(); - return std::make_tuple(std::move(P), std::move(K)); - } - }; - - } // namespace internal - - /* ---------------------------------------------------------------------- */ - //! set of functions returning an expression for PK2 stress based on - template - decltype(auto) PK1_stress(Strain_t && strain, Stress_t && stress) { - constexpr Dim_t dim{EigenCheck::tensor_dim::value}; - static_assert((dim == EigenCheck::tensor_dim::value), - "Stress and strain tensors have differing dimensions"); - return internal::PK1_stress::compute( - std::forward(strain), std::forward(stress)); - } - - /* ---------------------------------------------------------------------- */ - //! set of functions returning an expression for PK2 stress based on - template - decltype(auto) PK1_stress(Strain_t && strain, Stress_t && stress, - Tangent_t && tangent) { - constexpr Dim_t dim{EigenCheck::tensor_dim::value}; - static_assert((dim == EigenCheck::tensor_dim::value), - "Stress and strain tensors have differing dimensions"); - static_assert((dim == EigenCheck::tensor_4_dim::value), - "Stress and tangent tensors have differing dimensions"); - - return internal::PK1_stress::compute( - std::forward(strain), std::forward(stress), - std::forward(tangent)); - } - namespace internal { //! Base template for elastic modulus conversion template struct Converter { //! wrapped function (raison d'être) inline constexpr static Real compute(const Real & /*in1*/, const Real & /*in2*/) { // if no return has happened until now, the conversion is not // implemented yet static_assert( (In1 == In2), "This conversion has not been implemented yet, please add " "it here below as a specialisation of this function " "template. Check " "https://en.wikipedia.org/wiki/Lam%C3%A9_parameters for " - "// TODO: he formula."); + "the formula."); return 0; } }; /** * Spectialisation for when the output is the first input */ template struct Converter { //! wrapped function (raison d'être) inline constexpr static Real compute(const Real & A, const Real & /*B*/) { return A; } }; /** * Spectialisation for when the output is the second input */ template struct Converter { //! wrapped function (raison d'être) inline constexpr static Real compute(const Real & /*A*/, const Real & B) { return B; } }; /** * Specialisation μ(E, ν) */ template <> struct Converter { //! wrapped function (raison d'être) inline constexpr static Real compute(const Real & E, const Real & nu) { return E / (2 * (1 + nu)); } }; /** * Specialisation λ(E, ν) */ template <> struct Converter { //! wrapped function (raison d'être) inline constexpr static Real compute(const Real & E, const Real & nu) { return E * nu / ((1 + nu) * (1 - 2 * nu)); } }; /** * Specialisation K(E, ν) */ template <> struct Converter { //! wrapped function (raison d'être) inline constexpr static Real compute(const Real & E, const Real & nu) { return E / (3 * (1 - 2 * nu)); } }; /** * Specialisation E(K, µ) */ template <> struct Converter { //! wrapped function (raison d'être) inline constexpr static Real compute(const Real & K, const Real & G) { return 9 * K * G / (3 * K + G); } }; /** * Specialisation ν(K, µ) */ template <> struct Converter { //! wrapped function (raison d'être) inline constexpr static Real compute(const Real & K, const Real & G) { return (3 * K - 2 * G) / (2 * (3 * K + G)); } }; /** * Specialisation E(λ, µ) */ template <> struct Converter { //! wrapped function (raison d'être) inline constexpr static Real compute(const Real & lambda, const Real & G) { return G * (3 * lambda + 2 * G) / (lambda + G); } }; + /** + * Specialisation λ(K, µ) + */ + template <> + struct Converter { + //! wrapped function (raison d'être) + inline constexpr static Real compute(const Real & K, const Real & mu) { + return K - 2. * mu / 3.; + } + }; + } // namespace internal /** * allows the conversion from any two distinct input moduli to a * chosen output modulus */ template inline constexpr Real convert_elastic_modulus(const Real & in1, const Real & in2) { // enforcing sanity static_assert((In1 != In2), "The input modulus types cannot be identical"); // enforcing independence from order in which moduli are supplied constexpr bool inverted{In1 > In2}; using Converter = std::conditional_t, internal::Converter>; if (inverted) { return Converter::compute(std::move(in2), std::move(in1)); } else { return Converter::compute(std::move(in1), std::move(in2)); } } //! static inline implementation of Hooke's law template struct Hooke { /** * compute Lamé's first constant * @param young: Young's modulus * @param poisson: Poisson's ratio */ inline static constexpr Real compute_lambda(const Real & young, const Real & poisson) { return convert_elastic_modulus(young, poisson); } /** * compute Lamé's second constant (i.e., shear modulus) * @param young: Young's modulus * @param poisson: Poisson's ratio */ inline static constexpr Real compute_mu(const Real & young, const Real & poisson) { return convert_elastic_modulus(young, poisson); } /** * compute the bulk modulus * @param young: Young's modulus * @param poisson: Poisson's ratio */ inline static constexpr Real compute_K(const Real & young, const Real & poisson) { return convert_elastic_modulus(young, poisson); } /** * compute the stiffness tensor * @param lambda: Lamé's first constant * @param mu: Lamé's second constant (i.e., shear modulus) */ inline static Eigen::TensorFixedSize> compute_C(const Real & lambda, const Real & mu) { return lambda * Tensors::outer(Tensors::I2(), Tensors::I2()) + 2 * mu * Tensors::I4S(); } /** * compute the stiffness tensor * @param lambda: Lamé's first constant * @param mu: Lamé's second constant (i.e., shear modulus) */ inline static T4Mat compute_C_T4(const Real & lambda, const Real & mu) { return lambda * Matrices::Itrac() + 2 * mu * Matrices::Isymm(); } /** * return stress * @param lambda: First Lamé's constant * @param mu: Second Lamé's constant (i.e. shear modulus) * @param E: Green-Lagrange or small strain tensor */ template inline static decltype(auto) evaluate_stress(const Real & lambda, const Real & mu, s_t && E) { return E.trace() * lambda * Strain_t::Identity() + 2 * mu * E; } /** * return stress and tangent stiffness * @param lambda: First Lamé's constant * @param mu: Second Lamé's constant (i.e. shear modulus) * @param E: Green-Lagrange or small strain tensor * @param C: stiffness tensor (Piola-Kirchhoff 2 (or σ) w.r.t to `E`) */ template inline static decltype(auto) evaluate_stress(const Real & lambda, const Real & mu, Tangent_t && C, s_t && E) { return std::make_tuple( std::move(evaluate_stress(lambda, mu, std::move(E))), std::move(C)); } }; + namespace internal { + + /* ---------------------------------------------------------------------- + */ + template + struct NumericalTangentHelper { + using T4_t = T4Mat; + using T2_t = Eigen::Matrix; + using T2_vec = Eigen::Map>; + + template + static inline T4_t compute(FunType && fun, + const Eigen::MatrixBase & strain, + Real delta); + }; + + /* ---------------------------------------------------------------------- + */ + template + template + auto NumericalTangentHelper::compute( + FunType && fun, const Eigen::MatrixBase & strain, Real delta) + -> T4_t { + static_assert((FinDif == FiniteDiff::forward) or + (FinDif == FiniteDiff::backward), + "Not implemented"); + T4_t tangent{T4_t::Zero()}; + + const T2_t fun_val{fun(strain)}; + for (Dim_t i{}; i < Dim * Dim; ++i) { + T2_t strain2{strain}; + T2_vec strain_vec{strain2.data()}; + switch (FinDif) { + case FiniteDiff::forward: { + strain_vec(i) += delta; + + T2_t del_f_del{(fun(strain2) - fun_val) / delta}; + + tangent.col(i) = T2_vec(del_f_del.data()); + break; + } + case FiniteDiff::backward: { + strain_vec(i) -= delta; + + T2_t del_f_del{(fun_val - fun(strain2)) / delta}; + + tangent.col(i) = T2_vec(del_f_del.data()); + break; + } + } + static_assert(Int(decltype(tangent.col(i))::SizeAtCompileTime) == + Int(T2_t::SizeAtCompileTime), + "wrong column size"); + } + return tangent; + } + + /* ---------------------------------------------------------------------- + */ + template + struct NumericalTangentHelper { + using T4_t = T4Mat; + using T2_t = Eigen::Matrix; + using T2_vec = Eigen::Map>; + + template + static inline T4_t compute(FunType && fun, + const Eigen::MatrixBase & strain, + Real delta) { + T4_t tangent{T4_t::Zero()}; + + for (Dim_t i{}; i < Dim * Dim; ++i) { + T2_t strain1{strain}; + T2_t strain2{strain}; + T2_vec strain1_vec{strain1.data()}; + T2_vec strain2_vec{strain2.data()}; + strain1_vec(i) += delta; + strain2_vec(i) -= delta; + + T2_t del_f_del{(fun(strain1).eval() - fun(strain2).eval()) / + (2 * delta)}; + + tangent.col(i) = T2_vec(del_f_del.data()); + static_assert(Int(decltype(tangent.col(i))::SizeAtCompileTime) == + Int(T2_t::SizeAtCompileTime), + "wrong column size"); + } + return tangent; + } + }; + + } // namespace internal + /** + * Helper function to numerically determine tangent, intended for + * testing, rather than as a replacement for analytical tangents + */ + template + inline T4Mat compute_numerical_tangent( + FunType && fun, const Eigen::MatrixBase & strain, Real delta) { + static_assert(Derived::RowsAtCompileTime == Dim, + "can't handle dynamic matrix"); + static_assert(Derived::ColsAtCompileTime == Dim, + "can't handle dynamic matrix"); + + using T2_t = Eigen::Matrix; + using T2_vec = Eigen::Map>; + + static_assert( + std::is_convertible>::value, + "Function argument 'fun' needs to be a function taking " + "one second-rank tensor as input and returning a " + "second-rank tensor"); + + static_assert(Dim_t(T2_t::SizeAtCompileTime) == + Dim_t(T2_vec::SizeAtCompileTime), + "wrong map size"); + return internal::NumericalTangentHelper::compute( + std::forward(fun), strain, delta); + } + } // namespace MatTB } // namespace muSpectre #endif // SRC_MATERIALS_MATERIALS_TOOLBOX_HH_ diff --git a/src/materials/stress_transformations.hh b/src/materials/stress_transformations.hh new file mode 100644 index 0000000..50b52df --- /dev/null +++ b/src/materials/stress_transformations.hh @@ -0,0 +1,67 @@ +/** + * @file stress_transformations.hh + * + * @author Till Junge + * + * @date 29 Oct 2018 + * + * @brief isolation of stress conversions for quicker compilation + * + * Copyright © 2018 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with µSpectre; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef SRC_MATERIALS_STRESS_TRANSFORMATIONS_HH_ +#define SRC_MATERIALS_STRESS_TRANSFORMATIONS_HH_ + +namespace muSpectre { + + namespace MatTB { + + /* ---------------------------------------------------------------------- */ + //! set of functions returning an expression for PK2 stress based on + template + decltype(auto) PK1_stress(Strain_t && strain, Stress_t && stress) { + constexpr Dim_t dim{EigenCheck::tensor_dim::value}; + static_assert((dim == EigenCheck::tensor_dim::value), + "Stress and strain tensors have differing dimensions"); + return internal::PK1_stress::compute( + std::forward(strain), std::forward(stress)); + } + + /* ---------------------------------------------------------------------- */ + //! set of functions returning an expression for PK2 stress based on + template + decltype(auto) PK1_stress(Strain_t && strain, Stress_t && stress, + Tangent_t && tangent) { + constexpr Dim_t dim{EigenCheck::tensor_dim::value}; + static_assert((dim == EigenCheck::tensor_dim::value), + "Stress and strain tensors have differing dimensions"); + static_assert((dim == EigenCheck::tensor_4_dim::value), + "Stress and tangent tensors have differing dimensions"); + + return internal::PK1_stress::compute( + std::forward(strain), std::forward(stress), + std::forward(tangent)); + } + + } // namespace MatTB + +} // namespace muSpectre +#endif // SRC_MATERIALS_STRESS_TRANSFORMATIONS_HH_ diff --git a/src/materials/stress_transformations_Kirchhoff.hh b/src/materials/stress_transformations_Kirchhoff.hh new file mode 100644 index 0000000..7516494 --- /dev/null +++ b/src/materials/stress_transformations_Kirchhoff.hh @@ -0,0 +1,35 @@ +/** + * @file stress_transformations_Kirchhoff.hh + * + * @author Till Junge + * + * @date 29 Oct 2018 + * + * @brief Stress conversions for Kirchhoff stress (τ) + * + * Copyright © 2018 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with µSpectre; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef SRC_MATERIALS_STRESS_TRANSFORMATIONS_KIRCHHOFF_HH_ +#define SRC_MATERIALS_STRESS_TRANSFORMATIONS_KIRCHHOFF_HH_ + +#include "materials/stress_transformations_default_case.hh" +#include "materials/stress_transformations_Kirchhoff_impl.hh" +#include "stress_transformations.hh" + +#endif /* SRC_MATERIALS_STRESS_TRANSFORMATIONS_KIRCHHOFF_HH_ */ diff --git a/src/materials/stress_transformations_Kirchhoff_impl.hh b/src/materials/stress_transformations_Kirchhoff_impl.hh new file mode 100644 index 0000000..68e57f4 --- /dev/null +++ b/src/materials/stress_transformations_Kirchhoff_impl.hh @@ -0,0 +1,113 @@ +/** + * @file stress_transformations_Kirchhoff_impl.hh + * + * @author Till Junge + * + * @date 29 Oct 2018 + * + * @brief Implementation of stress conversions for Kirchhoff stress + * + * Copyright © 2018 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with µSpectre; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef SRC_MATERIALS_STRESS_TRANSFORMATIONS_KIRCHHOFF_IMPL_HH_ +#define SRC_MATERIALS_STRESS_TRANSFORMATIONS_KIRCHHOFF_IMPL_HH_ + +namespace muSpectre { + + namespace MatTB { + + namespace internal { + + /* ---------------------------------------------------------------------- + */ + /** + * Specialisation for the case where we get Kirchhoff stress (τ) + */ + template + struct PK1_stress + : public PK1_stress { + //! returns the converted stress + template + inline static decltype(auto) compute(Strain_t && F, Stress_t && tau) { + return tau * F.inverse().transpose(); + } + }; + + /* ---------------------------------------------------------------------- + */ + /** + * Specialisation for the case where we get Kirchhoff stress (τ) derived + * with respect to Gradient + */ + template + struct PK1_stress + : public PK1_stress { + //! short-hand + using Parent = PK1_stress; + using Parent::compute; + + //! returns the converted stress and stiffness + template + inline static decltype(auto) compute(Strain_t && F, Stress_t && tau, + Tangent_t && C) { + using T4_t = T4Mat; + using Mat_t = Eigen::Matrix; + Mat_t F_inv{F.inverse()}; + T4_t increment{T4_t::Zero()}; + for (int i{0}; i < Dim; ++i) { + const int k{i}; + for (int j{0}; j < Dim; ++j) { + const int a{j}; + for (int l{0}; l < Dim; ++l) { + get(increment, i, j, k, l) -= tau(a, l); + } + } + } + T4_t Ka{C + increment}; + + T4_t Kb{T4_t::Zero()}; + for (int i{0}; i < Dim; ++i) { + for (int j{0}; j < Dim; ++j) { + for (int k{0}; k < Dim; ++k) { + for (int l{0}; l < Dim; ++l) { + for (int a{0}; a < Dim; ++a) { + for (int b{0}; b < Dim; ++b) { + get(Kb, j, i, k, l) += + F_inv(i, a) * get(Ka, a, j, k, b) * F_inv(l, b); + } + } + } + } + } + } + Mat_t P = tau * F_inv.transpose(); + return std::make_tuple(std::move(P), std::move(Kb)); + } + }; + + } // namespace internal + + } // namespace MatTB + +} // namespace muSpectre + +#endif // SRC_MATERIALS_STRESS_TRANSFORMATIONS_KIRCHHOFF_IMPL_HH_ diff --git a/src/materials/stress_transformations_PK1.hh b/src/materials/stress_transformations_PK1.hh new file mode 100644 index 0000000..702c708 --- /dev/null +++ b/src/materials/stress_transformations_PK1.hh @@ -0,0 +1,35 @@ +/** + * @file stress_transformations_PK1.hh + * + * @author Till Junge + * + * @date 29 Oct 2018 + * + * @brief stress conversion for PK1 stress + * + * Copyright © 2018 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with µSpectre; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef SRC_MATERIALS_STRESS_TRANSFORMATIONS_PK1_HH_ +#define SRC_MATERIALS_STRESS_TRANSFORMATIONS_PK1_HH_ + +#include "materials/stress_transformations_default_case.hh" +#include "materials/stress_transformations_PK1_impl.hh" +#include "materials/stress_transformations.hh" + +#endif // SRC_MATERIALS_STRESS_TRANSFORMATIONS_PK1_HH_ diff --git a/src/materials/stress_transformations_PK1_impl.hh b/src/materials/stress_transformations_PK1_impl.hh new file mode 100644 index 0000000..f8b1973 --- /dev/null +++ b/src/materials/stress_transformations_PK1_impl.hh @@ -0,0 +1,88 @@ +/** + * @file stress_transformations_PK1_impl.hh + * + * @author Till Junge + * + * @date 29 Oct 2018 + * + * @brief implementation of stress conversion for PK1 stress + * + * Copyright © 2018 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with µSpectre; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef SRC_MATERIALS_STRESS_TRANSFORMATIONS_PK1_IMPL_HH_ +#define SRC_MATERIALS_STRESS_TRANSFORMATIONS_PK1_IMPL_HH_ + +#include "common/common.hh" +#include "common/tensor_algebra.hh" +#include "common/T4_map_proxy.hh" + +namespace muSpectre { + + namespace MatTB { + + namespace internal { + + /* ---------------------------------------------------------------------- + */ + /** Specialisation for the transparent case, where we already + have PK1 stress + **/ + template + struct PK1_stress + : public PK1_stress { + //! returns the converted stress + template + inline static decltype(auto) compute(Strain_t && /*dummy*/, + Stress_t && P) { + return std::forward(P); + } + }; + + /* ---------------------------------------------------------------------- + */ + /** Specialisation for the transparent case, where we already have PK1 + stress *and* stiffness is given with respect to the transformation + gradient + **/ + template + struct PK1_stress + : public PK1_stress { + //! base class + using Parent = + PK1_stress; + using Parent::compute; + + //! returns the converted stress and stiffness + template + inline static decltype(auto) compute(Strain_t && /*dummy*/, + Stress_t && P, Tangent_t && K) { + return std::make_tuple(std::forward(P), + std::forward(K)); + } + }; + + } // namespace internal + + } // namespace MatTB + +} // namespace muSpectre + +#endif // SRC_MATERIALS_STRESS_TRANSFORMATIONS_PK1_IMPL_HH_ diff --git a/src/materials/stress_transformations_PK2.hh b/src/materials/stress_transformations_PK2.hh new file mode 100644 index 0000000..701f5ab --- /dev/null +++ b/src/materials/stress_transformations_PK2.hh @@ -0,0 +1,35 @@ +/** + * @file stress_transformations_PK2.hh + * + * @author Till Junge + * + * @date 29 Oct 2018 + * + * @brief stress conversions for PK2 stress + * + * Copyright © 2018 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with µSpectre; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef SRC_MATERIALS_STRESS_TRANSFORMATIONS_PK2_HH_ +#define SRC_MATERIALS_STRESS_TRANSFORMATIONS_PK2_HH_ + +#include "materials/stress_transformations_default_case.hh" +#include "materials/stress_transformations_PK2_impl.hh" +#include "materials/stress_transformations.hh" + +#endif // SRC_MATERIALS_STRESS_TRANSFORMATIONS_PK2_HH_ diff --git a/src/materials/stress_transformations_PK2_impl.hh b/src/materials/stress_transformations_PK2_impl.hh new file mode 100644 index 0000000..b5d806b --- /dev/null +++ b/src/materials/stress_transformations_PK2_impl.hh @@ -0,0 +1,151 @@ +/** + * @file stress_transformations_PK2_impl.hh + * + * @author Till Junge + * + * @date 29 Oct 2018 + * + * @brief Implementation of stress conversions for PK2 stress + * + * Copyright © 2018 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with µSpectre; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef SRC_MATERIALS_STRESS_TRANSFORMATIONS_PK2_IMPL_HH_ +#define SRC_MATERIALS_STRESS_TRANSFORMATIONS_PK2_IMPL_HH_ + +namespace muSpectre { + + namespace MatTB { + + namespace internal { + + /* ---------------------------------------------------------------------- + */ + /** + * Specialisation for the case where we get material stress + * (Piola-Kirchhoff-2, PK2) + */ + template + struct PK1_stress + : public PK1_stress { + //! returns the converted stress + template + inline static decltype(auto) compute(Strain_t && F, Stress_t && S) { + return F * S; + } + }; + + /* ---------------------------------------------------------------------- + */ + /** + * Specialisation for the case where we get material stress + * (Piola-Kirchhoff-2, PK2) derived with respect to + * Green-Lagrange strain + */ + template + struct PK1_stress + : public PK1_stress { + //! base class + using Parent = + PK1_stress; + using Parent::compute; + + //! returns the converted stress and stiffness + template + inline static decltype(auto) compute(Strain_t && F, Stress_t && S, + Tangent_t && C) { + using T4 = typename std::remove_reference_t::PlainObject; + using Tmap = T4MatMap; + T4 K; + Tmap Kmap{K.data()}; + K.setZero(); + + for (int i = 0; i < Dim; ++i) { + for (int m = 0; m < Dim; ++m) { + for (int n = 0; n < Dim; ++n) { + get(Kmap, i, m, i, n) += S(m, n); + for (int j = 0; j < Dim; ++j) { + for (int r = 0; r < Dim; ++r) { + for (int s = 0; s < Dim; ++s) { + get(Kmap, i, m, j, n) += + F(i, r) * get(C, r, m, s, n) * (F(j, s)); + } + } + } + } + } + } + auto && P = + compute(std::forward(F), std::forward(S)); + return std::make_tuple(std::move(P), std::move(K)); + } + }; + + /* ---------------------------------------------------------------------- + */ + /** + * Specialisation for the case where we get material stress + * (Piola-Kirchhoff-2, PK2) derived with respect to + * the placement Gradient (F) + */ + template + struct PK1_stress + : public PK1_stress { + //! base class + using Parent = + PK1_stress; + using Parent::compute; + + //! returns the converted stress and stiffness + template + inline static decltype(auto) compute(Strain_t && F, Stress_t && S, + Tangent_t && C) { + using T4 = typename std::remove_reference_t::PlainObject; + using Tmap = T4MatMap; + T4 K; + Tmap Kmap{K.data()}; + K.setZero(); + + for (int i = 0; i < Dim; ++i) { + for (int m = 0; m < Dim; ++m) { + for (int n = 0; n < Dim; ++n) { + get(Kmap, i, m, i, n) += S(m, n); + for (int j = 0; j < Dim; ++j) { + for (int r = 0; r < Dim; ++r) { + get(Kmap, i, m, j, n) += F(i, r) * get(C, r, m, j, n); + } + } + } + } + } + auto && P = + compute(std::forward(F), std::forward(S)); + return std::make_tuple(std::move(P), std::move(K)); + } + }; + + } // namespace internal + + } // namespace MatTB + +} // namespace muSpectre + +#endif // SRC_MATERIALS_STRESS_TRANSFORMATIONS_PK2_IMPL_HH_ diff --git a/src/materials/stress_transformations_default_case.hh b/src/materials/stress_transformations_default_case.hh new file mode 100644 index 0000000..0f53fd1 --- /dev/null +++ b/src/materials/stress_transformations_default_case.hh @@ -0,0 +1,79 @@ +/** + * @file stress_transformations_default_case.hh + * + * @author Till Junge + * + * @date 29 Oct 2018 + * + * @brief default structure for stress conversions + * + * Copyright © 2018 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with µSpectre; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef SRC_MATERIALS_STRESS_TRANSFORMATIONS_DEFAULT_CASE_HH_ +#define SRC_MATERIALS_STRESS_TRANSFORMATIONS_DEFAULT_CASE_HH_ + +#include "common/common.hh" +#include "common/T4_map_proxy.hh" + +namespace muSpectre { + + namespace MatTB { + + namespace internal { + + /** Structure for functions returning PK1 stress from other stress + *measures + **/ + template + struct PK1_stress { + //! returns the converted stress + template + inline static decltype(auto) compute(Strain_t && /*strain*/, + Stress_t && /*stress*/) { + // the following test always fails to generate a compile-time error + static_assert((StressM == StressMeasure::Cauchy) && + (StressM == StressMeasure::PK1), + "The requested Stress conversion is not implemented. " + "You either made a programming mistake or need to " + "implement it as a specialisation of this function. " + "See PK2stress for an example."); + } + + //! returns the converted stress and stiffness + template + inline static decltype(auto) compute(Strain_t && /*strain*/, + Stress_t && /*stress*/, + Tangent_t && /*stiffness*/) { + // the following test always fails to generate a compile-time error + static_assert((StressM == StressMeasure::Cauchy) && + (StressM == StressMeasure::PK1), + "The requested Stress conversion is not implemented. " + "You either made a programming mistake or need to " + "implement it as a specialisation of this function. " + "See PK2stress for an example."); + } + }; + + } // namespace internal + + } // namespace MatTB + +} // namespace muSpectre + +#endif // SRC_MATERIALS_STRESS_TRANSFORMATIONS_DEFAULT_CASE_HH_ diff --git a/src/solver/deprecated_solver_cg.hh b/src/solver/deprecated_solver_cg.hh index 0af0414..2432b25 100644 --- a/src/solver/deprecated_solver_cg.hh +++ b/src/solver/deprecated_solver_cg.hh @@ -1,122 +1,120 @@ /** * @file deprecated_solver_cg.hh * * @author Till Junge * * @date 20 Dec 2017 * * @brief class for a simple implementation of a conjugate gradient * solver. This follows algorithm 5.2 in Nocedal's Numerical * Optimization (p 112) * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_SOLVER_DEPRECATED_SOLVER_CG_HH_ #define SRC_SOLVER_DEPRECATED_SOLVER_CG_HH_ #include "solver/deprecated_solver_base.hh" #include "common/communicator.hh" #include "common/field.hh" #include namespace muSpectre { /** * implements the `muSpectre::DeprecatedSolverBase` interface using a * conjugate gradient solver. This particular class is useful for * trouble shooting, as it can be made very verbose, but for * production runs, it is probably better to use * `muSpectre::DeprecatedSolverCGEigen`. */ template class DeprecatedSolverCG : public DeprecatedSolverBase { public: using Parent = DeprecatedSolverBase; //!< base class //! Input vector for solvers using SolvVectorIn = typename Parent::SolvVectorIn; //! Input vector for solvers using SolvVectorInC = typename Parent::SolvVectorInC; //! Output vector for solvers using SolvVectorOut = typename Parent::SolvVectorOut; using Cell_t = typename Parent::Cell_t; //!< cell type using Ccoord = typename Parent::Ccoord; //!< cell coordinates type //! kind of tangent that is required using Tg_req_t = typename Parent::TangentRequirement; //! cg only needs to handle fields that look like strain and stress using Field_t = TensorField; //! conjugate gradient needs directional stiffness constexpr static Tg_req_t tangent_requirement{Tg_req_t::NeedEffect}; //! Default constructor DeprecatedSolverCG() = delete; //! Constructor with domain resolutions, etc, DeprecatedSolverCG(Cell_t & cell, Real tol, Uint maxiter = 0, bool verbose = false); //! Copy constructor DeprecatedSolverCG(const DeprecatedSolverCG & other) = delete; //! Move constructor DeprecatedSolverCG(DeprecatedSolverCG && other) = default; //! Destructor virtual ~DeprecatedSolverCG() = default; //! Copy assignment operator DeprecatedSolverCG & operator=(const DeprecatedSolverCG & other) = delete; //! Move assignment operator DeprecatedSolverCG & operator=(DeprecatedSolverCG && other) = default; bool has_converged() const final { return this->converged; } //! actual solver void solve(const Field_t & rhs, Field_t & x); // this simplistic implementation has no initialisation phase so the default // is ok SolvVectorOut solve(const SolvVectorInC rhs, SolvVectorIn x_0) final; std::string name() const final { return "CG"; } protected: //! returns `muSpectre::Tg_req_t::NeedEffect` Tg_req_t get_tangent_req() const final; Field_t & r_k; //!< residual Field_t & p_k; //!< search direction Field_t & Ap_k; //!< effect of tangent on search direction bool converged{false}; //!< whether the solver has converged - - private: }; } // namespace muSpectre #endif // SRC_SOLVER_DEPRECATED_SOLVER_CG_HH_ diff --git a/src/solver/solver_cg.hh b/src/solver/solver_cg.hh index eb613fb..3813e28 100644 --- a/src/solver/solver_cg.hh +++ b/src/solver/solver_cg.hh @@ -1,107 +1,105 @@ /** * file solver_cg.hh * * @author Till Junge * * @date 24 Apr 2018 * * @brief class fo a simple implementation of a conjugate gradient solver. * This follows algorithm 5.2 in Nocedal's Numerical Optimization * (p 112) * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_SOLVER_SOLVER_CG_HH_ #define SRC_SOLVER_SOLVER_CG_HH_ #include "solver/solver_base.hh" namespace muSpectre { /** * implements the `muSpectre::SolverBase` interface using a * conjugate gradient solver. This particular class is useful for * trouble shooting, as it can be made very verbose, but for * production runs, it is probably better to use * `muSpectre::SolverCGEigen`. */ class SolverCG : public SolverBase { public: using Parent = SolverBase; //!< standard short-hand for base class //! for storage of fields using Vector_t = Parent::Vector_t; //! Input vector for solvers using Vector_ref = Parent::Vector_ref; //! Input vector for solvers using ConstVector_ref = Parent::ConstVector_ref; //! Output vector for solvers using Vector_map = Parent::Vector_map; //! Default constructor SolverCG() = delete; //! Copy constructor SolverCG(const SolverCG & other) = delete; /** * Constructor takes a Cell, tolerance, max number of iterations * and verbosity flag as input */ SolverCG(Cell & cell, Real tol, Uint maxiter, bool verbose = false); //! Move constructor SolverCG(SolverCG && other) = default; //! Destructor virtual ~SolverCG() = default; //! Copy assignment operator SolverCG & operator=(const SolverCG & other) = delete; //! Move assignment operator SolverCG & operator=(SolverCG && other) = default; //! initialisation does not need to do anything in this case void initialise() final{}; //! returns the solver's name std::string get_name() const final { return "CG"; } //! the actual solver Vector_map solve(const ConstVector_ref rhs) final; protected: Vector_t r_k; //!< residual Vector_t p_k; //!< search direction Vector_t Ap_k; //!< directional stiffness Vector_t x_k; //!< current solution - - private: }; } // namespace muSpectre #endif // SRC_SOLVER_SOLVER_CG_HH_ diff --git a/src/solver/solvers.cc b/src/solver/solvers.cc index 29be80a..cd072e8 100644 --- a/src/solver/solvers.cc +++ b/src/solver/solvers.cc @@ -1,407 +1,480 @@ /** * file solvers.cc * * @author Till Junge * * @date 24 Apr 2018 * * @brief implementation of dynamic newton-cg solver * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ -#include "solvers.hh" +#include "solver/solvers.hh" +#include "common/iterators.hh" #include #include namespace muSpectre { - //----------------------------------------------------------------------------// + Eigen::IOFormat format(Eigen::FullPrecision, 0, ", ", ",\n", "[", "]", "[", + "]"); + + //--------------------------------------------------------------------------// std::vector newton_cg(Cell & cell, const LoadSteps_t & load_steps, SolverBase & solver, Real newton_tol, Real equil_tol, Dim_t verbose) { const Communicator & comm = cell.get_communicator(); using Vector_t = Eigen::Matrix; using Matrix_t = Eigen::Matrix; // Corresponds to symbol δF or δε Vector_t incrF(cell.get_nb_dof()); // field to store the rhs for cg calculations Vector_t rhs(cell.get_nb_dof()); solver.initialise(); size_t count_width{}; const auto form{cell.get_formulation()}; std::string strain_symb{}; if (verbose > 0 && comm.rank() == 0) { // setup of algorithm 5.2 in Nocedal, Numerical Optimization (p. 111) std::cout << "Newton-" << solver.get_name() << " for "; switch (form) { case Formulation::small_strain: { strain_symb = "ε"; std::cout << "small"; break; } case Formulation::finite_strain: { strain_symb = "F"; std::cout << "finite"; break; } default: throw SolverError("unknown formulation"); break; } std::cout << " strain with" << std::endl << "newton_tol = " << newton_tol << ", cg_tol = " << solver.get_tol() - << " maxiter = " << solver.get_maxiter() << " and Δ" - << strain_symb << " =" << std::endl; - for (auto && tup : akantu::enumerate(load_steps)) { - auto && counter{std::get<0>(tup)}; - auto && grad{std::get<1>(tup)}; - std::cout << "Step " << counter + 1 << ":" << std::endl - << grad << std::endl; - } + << " maxiter = " << solver.get_maxiter() << " and " + << strain_symb << " from " << strain_symb << "₁ =" << std::endl + << load_steps.front() << std::endl + << " to " << strain_symb << "ₙ =" << std::endl + << load_steps.back() << std::endl + << "in increments of Δ" << strain_symb << " =" << std::endl + << (load_steps.back() - load_steps.front()) / load_steps.size() + << std::endl; count_width = size_t(std::log10(solver.get_maxiter())) + 1; } auto shape{cell.get_strain_shape()}; switch (form) { case Formulation::finite_strain: { cell.set_uniform_strain(Matrix_t::Identity(shape[0], shape[1])); for (const auto & delF : load_steps) { if (not((delF.rows() == shape[0]) and (delF.cols() == shape[1]))) { std::stringstream err{}; err << "Load increments need to be given in " << shape[0] << "×" << shape[1] << " matrices, but I got a " << delF.rows() << "×" << delF.cols() << " matrix:" << std::endl << delF; throw SolverError(err.str()); } } break; } case Formulation::small_strain: { cell.set_uniform_strain(Matrix_t::Zero(shape[0], shape[1])); for (const auto & delF : load_steps) { if (not((delF.rows() == shape[0]) and (delF.cols() == shape[1]))) { std::stringstream err{}; err << "Load increments need to be given in " << shape[0] << "×" << shape[1] << " matrices, but I got a " << delF.rows() << "×" << delF.cols() << " matrix:" << std::endl << delF; throw SolverError(err.str()); } if (not check_symmetry(delF)) { throw SolverError("all Δε must be symmetric!"); } } break; } default: throw SolverError("Unknown strain measure"); break; } // initialise return value std::vector ret_val{}; // storage for the previous mean strain (to compute ΔF or Δε) Matrix_t previous_macro_strain{load_steps.back().Zero(shape[0], shape[1])}; auto F{cell.get_strain_vector()}; //! incremental loop - for (const auto & macro_strain : load_steps) { + for (const auto & tup : akantu::enumerate(load_steps)) { + const auto & strain_step{std::get<0>(tup)}; + const auto & macro_strain{std::get<1>(tup)}; + if ((verbose > 0) and (comm.rank() == 0)) { + std::cout << "at Load step " << std::setw(count_width) + << strain_step + 1 << std::endl; + } using StrainMap_t = RawFieldMap>; for (auto && strain : StrainMap_t(F, shape[0], shape[1])) { strain += macro_strain - previous_macro_strain; } std::string message{"Has not converged"}; Real incr_norm{2 * newton_tol}, grad_norm{1}; Real stress_norm{2 * equil_tol}; bool has_converged{false}; auto convergence_test = [&incr_norm, &grad_norm, &newton_tol, &stress_norm, &equil_tol, &message, &has_converged]() { bool incr_test = incr_norm / grad_norm <= newton_tol; bool stress_test = stress_norm < equil_tol; if (incr_test) { message = "Residual tolerance reached"; } else if (stress_test) { message = "Reached stress divergence tolerance"; } has_converged = incr_test || stress_test; return has_converged; }; Uint newt_iter{0}; for (; newt_iter < solver.get_maxiter() && !has_converged; ++newt_iter) { // obtain material response auto res_tup{cell.evaluate_stress_tangent()}; auto & P{std::get<0>(res_tup)}; rhs = -P; cell.apply_projection(rhs); stress_norm = std::sqrt(comm.sum(rhs.squaredNorm())); if (convergence_test()) { break; } - //! this is a potentially avoidable copy TODO: check this out - incrF = solver.solve(rhs); + try { + incrF = solver.solve(rhs); + } catch (ConvergenceError & error) { + std::stringstream err{}; + err << "Failure at load step " << strain_step + 1 << " of " + << load_steps.size() << ". In Newton-Raphson step " << newt_iter + << ":" << std::endl + << error.what() << std::endl + << "The applied boundary condition is Δ" << strain_symb << " =" + << std::endl + << macro_strain << std::endl + << "and the load increment is Δ" << strain_symb << " =" + << std::endl + << macro_strain - previous_macro_strain << std::endl; + throw ConvergenceError(err.str()); + } F += incrF; incr_norm = std::sqrt(comm.sum(incrF.squaredNorm())); grad_norm = std::sqrt(comm.sum(F.squaredNorm())); - if ((verbose > 0) and (comm.rank() == 0)) { + if ((verbose > 1) and (comm.rank() == 0)) { std::cout << "at Newton step " << std::setw(count_width) << newt_iter << ", |δ" << strain_symb << "|/|Δ" << strain_symb << "| = " << std::setw(17) << incr_norm / grad_norm << ", tol = " << newton_tol << std::endl; if (verbose - 1 > 1) { std::cout << "<" << strain_symb << "> =" << std::endl << StrainMap_t(F, shape[0], shape[1]).mean() << std::endl; } } convergence_test(); } + if (newt_iter == solver.get_maxiter()) { + std::stringstream err{}; + err << "Failure at load step " << strain_step + 1 << " of " + << load_steps.size() << ". Newton-Raphson failed to converge. " + << "The applied boundary condition is Δ" << strain_symb << " =" + << std::endl + << macro_strain << std::endl + << "and the load increment is Δ" << strain_symb << " =" << std::endl + << macro_strain - previous_macro_strain << std::endl; + throw ConvergenceError(err.str()); + } // update previous macroscopic strain previous_macro_strain = macro_strain; // store results ret_val.emplace_back(OptimizeResult{ F, cell.get_stress_vector(), convergence_test(), Int(convergence_test()), message, newt_iter, solver.get_counter()}); // store history variables for next load increment cell.save_history_variables(); } return ret_val; } //----------------------------------------------------------------------------// std::vector de_geus(Cell & cell, const LoadSteps_t & load_steps, SolverBase & solver, Real newton_tol, Real equil_tol, Dim_t verbose) { const Communicator & comm = cell.get_communicator(); using Vector_t = Eigen::Matrix; using Matrix_t = Eigen::Matrix; // Corresponds to symbol δF or δε Vector_t incrF(cell.get_nb_dof()); // Corresponds to symbol ΔF or Δε Vector_t DeltaF(cell.get_nb_dof()); // field to store the rhs for cg calculations Vector_t rhs(cell.get_nb_dof()); solver.initialise(); size_t count_width{}; const auto form{cell.get_formulation()}; std::string strain_symb{}; if (verbose > 0 && comm.rank() == 0) { // setup of algorithm 5.2 in Nocedal, Numerical Optimization (p. 111) std::cout << "de Geus-" << solver.get_name() << " for "; switch (form) { case Formulation::small_strain: { strain_symb = "ε"; std::cout << "small"; break; } case Formulation::finite_strain: { strain_symb = "F"; std::cout << "finite"; break; } default: throw SolverError("unknown formulation"); break; } std::cout << " strain with" << std::endl << "newton_tol = " << newton_tol << ", cg_tol = " << solver.get_tol() - << " maxiter = " << solver.get_maxiter() << " and Δ" - << strain_symb << " =" << std::endl; - for (auto && tup : akantu::enumerate(load_steps)) { - auto && counter{std::get<0>(tup)}; - auto && grad{std::get<1>(tup)}; - std::cout << "Step " << counter + 1 << ":" << std::endl - << grad << std::endl; - } + << " maxiter = " << solver.get_maxiter() << " and " + << strain_symb << " from " << strain_symb << "₁ =" << std::endl + << load_steps.front() << std::endl + << " to " << strain_symb << "ₙ =" << std::endl + << load_steps.back() << std::endl + << "in increments of Δ" << strain_symb << " =" << std::endl + << (load_steps.back() - load_steps.front()) / load_steps.size() + << std::endl; count_width = size_t(std::log10(solver.get_maxiter())) + 1; } auto shape{cell.get_strain_shape()}; + Matrix_t default_strain_val{}; switch (form) { case Formulation::finite_strain: { cell.set_uniform_strain(Matrix_t::Identity(shape[0], shape[1])); for (const auto & delF : load_steps) { auto rows = delF.rows(); auto cols = delF.cols(); if (not((rows == shape[0]) and (cols == shape[1]))) { std::stringstream err{}; err << "Load increments need to be given in " << shape[0] << "×" << shape[1] << " matrices, but I got a " << delF.rows() << "×" << delF.cols() << " matrix:" << std::endl << delF; throw SolverError(err.str()); } } break; } case Formulation::small_strain: { cell.set_uniform_strain(Matrix_t::Zero(shape[0], shape[1])); for (const auto & delF : load_steps) { if (not((delF.rows() == shape[0]) and (delF.cols() == shape[1]))) { std::stringstream err{}; err << "Load increments need to be given in " << shape[0] << "×" << shape[1] << " matrices, but I got a " << delF.rows() << "×" << delF.cols() << " matrix:" << std::endl << delF; throw SolverError(err.str()); } if (not check_symmetry(delF)) { throw SolverError("all Δε must be symmetric!"); } } break; } default: throw SolverError("Unknown strain measure"); break; } // initialise return value std::vector ret_val{}; // storage for the previous mean strain (to compute ΔF or Δε) Matrix_t previous_macro_strain{load_steps.back().Zero(shape[0], shape[1])}; auto F{cell.get_strain_vector()}; //! incremental loop - for (const auto & macro_strain : load_steps) { + for (const auto & tup : akantu::enumerate(load_steps)) { + const auto & strain_step{std::get<0>(tup)}; + const auto & macro_strain{std::get<1>(tup)}; using StrainMap_t = RawFieldMap>; + if ((verbose > 0) and (comm.rank() == 0)) { + std::cout << "at Load step " << std::setw(count_width) + << strain_step + 1 << ", " << strain_symb << " =" << std::endl + << (macro_strain + default_strain_val).format(format) + << std::endl; + } std::string message{"Has not converged"}; Real incr_norm{2 * newton_tol}, grad_norm{1}; Real stress_norm{2 * equil_tol}; bool has_converged{false}; auto convergence_test = [&incr_norm, &grad_norm, &newton_tol, &stress_norm, &equil_tol, &message, &has_converged]() { bool incr_test = incr_norm / grad_norm <= newton_tol; bool stress_test = stress_norm < equil_tol; if (incr_test) { message = "Residual tolerance reached"; } else if (stress_test) { message = "Reached stress divergence tolerance"; } has_converged = incr_test || stress_test; return has_converged; }; Uint newt_iter{0}; - for (; newt_iter < solver.get_maxiter() && !has_converged; ++newt_iter) { + for (; ((newt_iter < solver.get_maxiter()) and (!has_converged)) or + (newt_iter < 2); + ++newt_iter) { // obtain material response auto res_tup{cell.evaluate_stress_tangent()}; auto & P{std::get<0>(res_tup)}; - if (newt_iter == 0) { - for (auto && strain : StrainMap_t(DeltaF, shape[0], shape[1])) { - strain = macro_strain - previous_macro_strain; - } - rhs = -cell.evaluate_projected_directional_stiffness(DeltaF); - stress_norm = std::sqrt(comm.sum(rhs.matrix().squaredNorm())); - if (convergence_test()) { - break; - } - incrF = solver.solve(rhs); - F += DeltaF; - } else { - rhs = -P; - cell.apply_projection(rhs); - stress_norm = std::sqrt(comm.sum(rhs.matrix().squaredNorm())); - if (convergence_test()) { - break; + try { + if (newt_iter == 0) { + for (auto && strain : StrainMap_t(DeltaF, shape[0], shape[1])) { + strain = macro_strain - previous_macro_strain; + } + rhs = -cell.evaluate_projected_directional_stiffness(DeltaF); + F += DeltaF; + stress_norm = std::sqrt(comm.sum(rhs.matrix().squaredNorm())); + if (stress_norm < equil_tol) { + incrF.setZero(); + } else { + incrF = solver.solve(rhs); + } + } else { + rhs = -P; + cell.apply_projection(rhs); + stress_norm = std::sqrt(comm.sum(rhs.matrix().squaredNorm())); + if (stress_norm < equil_tol) { + incrF.setZero(); + } else { + incrF = solver.solve(rhs); + } } - incrF = solver.solve(rhs); + } catch (ConvergenceError & error) { + std::stringstream err{}; + err << "Failure at load step " << strain_step + 1 << " of " + << load_steps.size() << ". In Newton-Raphson step " << newt_iter + << ":" << std::endl + << error.what() << std::endl + << "The applied boundary condition is Δ" << strain_symb << " =" + << std::endl + << macro_strain << std::endl + << "and the load increment is Δ" << strain_symb << " =" + << std::endl + << macro_strain - previous_macro_strain << std::endl; + throw ConvergenceError(err.str()); } F += incrF; incr_norm = std::sqrt(comm.sum(incrF.squaredNorm())); grad_norm = std::sqrt(comm.sum(F.squaredNorm())); - if ((verbose > 0) and // - (comm.rank() == 0)) { + if ((verbose > 0) and (comm.rank() == 0)) { std::cout << "at Newton step " << std::setw(count_width) << newt_iter << ", |δ" << strain_symb << "|/|Δ" << strain_symb << "| = " << std::setw(17) << incr_norm / grad_norm << ", tol = " << newton_tol << std::endl; if (verbose - 1 > 1) { std::cout << "<" << strain_symb << "> =" << std::endl << StrainMap_t(F, shape[0], shape[1]).mean() << std::endl; } } convergence_test(); } + if (newt_iter == solver.get_maxiter()) { + std::stringstream err{}; + err << "Failure at load step " << strain_step + 1 << " of " + << load_steps.size() << ". Newton-Raphson failed to converge. " + << "The applied boundary condition is Δ" << strain_symb << " =" + << std::endl + << macro_strain << std::endl + << "and the load increment is Δ" << strain_symb << " =" << std::endl + << macro_strain - previous_macro_strain << std::endl; + throw ConvergenceError(err.str()); + } // update previous macroscopic strain previous_macro_strain = macro_strain; // store results ret_val.emplace_back(OptimizeResult{ F, cell.get_stress_vector(), convergence_test(), Int(convergence_test()), message, newt_iter, solver.get_counter()}); // store history variables for next load increment cell.save_history_variables(); } return ret_val; } } // namespace muSpectre diff --git a/src/solver/solvers.hh b/src/solver/solvers.hh index 0e074f6..1b6f1ec 100644 --- a/src/solver/solvers.hh +++ b/src/solver/solvers.hh @@ -1,96 +1,96 @@ /** * file solvers.hh * * @author Till Junge * * @date 24 Apr 2018 * * @brief Free functions for solving rve problems * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef SRC_SOLVER_SOLVERS_HH_ #define SRC_SOLVER_SOLVERS_HH_ #include "solver/solver_base.hh" #include #include #include namespace muSpectre { using LoadSteps_t = std::vector; /** * Uses the Newton-conjugate Gradient method to find the static * equilibrium of a cell given a series of mean applied strains */ std::vector newton_cg(Cell & cell, const LoadSteps_t & load_steps, SolverBase & solver, Real newton_tol, Real equil_tol, Dim_t verbose = 0); /** * Uses the Newton-conjugate Gradient method to find the static * equilibrium of a cell given a mean applied strain */ - OptimizeResult newton_cg(Cell & cell, - const Eigen::Ref load_step, - SolverBase & solver, Real newton_tol, Real equil_tol, - Dim_t verbose = 0) { + inline OptimizeResult newton_cg(Cell & cell, + const Eigen::Ref load_step, + SolverBase & solver, Real newton_tol, + Real equil_tol, Dim_t verbose = 0) { LoadSteps_t load_steps{load_step}; return newton_cg(cell, load_steps, solver, newton_tol, equil_tol, verbose) .front(); } /* ---------------------------------------------------------------------- */ /** * Uses the method proposed by de Geus method to find the static * equilibrium of a cell given a series of mean applied strains */ std::vector de_geus(Cell & cell, const LoadSteps_t & load_steps, SolverBase & solver, Real newton_tol, Real equil_tol, Dim_t verbose = 0); /* ---------------------------------------------------------------------- */ /** * Uses the method proposed by de Geus method to find the static * equilibrium of a cell given a mean applied strain */ - OptimizeResult de_geus(Cell & cell, - const Eigen::Ref load_step, - SolverBase & solver, Real newton_tol, Real equil_tol, - Dim_t verbose = 0) { + inline OptimizeResult de_geus(Cell & cell, + const Eigen::Ref load_step, + SolverBase & solver, Real newton_tol, + Real equil_tol, Dim_t verbose = 0) { return de_geus(cell, LoadSteps_t{load_step}, solver, newton_tol, equil_tol, verbose)[0]; } } // namespace muSpectre #endif // SRC_SOLVER_SOLVERS_HH_ diff --git a/tests/header_test_eigen_tools.cc b/tests/header_test_eigen_tools.cc index b9fc987..55270d2 100644 --- a/tests/header_test_eigen_tools.cc +++ b/tests/header_test_eigen_tools.cc @@ -1,63 +1,132 @@ /** * @file header_test_eigen_tools.cc * * @author Till Junge * * @date 07 Mar 2018 * * @brief test the eigen_tools * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #include "common/eigen_tools.hh" #include "tests.hh" namespace muSpectre { BOOST_AUTO_TEST_SUITE(eigen_tools); BOOST_AUTO_TEST_CASE(exponential_test) { using Mat_t = Eigen::Matrix; Mat_t input{}; input << 0, .25 * pi, 0, .25 * pi, 0, 0, 0, 0, 1; Mat_t output{}; output << 1.32460909, 0.86867096, 0, 0.86867096, 1.32460909, 0, 0, 0, 2.71828183; auto my_output{expm(input)}; Real error{(my_output - output).norm()}; BOOST_CHECK_LT(error, 1e-8); if (error >= 1e-8) { std::cout << "input:" << std::endl << input << std::endl; std::cout << "output:" << std::endl << output << std::endl; std::cout << "my_output:" << std::endl << my_output << std::endl; } } + BOOST_AUTO_TEST_CASE(log_m_test) { + using Mat_t = Eigen::Matrix; + Mat_t input{}; + constexpr Real log_tol{1e-8}; + input << 1.32460909, 0.86867096, 0, 0.86867096, 1.32460909, 0, 0, 0, + 2.71828183; + Mat_t output{}; + + output << 0, .25 * pi, 0, .25 * pi, 0, 0, 0, 0, 1; + auto my_output{logm(input)}; + Real error{(my_output - output).norm() / output.norm()}; + + BOOST_CHECK_LT(error, log_tol); + if (error >= log_tol) { + std::cout << "input:" << std::endl << input << std::endl; + std::cout << "output:" << std::endl << output << std::endl; + std::cout << "my_output:" << std::endl << my_output << std::endl; + } + + input << 1.0001000000000002, 0.010000000000000116, 0, 0.010000000000000061, + 1.0000000000000002, 0, 0, 0, 1; + + // from scipy.linalg.logm + output << 4.99991667e-05, 9.99983334e-03, 0.00000000e+00, 9.99983334e-03, + -4.99991667e-05, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, + 0.00000000e+00; + + my_output = logm(input); + error = (my_output - output).norm() / output.norm(); + + BOOST_CHECK_LT(error, log_tol); + if (error >= log_tol) { + std::cout << "input:" << std::endl << input << std::endl; + std::cout << "output:" << std::endl << output << std::endl; + std::cout << "my_output:" << std::endl << my_output << std::endl; + } + + input << 1.0001000000000002, 0.010000000000000116, 0, 0.010000000000000061, + 1.0000000000000002, 0, 0, 0, 1; + input = input.transpose().eval(); + + // from scipy.linalg.logm + output << 4.99991667e-05, 9.99983334e-03, 0.00000000e+00, 9.99983334e-03, + -4.99991667e-05, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, + 0.00000000e+00; + + my_output = logm(input); + + error = (my_output - output).norm() / output.norm(); + + BOOST_CHECK_LT(error, log_tol); + if (error >= log_tol) { + std::cout << "input:" << std::endl << input << std::endl; + std::cout << "output:" << std::endl << output << std::endl; + std::cout << "my_output:" << std::endl << my_output << std::endl; + } + + Mat_t my_output_alt{logm_alt(input)}; + + error = (my_output_alt - output).norm() / output.norm(); + + BOOST_CHECK_LT(error, log_tol); + if (error >= log_tol) { + std::cout << "input:" << std::endl << input << std::endl; + std::cout << "output:" << std::endl << output << std::endl; + std::cout << "my_output:" << std::endl << my_output_alt << std::endl; + } + } + BOOST_AUTO_TEST_SUITE_END(); } // namespace muSpectre diff --git a/tests/tests.hh b/tests/header_test_ref_array.cc similarity index 69% copy from tests/tests.hh copy to tests/header_test_ref_array.cc index 5b5a81a..b11e4dd 100644 --- a/tests/tests.hh +++ b/tests/header_test_ref_array.cc @@ -1,48 +1,52 @@ /** - * @file tests.hh + * @file header_test_ref_array.cc * * @author Till Junge * - * @date 10 May 2017 + * @date 04 Dec 2018 * - * @brief common defs for tests + * @brief tests for the RefArray convenience struct * - * Copyright © 2017 Till Junge + * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * * Boston, MA 02111-1307, USA. + * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ -#include "common/common.hh" -#include -#include - -#ifndef TESTS_TESTS_HH_ -#define TESTS_TESTS_HH_ +#include "common/ref_array.hh" +#include "tests.hh" namespace muSpectre { - constexpr Real tol = 1e-14 * 100; // it's in percent + BOOST_AUTO_TEST_SUITE(RefArray_tests); -} // namespace muSpectre + BOOST_AUTO_TEST_CASE(two_d_test) { + std::array values{2, 3}; + RefArray refs{values[0], values[1]}; + + BOOST_CHECK_EQUAL(values[0], refs[0]); + BOOST_CHECK_EQUAL(values[1], refs[1]); + } -#endif // TESTS_TESTS_HH_ + BOOST_AUTO_TEST_SUITE_END(); + +} // namespace muSpectre diff --git a/tests/header_test_t4_map.cc b/tests/header_test_t4_map.cc index 98b2afc..2c082e4 100644 --- a/tests/header_test_t4_map.cc +++ b/tests/header_test_t4_map.cc @@ -1,124 +1,201 @@ /** * @file header_test_t4_map.cc * * @author Till Junge * * @date 20 Nov 2017 * * @brief Test the fourth-order map on second-order tensor implementation * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #include #include #include #include #include "common/common.hh" #include "tests.hh" +#include "test_goodies.hh" #include "common/T4_map_proxy.hh" namespace muSpectre { BOOST_AUTO_TEST_SUITE(T4map_tests); /** * Test fixture for construction of T4Map for the time being, symmetry is not * exploited */ template struct T4_fixture { T4_fixture() : matrix{}, tensor(matrix.data()) {} EIGEN_MAKE_ALIGNED_OPERATOR_NEW; using M4 = T4Mat; using T4 = T4MatMap; constexpr static Dim_t dim{Dim}; M4 matrix; T4 tensor; }; using fix_collection = boost::mpl::list, T4_fixture>; BOOST_FIXTURE_TEST_CASE_TEMPLATE(Simple_construction_test, F, fix_collection, F) { BOOST_CHECK_EQUAL(F::tensor.cols(), F::dim * F::dim); } BOOST_FIXTURE_TEST_CASE_TEMPLATE(write_access_test, F, fix_collection, F) { auto & t4 = F::tensor; constexpr Dim_t dim{F::dim}; Eigen::TensorFixedSize> t4c; Eigen::Map t4c_map(t4c.data()); for (Dim_t i = 0; i < F::dim; ++i) { for (Dim_t j = 0; j < F::dim; ++j) { for (Dim_t k = 0; k < F::dim; ++k) { for (Dim_t l = 0; l < F::dim; ++l) { get(t4, i, j, k, l) = 1000 * (i + 1) + 100 * (j + 1) + 10 * (k + 1) + l + 1; t4c(i, j, k, l) = 1000 * (i + 1) + 100 * (j + 1) + 10 * (k + 1) + l + 1; } } } } for (Dim_t i = 0; i < ipow(dim, 4); ++i) { BOOST_CHECK_EQUAL(F::matrix.data()[i], t4c.data()[i]); } } + BOOST_FIXTURE_TEST_CASE_TEMPLATE(numpy_compatibility, F, fix_collection, F) { + auto & t4 = F::tensor; + typename F::M4 numpy_ref{}; + if (F::dim == twoD) { + numpy_ref << 1111., 1112., 1121., 1122., 1211., 1212., 1221., 1222., + 2111., 2112., 2121., 2122., 2211., 2212., 2221., 2222.; + } else { + numpy_ref << 1111., 1112., 1113., 1121., 1122., 1123., 1131., 1132., + 1133., 1211., 1212., 1213., 1221., 1222., 1223., 1231., 1232., 1233., + 1311., 1312., 1313., 1321., 1322., 1323., 1331., 1332., 1333., 2111., + 2112., 2113., 2121., 2122., 2123., 2131., 2132., 2133., 2211., 2212., + 2213., 2221., 2222., 2223., 2231., 2232., 2233., 2311., 2312., 2313., + 2321., 2322., 2323., 2331., 2332., 2333., 3111., 3112., 3113., 3121., + 3122., 3123., 3131., 3132., 3133., 3211., 3212., 3213., 3221., 3222., + 3223., 3231., 3232., 3233., 3311., 3312., 3313., 3321., 3322., 3323., + 3331., 3332., 3333.; + } + for (Dim_t i = 0; i < F::dim; ++i) { + for (Dim_t j = 0; j < F::dim; ++j) { + for (Dim_t k = 0; k < F::dim; ++k) { + for (Dim_t l = 0; l < F::dim; ++l) { + get(t4, i, j, k, l) = + 1000 * (i + 1) + 100 * (j + 1) + 10 * (k + 1) + l + 1; + } + } + } + } + + Real error{(t4 - testGoodies::from_numpy(numpy_ref)).norm()}; + BOOST_CHECK_EQUAL(error, 0); + if (error != 0) { + std::cout << "T4:" << std::endl << t4 << std::endl; + std::cout << "reshuffled np:" << std::endl + << testGoodies::from_numpy(numpy_ref) << std::endl; + std::cout << "original np:" << std::endl << numpy_ref << std::endl; + } + } + + BOOST_FIXTURE_TEST_CASE_TEMPLATE(numpy_right_trans, F, fix_collection, F) { + auto & t4 = F::tensor; + typename F::M4 numpy_ref{}; + if (F::dim == twoD) { + numpy_ref << 1111., 1121., 1112., 1122., 1211., 1221., 1212., 1222., + 2111., 2121., 2112., 2122., 2211., 2221., 2212., 2222.; + } else { + numpy_ref << 1111., 1121., 1131., 1112., 1122., 1132., 1113., 1123., + 1133., 1211., 1221., 1231., 1212., 1222., 1232., 1213., 1223., 1233., + 1311., 1321., 1331., 1312., 1322., 1332., 1313., 1323., 1333., 2111., + 2121., 2131., 2112., 2122., 2132., 2113., 2123., 2133., 2211., 2221., + 2231., 2212., 2222., 2232., 2213., 2223., 2233., 2311., 2321., 2331., + 2312., 2322., 2332., 2313., 2323., 2333., 3111., 3121., 3131., 3112., + 3122., 3132., 3113., 3123., 3133., 3211., 3221., 3231., 3212., 3222., + 3232., 3213., 3223., 3233., 3311., 3321., 3331., 3312., 3322., 3332., + 3313., 3323., 3333.; + } + for (Dim_t i = 0; i < F::dim; ++i) { + for (Dim_t j = 0; j < F::dim; ++j) { + for (Dim_t k = 0; k < F::dim; ++k) { + for (Dim_t l = 0; l < F::dim; ++l) { + get(t4, i, j, k, l) = + 1000 * (i + 1) + 100 * (j + 1) + 10 * (k + 1) + l + 1; + } + } + } + } + + Real error{(t4 - testGoodies::right_transpose(numpy_ref)).norm()}; + BOOST_CHECK_EQUAL(error, 0); + if (error != 0) { + std::cout << "T4:" << std::endl << t4 << std::endl; + std::cout << "reshuffled np:" << std::endl + << testGoodies::from_numpy(numpy_ref) << std::endl; + std::cout << "original np:" << std::endl << numpy_ref << std::endl; + } + } + BOOST_FIXTURE_TEST_CASE_TEMPLATE(assign_matrix_test, F, fix_collection, F) { decltype(F::matrix) matrix; matrix.setRandom(); F::tensor = matrix; for (Dim_t i = 0; i < ipow(F::dim, 4); ++i) { BOOST_CHECK_EQUAL(F::matrix.data()[i], matrix.data()[i]); } } BOOST_AUTO_TEST_CASE(Return_ref_from_const_test) { constexpr Dim_t dim{2}; using T = int; using M4 = Eigen::Matrix; using M4c = const Eigen::Matrix; using T4 = T4MatMap; using T4c = T4MatMap; M4 mat; mat.setRandom(); M4c cmat{mat}; T4 tensor{mat.data()}; T4c ctensor{mat.data()}; T a = get(tensor, 0, 0, 0, 1); T b = get(ctensor, 0, 0, 0, 1); BOOST_CHECK_EQUAL(a, b); } BOOST_AUTO_TEST_SUITE_END(); } // namespace muSpectre diff --git a/tests/py_comparison_test_material_hyper_elasto_plastic1.cc b/tests/py_comparison_test_material_hyper_elasto_plastic1.cc new file mode 100644 index 0000000..52c6c73 --- /dev/null +++ b/tests/py_comparison_test_material_hyper_elasto_plastic1.cc @@ -0,0 +1,132 @@ +/** + * @file test_material_hyper_elasto_plastic1_comparison.cc + * + * @author Till Junge + * + * @date 30 Oct 2018 + * + * @brief simple wrapper around the MaterialHyperElastoPlastic1 to test it + * with arbitrary input + * + * Copyright © 2018 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with µSpectre; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Additional permission under GNU GPL version 3 section 7 + * + * If you modify this Program, or any covered work, by linking or combining it + * with proprietary FFT implementations or numerical libraries, containing parts + * covered by the terms of those libraries' licenses, the licensors of this + * Program grant you additional permission to convey the resulting work. + */ + +#include "materials/stress_transformations_Kirchhoff.hh" +#include "materials/material_hyper_elasto_plastic1.hh" +#include "materials/materials_toolbox.hh" + +#include +#include + +#include + +using pybind11::literals::operator""_a; +namespace py = pybind11; + +namespace muSpectre { + + template + decltype(auto) material_wrapper(Real K, Real mu, Real H, Real tau_y0, + py::EigenDRef F, + py::EigenDRef F_prev, + py::EigenDRef be_prev, + Real eps_prev) { + const Real Young{MatTB::convert_elastic_modulus< + ElasticModulus::Young, ElasticModulus::Bulk, ElasticModulus::Shear>( + K, mu)}; + const Real Poisson{MatTB::convert_elastic_modulus< + ElasticModulus::Poisson, ElasticModulus::Bulk, ElasticModulus::Shear>( + K, mu)}; + + using Mat_t = MaterialHyperElastoPlastic1; + Mat_t mat("Name", Young, Poisson, tau_y0, H); + + auto & coll{mat.get_collection()}; + coll.add_pixel({0}); + coll.initialise(); + + auto & F_{mat.get_F_prev_field()}; + auto & be_{mat.get_be_prev_field()}; + auto & eps_{mat.get_plast_flow_field()}; + + F_.get_map()[0].current() = F_prev; + be_.get_map()[0].current() = be_prev; + eps_.get_map()[0].current() = eps_prev; + mat.save_history_variables(); + + return mat.evaluate_stress_tangent(F, F_.get_map()[0], be_.get_map()[0], + eps_.get_map()[0]); + } + + template + py::tuple kirchhoff_fun(Real K, Real mu, Real H, Real tau_y0, + py::EigenDRef F, + py::EigenDRef F_prev, + py::EigenDRef be_prev, + Real eps_prev) { + auto && tup{ + material_wrapper(K, mu, H, tau_y0, F, F_prev, be_prev, eps_prev)}; + auto && tau{std::get<0>(tup)}; + auto && C{std::get<1>(tup)}; + + return py::make_tuple(std::move(tau), std::move(C)); + } + + template + py::tuple PK1_fun(Real K, Real mu, Real H, Real tau_y0, + py::EigenDRef F, + py::EigenDRef F_prev, + py::EigenDRef be_prev, + Real eps_prev) { + auto && tup{ + material_wrapper(K, mu, H, tau_y0, F, F_prev, be_prev, eps_prev)}; + auto && tau{std::get<0>(tup)}; + auto && C{std::get<1>(tup)}; + + using Mat_t = MaterialHyperElastoPlastic1; + constexpr auto StrainM{Mat_t::traits::strain_measure}; + constexpr auto StressM{Mat_t::traits::stress_measure}; + + auto && PK_tup{MatTB::PK1_stress( + Eigen::Matrix{F}, tau, C)}; + auto && P{std::get<0>(PK_tup)}; + auto && K_{std::get<1>(PK_tup)}; + + return py::make_tuple(std::move(P), std::move(K_)); + } + + PYBIND11_MODULE(material_hyper_elasto_plastic1, mod) { + mod.doc() = "Comparison provider for MaterialHyperElastoPlastic1"; + auto adder{[&mod](auto name, auto & fun) { + mod.def(name, &fun, "K"_a, "mu"_a, "H"_a, "tau_y0"_a, "F"_a, "F_prev"_a, + "be_prev"_a, "eps_prev"_a); + }}; + adder("kirchhoff_fun_2d", kirchhoff_fun); + adder("kirchhoff_fun_3d", kirchhoff_fun); + adder("PK1_fun_2d", PK1_fun); + adder("PK1_fun_3d", PK1_fun); + } + +} // namespace muSpectre diff --git a/tests/py_comparison_test_material_hyper_elasto_plastic1.py b/tests/py_comparison_test_material_hyper_elasto_plastic1.py new file mode 100755 index 0000000..003c476 --- /dev/null +++ b/tests/py_comparison_test_material_hyper_elasto_plastic1.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- +""" +@file py_comparison_test_material_hyper_elasto_plastic1.py + +@author Till Junge + +@date 14 Nov 2018 + +@brief compares MaterialHyperElastoPlastic1 to de Geus's python + implementation + +@section LICENSE + +Copyright © 2018 Till Junge + +µSpectre is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation, either version 3, or (at +your option) any later version. + +µSpectre 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with µSpectre; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. + +Additional permission under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or combining it +with proprietary FFT implementations or numerical libraries, containing parts +covered by the terms of those libraries' licenses, the licensors of this +Program grant you additional permission to convey the resulting work. +""" + +from material_hyper_elasto_plastic1 import * +import itertools +import numpy as np +np.set_printoptions(linewidth=180) + +import unittest + +##################### +dyad22 = lambda A2,B2: np.einsum('ij ,kl ->ijkl',A2,B2) +dyad11 = lambda A1,B1: np.einsum('i ,j ->ij ',A1,B1) +dot22 = lambda A2,B2: np.einsum('ij ,jk ->ik ',A2,B2) +dot24 = lambda A2,B4: np.einsum('ij ,jkmn->ikmn',A2,B4) +dot42 = lambda A4,B2: np.einsum('ijkl,lm ->ijkm',A4,B2) +inv2 = np.linalg.inv +ddot22 = lambda A2,B2: np.einsum('ij ,ji -> ',A2,B2) +ddot42 = lambda A4,B2: np.einsum('ijkl,lk ->ij ',A4,B2) +ddot44 = lambda A4,B4: np.einsum('ijkl,lkmn->ijmn',A4,B4) + + + + +class MatTest(unittest.TestCase): + def constitutive(self, F,F_t,be_t,ep_t, dim): + I = np.eye(dim) + II = dyad22(I,I) + I4 = np.einsum('il,jk',I,I) + I4rt = np.einsum('ik,jl',I,I) + I4s = (I4+I4rt)/2. + + def ln2(A2): + vals,vecs = np.linalg.eig(A2) + return sum( + [np.log(vals[i])*dyad11(vecs[:,i],vecs[:,i]) for i in range(dim)]) + + def exp2(A2): + vals,vecs = np.linalg.eig(A2) + return sum( + [np.exp(vals[i])*dyad11(vecs[:,i],vecs[:,i]) for i in range(dim)]) + + # function to compute linearization of the logarithmic Finger tensor + def dln2_d2(A2): + vals,vecs = np.linalg.eig(A2) + K4 = np.zeros([dim, dim, dim, dim]) + for m, n in itertools.product(range(dim),repeat=2): + + if vals[n]==vals[m]: + gc = (1.0/vals[m]) + else: + gc = (np.log(vals[n])-np.log(vals[m]))/(vals[n]-vals[m]) + K4 += gc*dyad22(dyad11(vecs[:,m],vecs[:,n]),dyad11(vecs[:,m],vecs[:,n])) + return K4 + + # elastic stiffness tensor + C4e = self.K*II+2.*self.mu*(I4s-1./3.*II) + + # trial state + Fdelta = dot22(F,inv2(F_t)) + be_s = dot22(Fdelta,dot22(be_t,Fdelta.T)) + lnbe_s = ln2(be_s) + tau_s = ddot42(C4e,lnbe_s)/2. + taum_s = ddot22(tau_s,I)/3. + taud_s = tau_s-taum_s*I + taueq_s = np.sqrt(3./2.*ddot22(taud_s,taud_s)) + div = np.where(taueq_s < 1e-12, np.ones_like(taueq_s), taueq_s) + N_s = 3./2.*taud_s/div + phi_s = taueq_s-(self.tauy0+self.H*ep_t) + phi_s = 1./2.*(phi_s+np.abs(phi_s)) + + # return map + dgamma = phi_s/(self.H+3.*self.mu) + ep = ep_t + dgamma + tau = tau_s -2.*dgamma*N_s*self.mu + lnbe = lnbe_s-2.*dgamma*N_s + be = exp2(lnbe) + P = dot22(tau,inv2(F).T) + + # consistent tangent operator + a0 = dgamma*self.mu/taueq_s + a1 = self.mu/(self.H+3.*self.mu) + C4ep = (((self.K-2./3.*self.mu)/2.+a0*self.mu)*II+(1.-3.*a0)*self.mu* + I4s+2.*self.mu*(a0-a1)*dyad22(N_s,N_s)) + dlnbe4_s = dln2_d2(be_s) + dbe4_s = 2.*dot42(I4s,be_s) + #K4a = ((C4e/2.)*(phi_s<=0.).astype(np.float)+ + # C4ep*(phi_s>0.).astype(np.float)) + K4a = np.where(phi_s<=0, C4e/2., C4ep) + K4b = ddot44(K4a,ddot44(dlnbe4_s,dbe4_s)) + K4c = dot42(-I4rt,tau)+K4b + K4 = dot42(dot24(inv2(F),K4c),inv2(F).T) + + return P,tau,K4,be,ep, dlnbe4_s, dbe4_s, K4a, K4b, K4c + + def setUp(self): + pass + def prep(self, dimension): + self.dim=dimension + self.K=2.+ np.random.rand() + self.mu=2.+ np.random.rand() + self.H=.1 + np.random.rand()/100 + self.tauy0=4. + np.random.rand()/10 + self.F_prev=np.eye(self.dim) + (np.random.random((self.dim, self.dim))-.5)/10 + self.F = self.F_prev + (np.random.random((self.dim, self.dim))-.5)/10 + noise = np.random.random((self.dim, self.dim))*1e-2 + self.be_prev=.5*(self.F_prev + self.F_prev.T + noise + noise.T) + self.eps_prev=.5+ np.random.rand()/10 + self.tol = 1e-13 + self.verbose=True + + def test_specific_case(self): + self.dim = 3 + self.K = 0.833 + self.mu = 0.386 + self.tauy0 = .003 + self.H = 0.004 + self.F_prev = np.eye(self.dim) + self.F = np.array([[ 1.00357938, 0.0012795, 0. ], + [-0.00126862, 0.99643366, 0. ], + [ 0., 0., 0.99999974]]) + self.be_prev = np.eye(self.dim) + self.eps_prev = 0.0 + self.tol = 1e-13 + self.verbose = True + + τ_µ, C_µ_s = kirchhoff_fun_3d(self.K, self.mu, self.H, self.tauy0, + self.F, self.F_prev, self.be_prev, + self.eps_prev) + shape = (self.dim, self.dim, self.dim, self.dim) + C_µ = C_µ_s.reshape(shape).transpose((0,1,3,2)) + + P_µ, K_µ_s = PK1_fun_3d(self.K, self.mu, self.H, self.tauy0, + self.F, self.F_prev, self.be_prev, + self.eps_prev) + K_µ = K_µ_s.reshape(shape).transpose((0,1,3,2)) + + response_p = self.constitutive(self.F, self.F_prev, self.be_prev, + self.eps_prev, self.dim) + τ_p, C_p = response_p[1], response_p[8] + P_p, K_p = response_p[0], response_p[2] + + τ_error = np.linalg.norm(τ_µ- τ_p)/np.linalg.norm(τ_µ) + if not τ_error < self.tol: + print("Error(τ) = {}".format(τ_error)) + print("τ_µ:\n{}".format(τ_µ)) + print("τ_p:\n{}".format(τ_p)) + self.assertLess(τ_error, + self.tol) + + C_error = np.linalg.norm(C_µ- C_p)/np.linalg.norm(C_µ) + if not C_error < self.tol: + print("Error(C) = {}".format(C_error)) + flat_shape = (self.dim**2, self.dim**2) + print("C_µ:\n{}".format(C_µ.reshape(flat_shape))) + print("C_p:\n{}".format(C_p.reshape(flat_shape))) + self.assertLess(C_error, + self.tol) + + P_error = np.linalg.norm(P_µ- P_p)/np.linalg.norm(P_µ) + if not P_error < self.tol: + print("Error(P) = {}".format(P_error)) + print("P_µ:\n{}".format(P_µ)) + print("P_p:\n{}".format(P_p)) + self.assertLess(P_error, + self.tol) + + K_error = np.linalg.norm(K_µ- K_p)/np.linalg.norm(K_µ) + if not K_error < self.tol: + print("Error(K) = {}".format(K_error)) + flat_shape = (self.dim**2, self.dim**2) + print("K_µ:\n{}".format(K_µ.reshape(flat_shape))) + print("K_p:\n{}".format(K_p.reshape(flat_shape))) + print("diff:\n{}".format(K_p.reshape(flat_shape)- + K_µ.reshape(flat_shape))) + self.assertLess(K_error, + self.tol) + + def test_equivalence_tau_C(self): + for dim in (2, 3): + self.runner_equivalence_τ_C(dim) + + def runner_equivalence_τ_C(self, dimension): + self.prep(dimension) + fun = kirchhoff_fun_2d if self.dim == 2 else kirchhoff_fun_3d + τ_µ, C_µ_s = fun(self.K, self.mu, self.H, self.tauy0, + self.F, self.F_prev, self.be_prev, + self.eps_prev) + shape = (self.dim, self.dim, self.dim, self.dim) + C_µ = C_µ_s.reshape(shape).transpose((0,1,3,2)) + + response_p = self.constitutive(self.F, self.F_prev, self.be_prev, + self.eps_prev, self.dim) + τ_p, C_p = response_p[1], response_p[8] + + τ_error = np.linalg.norm(τ_µ- τ_p)/np.linalg.norm(τ_µ) + if not τ_error < self.tol: + print("Error(τ) = {}".format(τ_error)) + print("τ_µ:\n{}".format(τ_µ)) + print("τ_p:\n{}".format(τ_p)) + self.assertLess(τ_error, + self.tol) + + C_error = np.linalg.norm(C_µ- C_p)/np.linalg.norm(C_µ) + if not C_error < self.tol: + print("Error(C) = {}".format(C_error)) + flat_shape = (self.dim**2, self.dim**2) + print("C_µ:\n{}".format(C_µ.reshape(flat_shape))) + print("C_p:\n{}".format(C_p.reshape(flat_shape))) + self.assertLess(C_error, + self.tol) + + def test_equivalence_P_K(self): + for dim in (2, 3): + self.runner_equivalence_P_K(dim) + + def runner_equivalence_P_K(self, dimension): + self.prep(dimension) + fun = PK1_fun_2d if self.dim == 2 else PK1_fun_3d + P_µ, K_µ_s = fun(self.K, self.mu, self.H, self.tauy0, + self.F, self.F_prev, self.be_prev, + self.eps_prev) + shape = (self.dim, self.dim, self.dim, self.dim) + K_µ = K_µ_s.reshape(shape).transpose((0,1,3,2)) + + response_p = self.constitutive(self.F, self.F_prev, self.be_prev, + self.eps_prev, self.dim) + P_p, K_p = response_p[0], response_p[2] + + P_error = np.linalg.norm(P_µ- P_p)/np.linalg.norm(P_µ) + if not P_error < self.tol: + print("Error(P) = {}".format(P_error)) + print("P_µ:\n{}".format(P_µ)) + print("P_p:\n{}".format(P_p)) + self.assertLess(P_error, + self.tol) + + K_error = np.linalg.norm(K_µ- K_p)/np.linalg.norm(K_µ) + if not K_error < self.tol: + print("Error(K) = {}".format(K_error)) + flat_shape = (self.dim**2, self.dim**2) + print("K_µ:\n{}".format(K_µ.reshape(flat_shape))) + print("K_p:\n{}".format(K_p.reshape(flat_shape))) + print("diff:\n{}".format(K_p.reshape(flat_shape)- + K_µ.reshape(flat_shape))) + self.assertLess(K_error, + self.tol) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/py_comparison_test_material_linear_elastic1.cc b/tests/py_comparison_test_material_linear_elastic1.cc new file mode 100644 index 0000000..ce3c1ea --- /dev/null +++ b/tests/py_comparison_test_material_linear_elastic1.cc @@ -0,0 +1,108 @@ +/** + * @file py_comparison_test_material_linear_elastic_1.cc + * + * @author Till Junge + * + * @date 05 Dec 2018 + * + * @brief simple wrapper around the MaterialLinearElastic1 to test it + * with arbitrary input + * + * Copyright © 2018 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with µSpectre; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Additional permission under GNU GPL version 3 section 7 + * + * If you modify this Program, or any covered work, by linking or combining it + * with proprietary FFT implementations or numerical libraries, containing parts + * covered by the terms of those libraries' licenses, the licensors of this + * Program grant you additional permission to convey the resulting work. + */ + +#include "materials/stress_transformations_PK1.hh" +#include "materials/material_linear_elastic1.hh" +#include "materials/materials_toolbox.hh" + +#include +#include + +#include + +using pybind11::literals::operator""_a; +namespace py = pybind11; + +namespace muSpectre { + template + std::tuple, + Eigen::Matrix> + material_wrapper(Real Young, Real Poisson, + py::EigenDRef F) { + using Mat_t = MaterialLinearElastic1; + Mat_t mat("Name", Young, Poisson); + + auto & coll{mat.get_collection()}; + coll.add_pixel({0}); + coll.initialise(); + + Eigen::Matrix F_mat(F); + Eigen::Matrix E{ + MatTB::convert_strain(F_mat)}; + return mat.evaluate_stress_tangent(std::move(E)); + } + + template + py::tuple PK2_fun(Real Young, Real Poisson, + py::EigenDRef F) { + auto && tup{material_wrapper(Young, Poisson, F)}; + auto && S{std::get<0>(tup)}; + Eigen::MatrixXd C{std::get<1>(tup)}; + + return py::make_tuple(Eigen::MatrixXd{S}, C); + } + + template + py::tuple PK1_fun(Real Young, Real Poisson, + py::EigenDRef F) { + auto && tup{material_wrapper(Young, Poisson, F)}; + auto && S{std::get<0>(tup)}; + auto && C{std::get<1>(tup)}; + + using Mat_t = MaterialLinearElastic1; + constexpr auto StrainM{Mat_t::traits::strain_measure}; + constexpr auto StressM{Mat_t::traits::stress_measure}; + + auto && PK_tup{MatTB::PK1_stress( + Eigen::Matrix{F}, S, C)}; + auto && P{std::get<0>(PK_tup)}; + auto && K{std::get<1>(PK_tup)}; + + return py::make_tuple(std::move(P), std::move(K)); + } + + PYBIND11_MODULE(material_linear_elastic1, mod) { + mod.doc() = "Comparison provider for MaterialLinearelastic1"; + auto adder{[&mod](auto name, auto & fun) { + mod.def(name, &fun, "Young"_a, "Poisson"_a, "F"_a); + }}; + adder("PK2_fun_2d", PK2_fun); + adder("PK2_fun_3d", PK2_fun); + adder("PK1_fun_2d", PK1_fun); + adder("PK1_fun_3d", PK1_fun); + } + +} // namespace muSpectre diff --git a/tests/py_comparison_test_material_linear_elastic1.py b/tests/py_comparison_test_material_linear_elastic1.py new file mode 100644 index 0000000..cb59e8c --- /dev/null +++ b/tests/py_comparison_test_material_linear_elastic1.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- +""" +@file py_comparison_test_material_linear_elastic_1.py + +@author Till Junge + +@date 05 Dec 2018 + +@brief compares MaterialLinearElastic1 to de Geus's python implementation + +Copyright © 2018 Till Junge + +µSpectre is free software; you can redistribute it and/or +modify it under the terms of the GNU General Lesser Public License as +published by the Free Software Foundation, either version 3, or (at +your option) any later version. + +µSpectre 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 +General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with µSpectre; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. + +Additional permission under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or combining it +with proprietary FFT implementations or numerical libraries, containing parts +covered by the terms of those libraries' licenses, the licensors of this +Program grant you additional permission to convey the resulting work. +""" + +from material_linear_elastic1 import * +import itertools +import numpy as np +np.set_printoptions(linewidth=180) + +import unittest + +##################### +dyad22 = lambda A2,B2: np.einsum('ij ,kl ->ijkl',A2,B2) +dyad11 = lambda A1,B1: np.einsum('i ,j ->ij ',A1,B1) +dot22 = lambda A2,B2: np.einsum('ij ,jk ->ik ',A2,B2) +dot24 = lambda A2,B4: np.einsum('ij ,jkmn->ikmn',A2,B4) +dot42 = lambda A4,B2: np.einsum('ijkl,lm ->ijkm',A4,B2) +inv2 = np.linalg.inv +trans2 = np.transpose +ddot22 = lambda A2,B2: np.einsum('ij ,ji -> ',A2,B2) +ddot42 = lambda A4,B2: np.einsum('ijkl,lk ->ij ',A4,B2) +ddot44 = lambda A4,B4: np.einsum('ijkl,lkmn->ijmn',A4,B4) + +class MatTest(unittest.TestCase): + def constitutive(self, F, dim): + I = np.eye(dim) + II = dyad22(I,I) + I4 = np.einsum('il,jk',I,I) + I4rt = np.einsum('ik,jl',I,I) + I4s = (I4+I4rt)/2. + + C4 = self.K*II+2.*self.mu*(I4s-1./3.*II) + S = ddot42(C4,.5*(dot22(trans2(F),F)-I)) + P = dot22(F,S) + K4 = dot24(S,I4)+ddot44(ddot44(I4rt,dot42(dot24(F,C4),trans2(F))),I4rt) + return P, S, K4, C4 + + def setUp(self): + pass + def prep(self, dimension): + self.dim=dimension + self.Young = 200e9+100*np.random.rand() + self.Poisson = .3 + .1*(np.random.rand()-.5) + self.K = self.Young/(3*(1-2*self.Poisson)) + self.mu = self.Young/(2*(1+self.Poisson)) + self.F = np.eye(self.dim) + (np.random.random((self.dim, self.dim))-.5)/10 + self.tol = 1e-13 + self.verbose=True + + def test_equivalence_S_C(self): + for dim in (2, 3): + self.runner_equivalence_S_C(dim) + + def runner_equivalence_S_C(self, dimension): + self.prep(dimension) + fun = PK2_fun_2d if self.dim == 2 else PK2_fun_3d + S_µ, C_µ_s = fun(self.Young, self.Poisson, self.F) + shape = (self.dim, self.dim, self.dim, self.dim) + C_µ = C_µ_s.reshape(shape).transpose((0,1,3,2)) + + response_p = self.constitutive(self.F, self.dim) + S_p, C_p = response_p[1], response_p[3] + + S_error = np.linalg.norm(S_µ- S_p)/np.linalg.norm(S_µ) + if not S_error < self.tol: + print("Error(S) = {}".format(S_error)) + print("S_µ:\n{}".format(S_µ)) + print("S_p:\n{}".format(S_p)) + self.assertLess(S_error, + self.tol) + + C_error = np.linalg.norm(C_µ- C_p)/np.linalg.norm(C_µ) + if not C_error < self.tol: + print("Error(C) = {}".format(C_error)) + flat_shape = (self.dim**2, self.dim**2) + print("C_µ:\n{}".format(C_µ.reshape(flat_shape))) + print("C_p:\n{}".format(C_p.reshape(flat_shape))) + self.assertLess(C_error, + self.tol) + + def test_equivalence_P_K(self): + for dim in (2, 3): + self.runner_equivalence_P_K(dim) + + def runner_equivalence_P_K(self, dimension): + self.prep(dimension) + fun = PK1_fun_2d if self.dim == 2 else PK1_fun_3d + P_µ, K_µ_s = fun(self.Young, self.Poisson, self.F) + shape = (self.dim, self.dim, self.dim, self.dim) + K_µ = K_µ_s.reshape(shape).transpose((0,1,3,2)) + + response_p = self.constitutive(self.F, self.dim) + P_p, K_p = response_p[0], response_p[2] + + P_error = np.linalg.norm(P_µ- P_p)/np.linalg.norm(P_µ) + if not P_error < self.tol: + print("Error(P) = {}".format(P_error)) + print("P_µ:\n{}".format(P_µ)) + print("P_p:\n{}".format(P_p)) + K_error = np.linalg.norm(K_µ- K_p)/np.linalg.norm(K_µ) + if not K_error < self.tol: + print("Error(K) = {}".format(K_error)) + flat_shape = (self.dim**2, self.dim**2) + print("K_µ:\n{}".format(K_µ.reshape(flat_shape))) + print("K_p:\n{}".format(K_p.reshape(flat_shape))) + print("diff:\n{}".format(K_p.reshape(flat_shape)- + K_µ.reshape(flat_shape))) + self.assertLess(P_error, + self.tol) + + self.assertLess(K_error, + self.tol) + + +if __name__ == "__main__": + unittest.main() + diff --git a/tests/python_binding_tests.py b/tests/python_binding_tests.py index a2efba2..dd7b4ef 100755 --- a/tests/python_binding_tests.py +++ b/tests/python_binding_tests.py @@ -1,203 +1,209 @@ #!/usr/bin/env python3 """ file python_binding_tests.py @author Till Junge @date 09 Jan 2018 @brief Unit tests for python bindings @section LICENCE Copyright © 2018 Till Junge µSpectre is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3, or (at your option) any later version. µSpectre 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 General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with µSpectre; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Additional permission under GNU GPL version 3 section 7 If you modify this Program, or any covered work, by linking or combining it with proprietary FFT implementations or numerical libraries, containing parts covered by the terms of those libraries' licenses, the licensors of this Program grant you additional permission to convey the resulting work. """ import unittest import numpy as np from python_test_imports import µ from python_fft_tests import FFT_Check from python_projection_tests import * from python_material_linear_elastic3_test import MaterialLinearElastic3_Check from python_material_linear_elastic4_test import MaterialLinearElastic4_Check from python_material_linear_elastic_generic1_test import MaterialLinearElasticGeneric1_Check from python_material_linear_elastic_generic2_test import MaterialLinearElasticGeneric2_Check from python_field_tests import FieldCollection_Check +from python_exact_reference_elastic_test import LinearElastic_Check + +from python_field_tests import FieldCollection_Check + + class CellCheck(unittest.TestCase): def test_Construction(self): """ Simple check for cell constructors """ resolution = [5,7] lengths = [5.2, 8.3] formulation = µ.Formulation.small_strain try: sys = µ.Cell(resolution, lengths, formulation) - mat = µ.material.MaterialLinearElastic1_2d.make(sys, "material", 210e9, .33) + mat = µ.material.MaterialLinearElastic1_2d.make(sys, "material", + 210e9, .33) except Exception as err: print(err) raise err class MaterialLinearElastic1_2dCheck(unittest.TestCase): def setUp(self): self.resolution = [5,7] self.lengths = [5.2, 8.3] self.formulation = µ.Formulation.small_strain self.sys = µ.Cell(self.resolution, self.lengths, self.formulation) self.mat = µ.material.MaterialLinearElastic1_2d.make( self.sys, "material", 210e9, .33) def test_add_material(self): self.mat.add_pixel([2,1]) class SolverCheck(unittest.TestCase): def setUp(self): self.resolution = [3, 3]#[5,7] self.lengths = [3., 3.]#[5.2, 8.3] self.formulation = µ.Formulation.finite_strain self.sys = µ.Cell(self.resolution, self.lengths, self.formulation) self.hard = µ.material.MaterialLinearElastic1_2d.make( self.sys, "hard", 210e9, .33) self.soft = µ.material.MaterialLinearElastic1_2d.make( self.sys, "soft", 70e9, .33) def test_solve(self): for i, pixel in enumerate(self.sys): if i < 3: self.hard.add_pixel(pixel) else: self.soft.add_pixel(pixel) self.sys.initialise() tol = 1e-6 Del0 = np.array([[0, .1], [0, 0]]) maxiter = 100 verbose = 0 solver=µ.solvers.SolverCG(self.sys, tol, maxiter, verbose) r = µ.solvers.de_geus(self.sys, Del0, solver,tol, verbose) #print(r) class EigenStrainCheck(unittest.TestCase): def setUp(self): self.resolution = [3, 3]#[5,7] self.lengths = [3., 3.]#[5.2, 8.3] self.formulation = µ.Formulation.small_strain self.cell1 = µ.Cell(self.resolution, self.lengths, self.formulation) self.cell2 = µ.Cell(self.resolution, self.lengths, self.formulation) self.mat1 = µ.material.MaterialLinearElastic1_2d.make( self.cell1, "simple", 210e9, .33) self.mat2 = µ.material.MaterialLinearElastic2_2d.make( self.cell2, "eigen", 210e9, .33) self.mat3 = µ.material.MaterialLinearElastic2_2d.make( self.cell2, "eigen2", 120e9, .33) def test_globalisation(self): for pixel in self.cell2: self.mat2.add_pixel(pixel, np.random.rand(2,2)) loc_eigenstrain = self.mat2.collection.get_real_field("Eigenstrain").array glo_eigenstrain = self.cell2.get_globalised_internal_real_array("Eigenstrain") error = np.linalg.norm(loc_eigenstrain-glo_eigenstrain) self.assertEqual(error, 0) def test_globalisation_constant(self): for i, pixel in enumerate(self.cell2): if i%2 == 0: self.mat2.add_pixel(pixel, np.ones((2,2))) else: self.mat3.add_pixel(pixel, np.ones((2,2))) glo_eigenstrain = self.cell2.get_globalised_internal_real_array("Eigenstrain") error = np.linalg.norm(glo_eigenstrain-1) self.assertEqual(error, 0) def test_solve(self): verbose_test = False if verbose_test: print("start test_solve") grad = np.array([[1.1, .2], [ .3, 1.5]]) gl_strain = -0.5*(grad.T.dot(grad) - np.eye(2)) gl_strain = -0.5*(grad.T + grad - 2*np.eye(2)) grad = -gl_strain if verbose_test: print("grad =\n{}\ngl_strain =\n{}".format(grad, gl_strain)) for i, pixel in enumerate(self.cell1): self.mat1.add_pixel(pixel) self.mat2.add_pixel(pixel, gl_strain) self.cell1.initialise() self.cell2.initialise() tol = 1e-6 Del0_1 = grad Del0_2 = np.zeros_like(grad) maxiter = 2 verbose = 0 def solve(cell, grad): solver=µ.solvers.SolverCG(cell, tol, maxiter, verbose) r = µ.solvers.newton_cg(cell, grad, solver, tol, tol, verbose) return r results = [solve(cell, del0) for (cell, del0) in zip((self.cell1, self.cell2), (Del0_1, Del0_2))] P1 = results[0].stress P2 = results[1].stress error = np.linalg.norm(P1-P2)/np.linalg.norm(.5*(P1+P2)) if verbose_test: print("cell 1, no eigenstrain") print("P1:\n{}".format(P1[:,0])) print("F1:\n{}".format(results[0].grad[:,0])) print("cell 2, with eigenstrain") print("P2:\n{}".format(P2[:,0])) print("F2:\n{}".format(results[1].grad[:,0])) print("end test_solve") self.assertLess(error, tol) if __name__ == '__main__': unittest.main() diff --git a/tests/python_exact_reference_elastic_test.py b/tests/python_exact_reference_elastic_test.py new file mode 100644 index 0000000..0f9c61e --- /dev/null +++ b/tests/python_exact_reference_elastic_test.py @@ -0,0 +1,440 @@ +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- +""" +@file python_exact_reference_test.py + +@author Till Junge + +@date 18 Jun 2018 + +@brief Tests exactness of each iterate with respect to python reference + implementation from GooseFFT for elasticity + +Copyright © 2018 Till Junge + +µSpectre is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation, either version 3, or (at +your option) any later version. + +µSpectre 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. + +Additional permission under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or combining it +with proprietary FFT implementations or numerical libraries, containing parts +covered by the terms of those libraries' licenses, the licensors of this +Program grant you additional permission to convey the resulting work. +""" + +import unittest +import numpy as np +from numpy.linalg import norm +from python_test_imports import µ + +import scipy.sparse.linalg as sp +import itertools + +np.set_printoptions(linewidth=180) +comparator_nb_cols=9 +# ----------------------------------- GRID ------------------------------------ + +ndim = 3 # number of dimensions +N = 3 # number of voxels (assumed equal for all directions) + +Nx = Ny = Nz = N + + +def deserialise_t4(t4): + turnaround = np.arange(ndim**2).reshape(ndim,ndim).T.reshape(-1) + retval = np.zeros([ndim*ndim, ndim*ndim]) + for i,j in itertools.product(range(ndim**2), repeat=2): + retval[i,j] = t4[:ndim, :ndim, :ndim, :ndim, 0,0].reshape(ndim**2, ndim**2)[turnaround[i], turnaround[j]] + pass + return retval + +def scalar_to_goose(s_msp): + s_goose = np.zeros((Nx, Ny, Nz)) + for i in range(Nx): + for j in range(Ny): + for k in range(Nz): + s_goose[i,j,k] = s_msp[Nz*Ny*i + Nz*j + k] + pass + pass + return s_goose + +def t2_to_goose(t2_msp): + t2_goose = np.zeros((ndim, ndim, Nx, Ny, Nz)) + for i in range(Nx): + for j in range(Ny): + for k in range(Nz): + t2_goose[:,:,i,j,k] = t2_msp[:, Nz*Ny*i + Nz*j + k].reshape(ndim, ndim).T + pass + pass + return t2_goose + +def t2_vec_to_goose(t2_msp_vec): + return t2_to_goose(t2_msp_vec.reshape(ndim*ndim, Nx*Ny*Nz)).reshape(-1) + +def scalar_vec_to_goose(s_msp_vec): + return scalar_to_goose(s_msp_vec.reshape(Nx*Ny*N)).reshape(-1) + +def t4_to_goose(t4_msp, right_transposed=True): + t4_goose = np.zeros((ndim, ndim, ndim, ndim, Nx, Ny, Nz)) + turnaround = np.arange(ndim**2).reshape(ndim,ndim).T.reshape(-1) + for i in range(Nx): + for j in range(Ny): + for k in range(Nz): + tmp = t4_msp[:, Nz*Ny*i + Nz*j + k].reshape(ndim**2, ndim**2) + goose_view = t4_goose[:,:,:,:,i,j,k].reshape(ndim**2, ndim**2) + for a,b in itertools.product(range(ndim**2), repeat=2): + a_id = a if right_transposed else turnaround[a] + goose_view[a,b] = tmp[a_id, turnaround[b]] + pass + pass + return t4_goose + +def t4_vec_to_goose(t4_msp_vec): + return t4_to_goose(t4_msp_vec.reshape(ndim**4, Nx*Ny*Nz)).reshape(-1) + +def t2_from_goose(t2_goose): + nb_pix = Nx*Ny*Nz + t2_msp = np.zeros((ndim**2, nb_pix), order='F') + for i in range(Nx): + for j in range(Ny): + for k in range(Nz): + view = t2_msp[:, i + Nx*j + Nx*Ny*k].reshape(ndim, ndim).T + view = t2goose[:,:,i,j,k].T + pass + pass + return t2_msp + + +# ---------------------- PROJECTION, TENSORS, OPERATIONS ---------------------- + +# tensor operations/products: np.einsum enables index notation, avoiding loops +# e.g. ddot42 performs $C_ij = A_ijkl B_lk$ for the entire grid +trans2 = lambda A2 : np.einsum('ijxyz ->jixyz ',A2 ) +ddot42 = lambda A4,B2: np.einsum('ijklxyz,lkxyz ->ijxyz ',A4,B2) +ddot44 = lambda A4,B4: np.einsum('ijklxyz,lkmnxyz->ijmnxyz',A4,B4) +dot22 = lambda A2,B2: np.einsum('ijxyz ,jkxyz ->ikxyz ',A2,B2) +dot24 = lambda A2,B4: np.einsum('ijxyz ,jkmnxyz->ikmnxyz',A2,B4) +dot42 = lambda A4,B2: np.einsum('ijklxyz,lmxyz ->ijkmxyz',A4,B2) +dyad22 = lambda A2,B2: np.einsum('ijxyz ,klxyz ->ijklxyz',A2,B2) + +# identity tensor [single tensor] +i = np.eye(ndim) +# identity tensors [grid of tensors] +I = np.einsum('ij,xyz' , i ,np.ones([N,N,N])) +I4 = np.einsum('ijkl,xyz->ijklxyz',np.einsum('il,jk',i,i),np.ones([N,N,N])) +I4rt = np.einsum('ijkl,xyz->ijklxyz',np.einsum('ik,jl',i,i),np.ones([N,N,N])) +I4s = (I4+I4rt)/2. +II = dyad22(I,I) + +# projection operator [grid of tensors] +# NB can be vectorized (faster, less readable), see: "elasto-plasticity.py" +# - support function / look-up list / zero initialize +delta = lambda i,j: np.float(i==j) # Dirac delta function +freq = np.arange(-(N-1)/2.,+(N+1)/2.) # coordinate axis -> freq. axis +Ghat4 = np.zeros([ndim,ndim,ndim,ndim,N,N,N]) # zero initialize +# - compute +for i,j,l,m in itertools.product(range(ndim),repeat=4): + for x,y,z in itertools.product(range(N), repeat=3): + q = np.array([freq[x], freq[y], freq[z]]) # frequency vector + if not q.dot(q) == 0: # zero freq. -> mean + Ghat4[i,j,l,m,x,y,z] = delta(i,m)*q[j]*q[l]/(q.dot(q)) + +# (inverse) Fourier transform (for each tensor component in each direction) +fft = lambda x : np.fft.fftshift(np.fft.fftn (np.fft.ifftshift(x),[N,N,N])) +ifft = lambda x : np.fft.fftshift(np.fft.ifftn(np.fft.ifftshift(x),[N,N,N])) + +# functions for the projection 'G', and the product 'G : K^LT : (delta F)^T' +G = lambda A2 : np.real( ifft( ddot42(Ghat4,fft(A2)) ) ).reshape(-1) +K_dF = lambda dFm: trans2(ddot42(K4,trans2(dFm.reshape(ndim,ndim,N,N,N)))) +G_K_dF = lambda dFm: G(K_dF(dFm)) + +# ------------------- PROBLEM DEFINITION / CONSTITIVE MODEL ------------------- + +# phase indicator: cubical inclusion of volume fraction (9**3)/(31**3) +phase = np.zeros([N,N,N]); phase[:2,:2,:2] = 1. +# material parameters + function to convert to grid of scalars +param = lambda M0,M1: M0*np.ones([N,N,N])*(1.-phase)+M1*np.ones([N,N,N])*phase +K = param(0.833,8.33) # bulk modulus [grid of scalars] +mu = param(0.386,3.86) # shear modulus [grid of scalars] + +# constitutive model: grid of "F" -> grid of "P", "K4" [grid of tensors] +def constitutive(F): + C4 = K*II+2.*mu*(I4s-1./3.*II) + S = ddot42(C4,.5*(dot22(trans2(F),F)-I)) + P = dot22(F,S) + K4 = dot24(S,I4)+ddot44(ddot44(I4rt,dot42(dot24(F,C4),trans2(F))),I4rt) + return P,K4 + + +F = np.array(I,copy=True) +P,K4 = constitutive(F) + +class Counter(object): + def __init__(self): + self.count = self.reset() + + def reset(self): + self.count = 0 + return self.count + + def get(self): + return self.count + + def __call__(self, dummy): + self.count += 1 + + +class LinearElastic_Check(unittest.TestCase): + def t2_comparator(self, µT2, gT2): + err_sum = 0. + err_max = 0. + for counter, (i, j, k) in enumerate(self.rve): + print((i,j,k)) + µ_arr = µT2[:, counter].reshape(ndim, ndim).T + g_arr = gT2[:,:,i,j,k] + self.assertEqual(Nz*Ny*i+Nz*j + k, counter) + print("µSpectre:") + print(µ_arr) + print("Goose:") + print(g_arr) + print(µ_arr-g_arr) + err = norm(µ_arr-g_arr) + print("error norm for pixel {} = {}".format((i, j, k), err)) + err_sum += err + err_max = max(err_max, err) + pass + print("∑(err) = {}, max(err) = {}".format (err_sum, err_max)) + return err_sum + + + def t4_comparator(self, µT4, gT4, right_transposed=True): + """ right_transposed: in de Geus's notation, e.g., + stiffness tensors have the last two dimensions inverted + """ + err_sum = 0. + err_max = 0. + errs = dict() + turnaround = np.arange(ndim**2).reshape(ndim,ndim).T.reshape(-1) + + def zero_repr(arr): + arrcopy = arr.copy() + arrcopy[abs(arr)<1e-13] = 0. + return arrcopy + for counter, (i, j, k) in enumerate(self.rve): + µ_arr_tmp = µT4[:, counter].reshape(ndim**2, ndim**2).T + µ_arr = np.empty((ndim**2, ndim**2)) + for a,b in itertools.product(range(ndim**2), repeat=2): + a = a if right_transposed else turnaround[a] + µ_arr[a,b] = µ_arr_tmp[a, turnaround[b]] + g_arr = gT4[:,:,:,:,i,j,k].reshape(ndim**2, ndim**2) + self.assertEqual(Nz*Ny*i+Nz*j + k, counter) + + print("µSpectre:") + print(zero_repr(µ_arr[:, :comparator_nb_cols])) + print("Goose:") + print(zero_repr(g_arr[:, :comparator_nb_cols])) + print("Diff") + print(zero_repr((µ_arr-g_arr)[:, :comparator_nb_cols])) + err = norm(µ_arr-g_arr)/norm(g_arr) + print("error norm for pixel {} = {}".format((i, j, k), err)) + err_sum += err + errs[(i,j,k)] = err + err_max = max(err_max, err) + print("count {:>2}: err_norm = {:.5f}, err_sum = {:.5f}".format( + counter, err, err_sum)) + pass + print("∑(err) = {}, max(err) = {}".format (err_sum, err_max)) + return err_sum, errs + + def setUp(self): + #---------------------------- µSpectre init ----------------------------------- + resolution = list(phase.shape) + dim = len(resolution) + self.dim=dim + + center = np.array([r//2 for r in resolution]) + incl = resolution[0]//5 + + + ## Domain dimensions + lengths = [float(r) for r in resolution] + ## formulation (small_strain or finite_strain) + formulation = µ.Formulation.finite_strain + + ## build a computational domain + self.rve = µ.Cell(resolution, lengths, formulation) + def get_E_nu(bulk, shear): + Young = 9*bulk*shear/(3*bulk + shear) + Poisson = Young/(2*shear) - 1 + return Young, Poisson + + mat = µ.material.MaterialLinearElastic1_3d + + E, nu = get_E_nu(.833, .386) + hard = mat.make(self.rve, 'hard', 10*E, nu) + soft = mat.make(self.rve, 'soft', E, nu) + + for pixel in self.rve: + if pixel[0] < 2 and pixel[1] < 2 and pixel[2] < 2: + hard.add_pixel(pixel) + else: + soft.add_pixel(pixel) + + def test_solve(self): + before_cg_tol = 1e-11 + cg_tol = 1e-11 + after_cg_tol = 1e-9 + newton_tol = 1e-4 + # ----------------------------- NEWTON ITERATIONS --------------------- + + # initialize deformation gradient, and stress/stiffness [tensor grid] + global K4, P, F + F = np.array(I,copy=True) + F2 = np.array(I,copy=True)*1.1 + P2,K42 = constitutive(F2) + P,K4 = constitutive(F) + self.rve.set_uniform_strain(np.array(np.eye(ndim))) + µF = self.rve.get_strain() + + self.assertLess(norm(t2_vec_to_goose(µF) - F.reshape(-1))/norm(F), before_cg_tol) + # set macroscopic loading + DbarF = np.zeros([ndim,ndim,N,N,N]); DbarF[0,1] += 1.0 + + # initial residual: distribute "barF" over grid using "K4" + b = -G_K_dF(DbarF) + F += DbarF + Fn = np.linalg.norm(F) + iiter = 0 + + # µSpectre inits + µbarF = np.zeros_like(µF) + µbarF[ndim, :] += 1. + µF2 = µF.copy()*1.1 + µP2, µK2 = self.rve.evaluate_stress_tangent(µF2) + err = norm(t2_vec_to_goose(µP2) - P2.reshape(-1))/norm(P2) + + + if not (err < before_cg_tol): + self.t2_comparator(µP2, µK2) + self.assertLess(err, before_cg_tol) + self.rve.set_uniform_strain(np.array(np.eye(ndim))) + µP, µK = self.rve.evaluate_stress_tangent(µF) + err = norm(t2_vec_to_goose(µP) - P.reshape(-1)) + if not (err < before_cg_tol): + print(µF) + self.t2_comparator(µP, P) + self.assertLess(err, before_cg_tol) + err = norm(t4_vec_to_goose(µK) - K4.reshape(-1))/norm(K4) + if not (err < before_cg_tol): + print ("err = {}".format(err)) + + self.assertLess(err, before_cg_tol) + µF += µbarF + µFn = norm(µF) + self.assertLess(norm(t2_vec_to_goose(µF) - F.reshape(-1))/norm(F), before_cg_tol) + µG_K_dF = lambda x: self.rve.directional_stiffness(x.reshape(µF.shape)).reshape(-1) + µG = lambda x: self.rve.project(x).reshape(-1) + µb = -µG_K_dF(µbarF) + + err = (norm(t2_vec_to_goose(µb.reshape(µF.shape)) - b) / + norm(b)) + if not (err < before_cg_tol): + print("|µb| = {}".format(norm(µb))) + print("|b| = {}".format(norm(b))) + print("total error = {}".format(err)) + self.t2_comparator(µb.reshape(µF.shape), b.reshape(F.shape)) + self.assertLess(err, before_cg_tol) + + # iterate as long as the iterative update does not vanish + while True: + # solve linear system using CG + g_counter = Counter() + dFm,_ = sp.cg(tol=cg_tol, + A = sp.LinearOperator(shape=(F.size,F.size), + matvec=G_K_dF,dtype='float'), + b = b, + callback=g_counter + ) + + µ_counter = Counter() + µdFm,_ = sp.cg(tol=cg_tol, + A = sp.LinearOperator(shape=(F.size,F.size), + matvec=µG_K_dF,dtype='float'), + b = µb, + callback=µ_counter) + + err = g_counter.get()-µ_counter.get() + if err != 0: + print("n_iter(g) = {}, n_iter(µ) = {}".format(g_counter.get(), + µ_counter.get())) + pass + + + # in the last iteration, the increment is essentially + # zero, so we don't care about relative error anymore + err = norm(t2_vec_to_goose(µdFm) - dFm)/norm(dFm) + if norm(dFm)/Fn > newton_tol and norm(µdFm)/Fn > newton_tol: + if not (err < after_cg_tol): + self.t2_comparator(µdFm.reshape(µF.shape), dFm.reshape(F.shape)) + print("µdFm.shape = {}".format(µdFm.shape)) + print("|µdFm| = {}".format(norm(µdFm))) + print("|dFm| = {}".format(norm(dFm))) + print("|µdFm - dFm| = {}".format(norm(µdFm-dFm))) + print("AssertionWarning: {} is not less than {}".format(err, + after_cg_tol)) + self.assertLess(err, after_cg_tol) + # update DOFs (array -> tens.grid) + F += dFm.reshape(ndim,ndim,N,N,N) + µF += µdFm.reshape(µF.shape) + # new residual stress and tangent + P,K4 = constitutive(F) + µP, µK = self.rve.evaluate_stress_tangent(µF) + + err = norm(t2_vec_to_goose(µP) - P.reshape(-1))/norm(P) + self.assertLess(err, before_cg_tol) + + err = norm(t4_vec_to_goose(µK) - K4.reshape(-1))/norm(K4) + if not (err < before_cg_tol): + print ("err = {}".format(err)) + self.t4_comparator(µK, K4) + self.assertLess(err, before_cg_tol) + # convert res.stress to residual + b = -G(P) + µb = -µG(µP) + # in the last iteration, the rhs is essentianly zero, + # leading to large relative errors, which are ok. So we + # want either the relative error for the rhs to be small, + # or their absolute error to be small compared to unity + diff_norm = norm(t2_vec_to_goose(µb) - b.reshape(-1)) + err = diff_norm/norm(b) + if not ((err < after_cg_tol) or (diff_norm < before_cg_tol)): + self.t2_comparator(µb.reshape(µF.shape), b.reshape(F.shape)) + print("|µb| = {}".format(norm(µb))) + print("|b| = {}".format(norm(b))) + print("err = {}".format(err)) + print("|µb-b| = {}".format(norm(t2_vec_to_goose(µb) - b.reshape(-1)))) + + print("AssertionWarning: {} is not less than {}".format(err, before_cg_tol)) + self.assertTrue((err < after_cg_tol) or (diff_norm < after_cg_tol)) + # print residual to the screen + print('Goose: %10.15e'%(np.linalg.norm(dFm)/Fn)) + print('µSpectre: %10.15e'%(np.linalg.norm(µdFm)/µFn)) + if np.linalg.norm(dFm)/Fn0: break # check convergence + iiter += 1 + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/python_exact_reference_plastic_test.py b/tests/python_exact_reference_plastic_test.py new file mode 100644 index 0000000..72e747e --- /dev/null +++ b/tests/python_exact_reference_plastic_test.py @@ -0,0 +1,784 @@ +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- +from python_exact_reference_elastic_test import ndim, N, Nx, Ny, Nz +from material_hyper_elasto_plastic1 import PK1_fun_3d +import python_exact_reference_elastic_test as elastic_ref +from python_exact_reference_elastic_test import Counter +from python_exact_reference_elastic_test import t2_from_goose +from python_exact_reference_elastic_test import t4_vec_to_goose +from python_exact_reference_elastic_test import t4_to_goose +from python_exact_reference_elastic_test import scalar_vec_to_goose +from python_exact_reference_elastic_test import t2_vec_to_goose +from python_exact_reference_elastic_test import deserialise_t4, t2_to_goose +import sys +import itertools +import scipy.sparse.linalg as sp +""" +file python_exact_reference_plastic_test.py + +@author Till Junge + +@date 22 Jun 2018 + +@brief Tests exactness of each iterate with respect to python reference + implementation from GooseFFT for plasticity + +Copyright © 2018 Till Junge + +µSpectre is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation, either version 3, or (at +your option) any later version. + +µSpectre 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. + +Additional permission under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or combining it +with proprietary FFT implementations or numerical libraries, containing parts +covered by the terms of those libraries' licenses, the licensors of this +Program grant you additional permission to convey the resulting work. +""" + + +import unittest +import numpy as np +import numpy.linalg as linalg +from python_test_imports import µ +# turn of warning for zero division +# (which occurs in the linearization of the logarithmic strain) +np.seterr(divide='ignore', invalid='ignore') + + +# ----------------------------------- GRID ------------------------------------ +shape = [Nx, Ny, Nz] + + +def standalone_dyad22(A2, B2): return np.einsum('ij ,kl ->ijkl', A2, B2) + + +def standalone_dyad11(A1, B1): return np.einsum('i ,j ->ij ', A1, B1) + + +def standalone_dot22(A2, B2): return np.einsum('ij ,jk ->ik ', A2, B2) + + +def standalone_dot24(A2, B4): return np.einsum('ij ,jkmn->ikmn', A2, B4) + + +def standalone_dot42(A4, B2): return np.einsum('ijkl,lm ->ijkm', A4, B2) + + +standalone_inv2 = np.linalg.inv + + +def standalone_ddot22(A2, B2): return np.einsum('ij ,ji -> ', A2, B2) + + +def standalone_ddot42(A4, B2): return np.einsum('ijkl,lk ->ij ', A4, B2) + + +def standalone_ddot44(A4, B4): return np.einsum('ijkl,lkmn->ijmn', A4, B4) + + +def constitutive_standalone(K, mu, H, tauy0, F, F_t, be_t, ep_t, dim): + I = np.eye(dim) + II = standalone_dyad22(I, I) + I4 = np.einsum('il,jk', I, I) + I4rt = np.einsum('ik,jl', I, I) + I4s = (I4+I4rt)/2. + + def ln2(A2): + vals, vecs = np.linalg.eig(A2) + return sum( + [np.log(vals[i])*standalone_dyad11(vecs[:, i], vecs[:, i]) + for i in range(dim)]) + + def exp2(A2): + vals, vecs = np.linalg.eig(A2) + return sum( + [np.exp(vals[i])*standalone_dyad11(vecs[:, i], vecs[:, i]) + for i in range(dim)]) + + # function to compute linearization of the logarithmic Finger tensor + def dln2_d2(A2): + vals, vecs = np.linalg.eig(A2) + K4 = np.zeros([dim, dim, dim, dim]) + for m, n in itertools.product(range(dim), repeat=2): + + if vals[n] == vals[m]: + gc = (1.0/vals[m]) + else: + gc = (np.log(vals[n])-np.log(vals[m]))/(vals[n]-vals[m]) + K4 += gc*standalone_dyad22(standalone_dyad11( + vecs[:, m], vecs[:, n]), standalone_dyad11(vecs[:, m], + vecs[:, n])) + return K4 + + # elastic stiffness tensor + C4e = K*II+2.*mu*(I4s-1./3.*II) + + # trial state + Fdelta = standalone_dot22(F, standalone_inv2(F_t)) + be_s = standalone_dot22(Fdelta, standalone_dot22(be_t, Fdelta.T)) + lnbe_s = ln2(be_s) + tau_s = standalone_ddot42(C4e, lnbe_s)/2. + taum_s = standalone_ddot22(tau_s, I)/3. + taud_s = tau_s-taum_s*I + taueq_s = np.sqrt(3./2.*standalone_ddot22(taud_s, taud_s)) + div = np.where(taueq_s < 1e-12, np.ones_like(taueq_s), taueq_s) + N_s = 3./2.*taud_s/div + phi_s = taueq_s-(tauy0+H*ep_t) + phi_s = 1./2.*(phi_s+np.abs(phi_s)) + + # return map + dgamma = phi_s/(H+3.*mu) + ep = ep_t + dgamma + tau = tau_s - 2.*dgamma*N_s*mu + lnbe = lnbe_s-2.*dgamma*N_s + be = exp2(lnbe) + P = standalone_dot22(tau, standalone_inv2(F).T) + + # consistent tangent operator + a0 = dgamma*mu/taueq_s + a1 = mu/(H+3.*mu) + C4ep = (((K-2./3.*mu)/2.+a0*mu)*II+(1.-3.*a0)*mu * + I4s+2.*mu*(a0-a1)*standalone_dyad22(N_s, N_s)) + dlnbe4_s = dln2_d2(be_s) + dbe4_s = 2.*standalone_dot42(I4s, be_s) + # K4a = ((C4e/2.)*(phi_s<=0.).astype(np.float)+ + # C4ep*(phi_s>0.).astype(np.float)) + K4a = np.where(phi_s <= 0, C4e/2., C4ep) + K4b = standalone_ddot44(K4a, standalone_ddot44(dlnbe4_s, dbe4_s)) + K4c = standalone_dot42(-I4rt, tau)+K4b + K4 = standalone_dot42(standalone_dot24( + standalone_inv2(F), K4c), standalone_inv2(F).T) + + return P, tau, K4, be, ep, dlnbe4_s, dbe4_s, K4a, K4b, K4c + + +# ----------------------------- TENSOR OPERATIONS ----------------------------- + +# tensor operations / products: np.einsum enables index notation, avoiding loops +# e.g. ddot42 performs $C_ij = A_ijkl B_lk$ for the entire grid +def trans2(A2): return np.einsum('ijxyz ->jixyz ', A2) + + +def ddot22(A2, B2): return np.einsum('ijxyz ,jixyz ->xyz ', A2, B2) + + +def ddot42(A4, B2): return np.einsum('ijklxyz,lkxyz ->ijxyz ', A4, B2) + + +def ddot44(A4, B4): return np.einsum('ijklxyz,lkmnxyz->ijmnxyz', A4, B4) + + +def dot11(A1, B1): return np.einsum('ixyz ,ixyz ->xyz ', A1, B1) + + +def dot22(A2, B2): return np.einsum('ijxyz ,jkxyz ->ikxyz ', A2, B2) + + +def dot24(A2, B4): return np.einsum('ijxyz ,jkmnxyz->ikmnxyz', A2, B4) + + +def dot42(A4, B2): return np.einsum('ijklxyz,lmxyz ->ijkmxyz', A4, B2) + + +def dyad22(A2, B2): return np.einsum('ijxyz ,klxyz ->ijklxyz', A2, B2) + + +def dyad11(A1, B1): return np.einsum('ixyz ,jxyz ->ijxyz ', A1, B1) + + +# eigenvalue decomposition of 2nd-order tensor: return in convention i,j,x,y,z +# NB requires to swap default order of NumPy (in in/output) +def eig2(A2): + def swap1i(A1): return np.einsum('xyzi ->ixyz ', A1) + + def swap2(A2): return np.einsum('ijxyz->xyzij', A2) + + def swap2i(A2): return np.einsum('xyzij->ijxyz', A2) + vals, vecs = np.linalg.eig(swap2(A2)) + vals = swap1i(vals) + vecs = swap2i(vecs) + return vals, vecs + +# logarithm of grid of 2nd-order tensors + + +def ln2(A2): + vals, vecs = eig2(A2) + return sum([np.log(vals[i])*dyad11(vecs[:, i], vecs[:, i]) + for i in range(3)]) + +# exponent of grid of 2nd-order tensors + + +def exp2(A2): + vals, vecs = eig2(A2) + return sum([np.exp(vals[i])*dyad11(vecs[:, i], vecs[:, i]) + for i in range(3)]) + +# determinant of grid of 2nd-order tensors + + +def det2(A2): + return (A2[0, 0]*A2[1, 1]*A2[2, 2]+A2[0, 1]*A2[1, 2]*A2[2, 0] + + A2[0, 2]*A2[1, 0]*A2[2, 1]) -\ + (A2[0, 2]*A2[1, 1]*A2[2, 0]+A2[0, 1]*A2[1, 0] + * A2[2, 2]+A2[0, 0]*A2[1, 2]*A2[2, 1]) + +# inverse of grid of 2nd-order tensors + + +def inv2(A2): + A2det = det2(A2) + A2inv = np.empty([3, 3, Nx, Ny, Nz]) + A2inv[0, 0] = (A2[1, 1]*A2[2, 2]-A2[1, 2]*A2[2, 1])/A2det + A2inv[0, 1] = (A2[0, 2]*A2[2, 1]-A2[0, 1]*A2[2, 2])/A2det + A2inv[0, 2] = (A2[0, 1]*A2[1, 2]-A2[0, 2]*A2[1, 1])/A2det + A2inv[1, 0] = (A2[1, 2]*A2[2, 0]-A2[1, 0]*A2[2, 2])/A2det + A2inv[1, 1] = (A2[0, 0]*A2[2, 2]-A2[0, 2]*A2[2, 0])/A2det + A2inv[1, 2] = (A2[0, 2]*A2[1, 0]-A2[0, 0]*A2[1, 2])/A2det + A2inv[2, 0] = (A2[1, 0]*A2[2, 1]-A2[1, 1]*A2[2, 0])/A2det + A2inv[2, 1] = (A2[0, 1]*A2[2, 0]-A2[0, 0]*A2[2, 1])/A2det + A2inv[2, 2] = (A2[0, 0]*A2[1, 1]-A2[0, 1]*A2[1, 0])/A2det + return A2inv + +# ------------------------ INITIATE (IDENTITY) TENSORS ------------------------ + + +# identity tensor (single tensor) +i = np.eye(3) +# identity tensors (grid) +I = np.einsum('ij,xyz', i, np.ones([Nx, Ny, Nz])) +I4 = np.einsum('ijkl,xyz->ijklxyz', np.einsum('il,jk', i, i), + np.ones([Nx, Ny, Nz])) +I4rt = np.einsum('ijkl,xyz->ijklxyz', np.einsum('ik,jl', i, i), + np.ones([Nx, Ny, Nz])) +I4s = (I4+I4rt)/2. +II = dyad22(I, I) + +# ------------------------------------ FFT ------------------------------------ + +# projection operator (only for non-zero frequency, associated with the mean) +# NB: vectorized version of "hyper-elasticity.py" +# - allocate / support function +Ghat4 = np.zeros([3, 3, 3, 3, Nx, Ny, Nz]) # projection operator +x = np.zeros([3, Nx, Ny, Nz], dtype='int64') # position vectors +q = np.zeros([3, Nx, Ny, Nz], dtype='int64') # frequency vectors + + +# Dirac delta function +def delta(i, j): return np.float(i == j) + + +# - set "x" as position vector of all grid-points [grid of vector-components] +x[0], x[1], x[2] = np.mgrid[:Nx, :Ny, :Nz] +# - convert positions "x" to frequencies "q" [grid of vector-components] +for i in range(3): + freq = np.arange(-(shape[i]-1)/2, +(shape[i]+1)/2, dtype='int64') + q[i] = freq[x[i]] +# - compute "Q = ||q||", and "norm = 1/Q" being zero for the mean (Q==0) +# NB: avoid zero division +q = q.astype(np.float) +Q = dot11(q, q) +Z = Q == 0 +Q[Z] = 1. +norm = 1./Q +norm[Z] = 0. +# - set projection operator [grid of tensors] +for i, j, l, m in itertools.product(range(3), repeat=4): + Ghat4[i, j, l, m] = norm*delta(i, m)*q[j]*q[l] + +# (inverse) Fourier transform (for each tensor component in each direction) + + +def fft(x): return np.fft.fftshift( + np.fft.fftn(np.fft.ifftshift(x), [Nx, Ny, Nz])) + + +def ifft(x): return np.fft.fftshift( + np.fft.ifftn(np.fft.ifftshift(x), [Nx, Ny, Nz])) + + +# functions for the projection 'G', and the product 'G : K^LT : (delta F)^T' +def G(A2): return np.real(ifft(ddot42(Ghat4, fft(A2)))).reshape(-1) + + +def K_dF(dFm): return trans2(ddot42(K4, trans2(dFm.reshape(3, 3, Nx, Ny, Nz)))) + + +def G_K_dF(dFm): return G(K_dF(dFm)) + +# --------------------------- CONSTITUTIVE RESPONSE --------------------------- + +# constitutive response to a certain loading and history +# NB: completely uncoupled from the FFT-solver, but implemented as a regular +# grid of quadrature points, to have an efficient code; +# each point is completely independent, just evaluated at the same time + + +def constitutive(F, F_t, be_t, ep_t): + + # function to compute linearization of the logarithmic Finger tensor + def dln2_d2(A2): + vals, vecs = eig2(A2) + K4 = np.zeros([3, 3, 3, 3, Nx, Ny, Nz]) + for m, n in itertools.product(range(3), repeat=2): + gc = (np.log(vals[n])-np.log(vals[m]))/(vals[n]-vals[m]) + gc[vals[n] == vals[m]] = (1.0/vals[m])[vals[n] == vals[m]] + K4 += gc*dyad22(dyad11(vecs[:, m], vecs[:, n]), + dyad11(vecs[:, m], vecs[:, n])) + return K4 + + # elastic stiffness tensor + C4e = K*II+2.*mu*(I4s-1./3.*II) + + # trial state + Fdelta = dot22(F, inv2(F_t)) + be_s = dot22(Fdelta, dot22(be_t, trans2(Fdelta))) + lnbe_s = ln2(be_s) + tau_s = ddot42(C4e, lnbe_s)/2. + taum_s = ddot22(tau_s, I)/3. + taud_s = tau_s-taum_s*I + taueq_s = np.sqrt(3./2.*ddot22(taud_s, taud_s)) + div = np.where(taueq_s < 1e-12, np.ones_like(taueq_s), taueq_s) + N_s = 3./2.*taud_s/div + phi_s = taueq_s-(tauy0+H*ep_t) + phi_s = 1./2.*(phi_s+np.abs(phi_s)) + + # return map + dgamma = phi_s/(H+3.*mu) + ep = ep_t + dgamma + tau = tau_s - 2.*dgamma*N_s*mu + lnbe = lnbe_s-2.*dgamma*N_s + be = exp2(lnbe) + P = dot22(tau, trans2(inv2(F))) + + # consistent tangent operator + a0 = dgamma*mu/taueq_s + a1 = mu/(H+3.*mu) + C4ep = ((K-2./3.*mu)/2.+a0*mu)*II+(1.-3.*a0) * \ + mu*I4s+2.*mu*(a0-a1)*dyad22(N_s, N_s) + dlnbe4_s = dln2_d2(be_s) + dbe4_s = 2.*dot42(I4s, be_s) + + K4a = np.where(phi_s <= 0, C4e/2., C4ep) + K4b = ddot44(K4a, ddot44(dlnbe4_s, dbe4_s)) + K4c = dot42(-I4rt, tau)+K4b + K4 = dot42(dot24(inv2(F), K4c), trans2(inv2(F))) + + return P, K4, be, ep, dlnbe4_s, dbe4_s, K4a, K4b, K4c + + +# phase indicator: square inclusion of volume fraction (3*3*15)/(11*13*15) +phase = np.zeros([Nx, Ny, Nz]) +phase[0, 0, 0] = 1. +# function to convert material parameters to grid of scalars + + +def param(M0, M1): return M0*np.ones([Nx, Ny, Nz])*(1.-phase) +\ + M1*np.ones([Nx, Ny, Nz]) * phase + + +# material parameters +K = param(0.833, 0.833) # bulk modulus +Kmat = K +mu = param(0.386, 0.386) # shear modulus +H = param(0.004, 0.008) # hardening modulus +tauy0 = param(0.003, 0.006) # initial yield stress + +# ---------------------------------- LOADING ---------------------------------- + +# stress, deformation gradient, plastic strain, elastic Finger tensor +# NB "_t" signifies that it concerns the value at the previous increment +ep_t = np.zeros([Nx, Ny, Nz]) +P = np.zeros([3, 3, Nx, Ny, Nz]) +F = np.array(I, copy=True) +F_t = np.array(I, copy=True) +be_t = np.array(I, copy=True) + +# initialize macroscopic incremental loading +ninc = 50 +lam = 0.0 +barF = np.array(I, copy=True) +barF_t = np.array(I, copy=True) + +# initial tangent operator: the elastic tangent +K4 = K*II+2.*mu*(I4s-1./3.*II) + + +class ElastoPlastic_Check(unittest.TestCase): + t2_comparator = elastic_ref.LinearElastic_Check.t2_comparator + t4_comparator = elastic_ref.LinearElastic_Check.t4_comparator + + def scalar_comparator(self, µ, g): + err_sum = 0. + err_max = 0. + for counter, (i, j, k) in enumerate(self.rve): + print((i, j, k)) + µ_arr = µ[counter] + g_arr = g[i, j, k] + self.assertEqual(Nz*Ny*i+Nz*j + k, counter) + print("µSpectre:") + print(µ_arr) + print("Goose:") + print(g_arr) + print(µ_arr-g_arr) + err = linalg.norm(µ_arr-g_arr) + print("error norm = {}".format(err)) + err_sum += err + err_max = max(err_max, err) + pass + print("∑(err) = {}, max(err) = {}".format(err_sum, err_max)) + return err_sum + + def setUp(self): + # ---------------------------- µSpectre init -------------------------- + resolution = list(phase.shape) + dim = len(resolution) + self.dim = dim + + center = np.array([r//2 for r in resolution]) + incl = resolution[0]//5 + + # Domain dimensions + lengths = [float(r) for r in resolution] + ## formulation (small_strain or finite_strain) + formulation = µ.Formulation.finite_strain + + # build a computational domain + self.rve = µ.Cell(resolution, lengths, formulation) + + def get_E_nu(bulk, shear): + Young = 9*bulk*shear/(3*bulk + shear) + Poisson = Young/(2*shear) - 1 + return Young, Poisson + + mat = µ.material.MaterialHyperElastoPlastic1_3d + + E, nu = get_E_nu(.833, .386) + H = 0.004 + tauy0 = .003 + self.hard = mat.make(self.rve, 'hard', E, nu, 2*tauy0, 2*H) + self.soft = mat.make(self.rve, 'soft', E, nu, tauy0, H) + + for pixel in self.rve: + if pixel[0] == 0 and pixel[1] == 0 and pixel[2] == 0: + self.hard.add_pixel(pixel) + else: + self.soft.add_pixel(pixel) + pass + pass + return + + def test_solve(self): + strict_tol = 1e-11 + cg_tol = 1e-11 + after_cg_tol = 1e-10 + newton_tol = 1e-5 + self.rve.set_uniform_strain(np.array(np.eye(ndim))) + µF = self.rve.get_strain() + µF_t = µF.copy() + µbarF_t = µF.copy() + # incremental deformation + for inc in range(1, ninc): + + print('=============================') + print('inc: {0:d}'.format(inc)) + + # set macroscopic deformation gradient (pure-shear) + global lam, F, F_t, barF_t, K4, be_t, ep_t + lam += 0.2/float(ninc) + barF = np.array(I, copy=True) + barF[0, 0] = (1.+lam) + barF[1, 1] = 1./(1.+lam) + + def rel_error_scalar(µ, g, tol, do_assert=True): + err = (linalg.norm(scalar_vec_to_goose(µ) - g.reshape(-1)) / + linalg.norm(g)) + if not (err < tol): + self.scalar_comparator(µ.reshape(-1), g) + if do_assert: + self.assertLess(err, tol) + else: + print("AssertionWarning: {} is not less than {}".format( + err, tol)) + pass + return err + + def rel_error_t2(µ, g, tol, do_assert=True): + err = linalg.norm(t2_vec_to_goose( + µ) - g.reshape(-1)) / linalg.norm(g) + if not (err < tol): + self.t2_comparator(µ.reshape(µF.shape), g.reshape(F.shape)) + if do_assert: + self.assertLess(err, tol) + else: + print("AssertionWarning: {} is not less than {}".format( + err, tol)) + pass + return err + + def rel_error_t4(µ, g, tol, right_transposed=True, do_assert=True, + pixel_tol=1e-4): + err = linalg.norm(t4_vec_to_goose( + µ) - g.reshape(-1)) / linalg.norm(g) + errors = None + if not (err < tol): + err_sum, errors = self.t4_comparator(µ.reshape(µK.shape), + g.reshape(K4.shape), + right_transposed) + if do_assert: + self.assertLess(err, tol) + else: + print("AssertionWarning: {} is not less than {}".format( + err, tol)) + + pass + return err, errors + + def abs_error_t2(µ, g, tol, do_assert=True): + ref_norm = linalg.norm(g) + if ref_norm > 1: + return rel_error_t2(µ, g, tol, do_assert) + else: + err = linalg.norm(t2_vec_to_goose(µ) - g.reshape(-1)) + if not (err < tol): + self.t2_comparator( + µ.reshape(µF.shape), g.reshape(F.shape)) + if do_assert: + self.assertLess(err, tol) + else: + print(("AssertionWarning: {} is not less than {}" + + "").format( + err, tol)) + return err + + rel_error_t2(µF, F, strict_tol) + + # store normalization + Fn = np.linalg.norm(F) + + # first iteration residual: distribute "barF" over grid using "K4" + b = -G_K_dF(barF-barF_t) + F += barF-barF_t + + # parameters for Newton iterations: normalization and iteration + # counter + Fn = np.linalg.norm(F) + iiter = 0 + + # µSpectre inits + µbarF = np.zeros_like(µF) + µbarF[0, :] = 1. + lam + µbarF[ndim + 1, :] = 1./(1. + lam) + µbarF[-1, :] = 1. + rel_error_t2(µbarF, barF, strict_tol) + if inc == 1: + µP, µK = self.rve.evaluate_stress_tangent(µF) + rel_error_t4(µK, K4, strict_tol) + µF += µbarF - µbarF_t + rel_error_t2(µF, F, strict_tol) + + µFn = linalg.norm(µF) + self.assertLess((µFn-Fn)/Fn, strict_tol) + + def µG_K_dF(x): return self.rve.directional_stiffness( + x.reshape(µF.shape)).reshape(-1) + + def µG(x): return self.rve.project(x).reshape(-1) + µb = -µG_K_dF(µbarF-µbarF_t) + abs_error_t2(µb, b, strict_tol) + # because of identical elastic properties, µb has got to + # be zero before plasticity kicks in + print("inc = {}".format(inc)) + if inc == 1: + self.assertLess(linalg.norm(µb), strict_tol) + + global_be_t = self.rve.get_globalised_current_real_array( + "Previous left Cauchy-Green deformation bₑᵗ") + + # iterate as long as the iterative update does not vanish + while True: + + # solve linear system using the Conjugate Gradient iterative + # solver + g_counter = Counter() + dFm, _ = sp.cg(tol=cg_tol, + atol=1e-10, + A=sp.LinearOperator( + shape=(F.size, F.size), matvec=G_K_dF, + dtype='float'), + b=b, + callback=g_counter + ) + µ_counter = Counter() + µdFm, _ = sp.cg(tol=cg_tol, + atol=1e-10, + A=sp.LinearOperator(shape=(F.size, F.size), + matvec=µG_K_dF, + dtype='float'), + b=µb, + callback=µ_counter) + + err = g_counter.get()-µ_counter.get() + if err != 0: + print( + "n_iter(g) = {}, n_iter(µ) = {}".format( + g_counter.get(), µ_counter.get())) + print("AssertionWarning: {} != {}".format(g_counter.get(), + µ_counter.get())) + try: + err = abs_error_t2(µdFm, dFm, after_cg_tol, do_assert=True) + except Exception as err: + raise err + # add solution of linear system to DOFs + F += dFm.reshape(3, 3, Nx, Ny, Nz) + µF += µdFm.reshape(µF.shape) + + err = rel_error_t2(µF, F, strict_tol, do_assert=True) + # compute residual stress and tangent, convert to residual + P, K4, be, ep, dln, dbe4_s, K4a, K4b, K4c = constitutive( + F, F_t, be_t, ep_t) + µP, µK = self.rve.evaluate_stress_tangent(µF) + err = rel_error_t2(µP, P, strict_tol) + µbe = self.rve.get_globalised_current_real_array( + "Previous left Cauchy-Green deformation bₑᵗ") + err = rel_error_t2(µbe, be, strict_tol) + + µep = self.rve.get_globalised_current_real_array( + "cumulated plastic flow εₚ") + err = rel_error_scalar(µep, ep, strict_tol) + + err, errors = rel_error_t4(µK, K4, strict_tol, do_assert=False) + if not err < strict_tol: + def t2_disp(name, µ, g, i, j, k, index): + g_v = g[:, :, i, j, k].copy() + µ_v = µ[:, index].reshape(3, 3).T + print("{}_g =\n{}".format(name, g_v)) + print("{}_µ =\n{}".format(name, µ_v)) + print("{}_err = {}".format( + name, np.linalg.norm(g_v-µ_v))) + return g_v, µ_v + + def t0_disp(name, µ, g, i, j, k, index): + g_v = g[i, j, k].copy() + µ_v = µ[:, index] + print("{}_g =\n{}".format(name, g_v)) + print("{}_µ =\n{}".format(name, µ_v)) + print("{}_err = {}".format(name, abs(g_v-µ_v))) + return g_v, µ_v + + for pixel, error in errors.items(): + for index, (ir, jr, kr) in enumerate(self.rve): + i, j, k = pixel + if (i, j, k) == (ir, jr, kr): + break + if error > 1e-4: + i, j, k = pixel + print("error for pixel {} ({}) = {}".format( + pixel, index, error)) + t2_disp("F", µF, F, i, j, k, index) + t2_disp("be", µbe, be, i, j, k, index) + F_t_n, dummy = t2_disp( + "F_t", µF_t, F_t, i, j, k, index) + print("ep.shape = {}".format(µep.shape)) + t0_disp("ep", µep, ep, i, j, k, index) + K_comp_g = K4[:, :, :, :, i, j, k].reshape(9, 9) + K_comp_µ = µK[:, index].reshape( + 3, 3, 3, 3).transpose( + 1, 0, 3, 2).reshape(9, 9) + F_n = F[:, :, i, j, k] + be_n = be_t[:, :, i, j, k] + ep_n = ep_t[i, j, k] + response = constitutive_standalone(Kmat[pixel], + mu[pixel], + H[pixel], + tauy0[pixel], + F_n, + F_t_n, be_n, + ep_n, 3) + P_gn, K_gn = response[0], response[2] + P_µn, K_µn = PK1_fun_3d(Kmat[pixel], + mu[pixel], + H[pixel], + tauy0[pixel], F_n, + F_t_n, be_n, ep_n) + K_µn = K_µn.reshape(3, 3, 3, 3).transpose( + 1, 0, 3, 2).reshape(9, 9) + print("P_µn =\n{}".format(P_µn)) + print("P_gn =\n{}".format(P_gn)) + P_comp_gn, P_comp_µn = t2_disp( + "P_comp", µP, P, i, j, k, index) + print("|P_µn - P_comp_µn| = {}".format( + np.linalg.norm(P_µn-P_comp_µn))) + print("|P_gn - P_comp_gn| = {}".format( + np.linalg.norm(P_gn-P_comp_gn))) + print("|P_gn - P_µn| = {}".format( + np.linalg.norm(P_gn-P_µn))) + print("|P_comp_gn - P_comp_µn| = {}".format( + np.linalg.norm(P_comp_gn-P_comp_µn))) + print() + K_gn.shape = 9, 9 + print("K_µn.shape = {}".format(K_µn.shape)) + print("K_gn.shape = {}".format(K_gn.shape)) + print("K_comp_g =\n{}".format(K_comp_g)) + print("K_comp_µ =\n{}".format(K_comp_µ)) + print("K_µn=\n{}".format(K_µn)) + print("K_gn=\n{}".format(K_gn)) + print("|K_µn - K_comp_µ| = {}".format( + np.linalg.norm(K_µn-K_comp_µ))) + print("|K_gn - K_comp_g| = {}".format( + np.linalg.norm(K_gn-K_comp_g))) + print("|K_gn - K_µn| = {}".format( + np.linalg.norm(K_gn-K_µn))) + print("|K_comp_g - K_comp_µ| = {}".format( + np.linalg.norm(K_comp_g-K_comp_µ))) + break + + raise AssertionError( + "at iiter = {}, inc = {}, caught this: '{}'".format( + iiter, inc, err)) + + b = -G(P) + µb = -µG(µP) + + err = abs_error_t2(µb, b, strict_tol) # after_cg_tol) + + # check for convergence, print convergence info to screen + print( + 'Goose: rel_residual {:10.15e}, |rhs|: {:10.15e}'.format( + np.linalg.norm(dFm)/Fn, linalg.norm(b))) + print( + 'µSpectre:rel_residual {:10.15e}, |rhs|: {:10.15e}'.format( + np.linalg.norm(µdFm)/µFn, linalg.norm(µb))) + if np.linalg.norm(dFm)/Fn < 1.e-5 and iiter > 0: + break + + # update Newton iteration counter + print("reached end of iiter = {}".format(iiter)) + iiter += 1 + + # end-of-increment: update history + barF_t = np.array(barF, copy=True) + µbarF_t[:] = µbarF + F_t = np.array(F, copy=True) + be_t = np.array(be, copy=True) + ep_t = np.array(ep, copy=True) + µF_t[:] = µF + self.rve.save_history_variables() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/python_field_tests.py b/tests/python_field_tests.py index e44ad84..7072a3b 100644 --- a/tests/python_field_tests.py +++ b/tests/python_field_tests.py @@ -1,78 +1,78 @@ """ file python_field_tests.py @author Till Junge @date 06 Jul 2018 @brief tests the python bindings for fieldcollections, fields, and statefields Copyright © 2018 Till Junge µSpectre is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3, or (at your option) any later version. µSpectre 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 General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with µSpectre; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Additional permission under GNU GPL version 3 section 7 If you modify this Program, or any covered work, by linking or combining it with proprietary FFT implementations or numerical libraries, containing parts covered by the terms of those libraries' licenses, the licensors of this Program grant you additional permission to convey the resulting work. """ import unittest import numpy as np from python_test_imports import µ class FieldCollection_Check(unittest.TestCase): """Because field collections do not have python-accessible constructors, this test creates a problem with a material with statefields """ def setUp(self): self.resolution = [3, 3] self.lengths = [1.58, 5.87] self.formulation = µ.Formulation.finite_strain self.cell = µ.Cell(self.resolution, self.lengths, self.formulation) self.dim = len(self.lengths) self.mat = µ.material.MaterialLinearElastic2_2d.make( self.cell, "material", 210e9, .33) def test_fields(self): eigen_strain = np.array([[.01, .02], [.03, -.01]]) for i, pixel in enumerate(self.cell): self.mat.add_pixel(pixel, i/self.cell.size*eigen_strain) self.cell.initialise() - dir(µ.material.Material_2d) - self.assertTrue(isinstance(self.mat, µ.material.Material_2d)) + dir(µ.material.MaterialBase_2d) + self.assertTrue(isinstance(self.mat, µ.material.MaterialBase_2d)) collection = self.mat.collection field_name = collection.field_names[0] self.assertRaises(Exception, collection.get_complex_field, field_name) self.assertRaises(Exception, collection.get_int_field, field_name) self.assertRaises(Exception, collection.get_uint_field, field_name) eigen_strain_field = collection.get_real_field(field_name) print(eigen_strain_field.array.T) for i, row in enumerate(eigen_strain_field.array.T): error = np.linalg.norm(i/self.cell.size*eigen_strain - row.reshape(eigen_strain.shape).T) self.assertEqual(0, error) diff --git a/tests/test_field_collections_1.cc b/tests/test_field_collections_1.cc new file mode 100644 index 0000000..cb3260c --- /dev/null +++ b/tests/test_field_collections_1.cc @@ -0,0 +1,222 @@ +/** + * @file test_field_collections_1.cc + * + * @author Till Junge + * + * @date 20 Sep 2017 + * + * @brief Test the FieldCollection classes which provide fast optimized + * iterators over run-time typed fields + * + * Copyright © 2017 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with µSpectre; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Additional permission under GNU GPL version 3 section 7 + * + * If you modify this Program, or any covered work, by linking or combining it + * with proprietary FFT implementations or numerical libraries, containing parts + * covered by the terms of those libraries' licenses, the licensors of this + * Program grant you additional permission to convey the resulting work. + */ + +#include "test_field_collections.hh" +namespace muSpectre { + + BOOST_AUTO_TEST_SUITE(field_collection_tests); + + BOOST_AUTO_TEST_CASE(simple) { + constexpr Dim_t sdim = 2; + using FC_t = GlobalFieldCollection; + FC_t fc; + + BOOST_CHECK_EQUAL(FC_t::spatial_dim(), sdim); + BOOST_CHECK_EQUAL(fc.get_spatial_dim(), sdim); + } + + BOOST_FIXTURE_TEST_CASE_TEMPLATE(Simple_construction_test, F, + test_collections, F) { + BOOST_CHECK_EQUAL(F::FC_t::spatial_dim(), F::sdim()); + BOOST_CHECK_EQUAL(F::fc.get_spatial_dim(), F::sdim()); + } + + BOOST_FIXTURE_TEST_CASE_TEMPLATE(get_field2_test, F, test_collections, F) { + const auto order{2}; + + using FC_t = typename F::FC_t; + using TF_t = TensorField; + auto && myfield = make_field("TensorField real 2", F::fc); + + using TensorMap = TensorFieldMap; + using MatrixMap = MatrixFieldMap; + using ArrayMap = ArrayFieldMap; + + TensorMap TFM(myfield); + MatrixMap MFM(myfield); + ArrayMap AFM(myfield); + + BOOST_CHECK_EQUAL(TFM.info_string(), "Tensor(d, " + std::to_string(order) + + "_o, " + + std::to_string(F::mdim()) + "_d)"); + BOOST_CHECK_EQUAL(MFM.info_string(), "Matrix(d, " + + std::to_string(F::mdim()) + "x" + + std::to_string(F::mdim()) + ")"); + BOOST_CHECK_EQUAL(AFM.info_string(), "Array(d, " + + std::to_string(F::mdim()) + "x" + + std::to_string(F::mdim()) + ")"); + } + + BOOST_FIXTURE_TEST_CASE_TEMPLATE(multi_field_test, F, mult_collections, F) { + using FC_t = typename F::FC_t; + // possible maptypes for Real tensor fields + using T_type = Real; + using T_TFM1_t = TensorFieldMap; + using T_TFM2_t = + TensorFieldMap; //! dangerous + using T4_Map_t = T4MatrixFieldMap; + + // impossible maptypes for Real tensor fields + using T_SFM_t = ScalarFieldMap; + using T_MFM_t = MatrixFieldMap; + using T_AFM_t = ArrayFieldMap; + using T_MFMw1_t = MatrixFieldMap; + using T_MFMw2_t = MatrixFieldMap; + using T_MFMw3_t = MatrixFieldMap; + const std::string T_name{"Tensorfield Real o4"}; + const std::string T_name_w{"TensorField Real o4 wrongname"}; + + BOOST_CHECK_THROW(T_SFM_t(F::fc.at(T_name)), FieldInterpretationError); + BOOST_CHECK_NO_THROW(T_TFM1_t(F::fc.at(T_name))); + BOOST_CHECK_NO_THROW(T_TFM2_t(F::fc.at(T_name))); + BOOST_CHECK_NO_THROW(T4_Map_t(F::fc.at(T_name))); + BOOST_CHECK_THROW(T4_Map_t(F::fc.at(T_name_w)), std::out_of_range); + BOOST_CHECK_THROW(T_MFM_t(F::fc.at(T_name)), FieldInterpretationError); + BOOST_CHECK_THROW(T_AFM_t(F::fc.at(T_name)), FieldInterpretationError); + BOOST_CHECK_THROW(T_MFMw1_t(F::fc.at(T_name)), FieldInterpretationError); + BOOST_CHECK_THROW(T_MFMw2_t(F::fc.at(T_name)), FieldInterpretationError); + BOOST_CHECK_THROW(T_MFMw2_t(F::fc.at(T_name)), FieldInterpretationError); + BOOST_CHECK_THROW(T_MFMw3_t(F::fc.at(T_name)), FieldInterpretationError); + BOOST_CHECK_THROW(T_SFM_t(F::fc.at(T_name_w)), std::out_of_range); + + // possible maptypes for integer scalar fields + using S_type = Int; + using S_SFM_t = ScalarFieldMap; + using S_TFM1_t = TensorFieldMap; + using S_TFM2_t = TensorFieldMap; + using S_MFM_t = MatrixFieldMap; + using S_AFM_t = ArrayFieldMap; + using S4_Map_t = T4MatrixFieldMap; + // impossible maptypes for integer scalar fields + using S_MFMw1_t = MatrixFieldMap; + using S_MFMw2_t = MatrixFieldMap; + using S_MFMw3_t = MatrixFieldMap; + const std::string S_name{"integer Scalar"}; + const std::string S_name_w{"integer Scalar wrongname"}; + + BOOST_CHECK_NO_THROW(S_SFM_t(F::fc.at(S_name))); + BOOST_CHECK_NO_THROW(S_TFM1_t(F::fc.at(S_name))); + BOOST_CHECK_NO_THROW(S_TFM2_t(F::fc.at(S_name))); + BOOST_CHECK_NO_THROW(S_MFM_t(F::fc.at(S_name))); + BOOST_CHECK_NO_THROW(S_AFM_t(F::fc.at(S_name))); + BOOST_CHECK_NO_THROW(S4_Map_t(F::fc.at(S_name))); + BOOST_CHECK_THROW(S_MFMw1_t(F::fc.at(S_name)), FieldInterpretationError); + BOOST_CHECK_THROW(T4_Map_t(F::fc.at(S_name)), FieldInterpretationError); + BOOST_CHECK_THROW(S_MFMw2_t(F::fc.at(S_name)), FieldInterpretationError); + BOOST_CHECK_THROW(S_MFMw2_t(F::fc.at(S_name)), FieldInterpretationError); + BOOST_CHECK_THROW(S_MFMw3_t(F::fc.at(S_name)), FieldInterpretationError); + BOOST_CHECK_THROW(S_SFM_t(F::fc.at(S_name_w)), std::out_of_range); + + // possible maptypes for complex matrix fields + using M_type = Complex; + using M_MFM_t = MatrixFieldMap; + using M_AFM_t = ArrayFieldMap; + // impossible maptypes for complex matrix fields + using M_SFM_t = ScalarFieldMap; + using M_MFMw1_t = MatrixFieldMap; + using M_MFMw2_t = MatrixFieldMap; + using M_MFMw3_t = MatrixFieldMap; + const std::string M_name{"Matrixfield Complex sdim x mdim"}; + const std::string M_name_w{"Matrixfield Complex sdim x mdim wrongname"}; + + BOOST_CHECK_THROW(M_SFM_t(F::fc.at(M_name)), FieldInterpretationError); + BOOST_CHECK_NO_THROW(M_MFM_t(F::fc.at(M_name))); + BOOST_CHECK_NO_THROW(M_AFM_t(F::fc.at(M_name))); + BOOST_CHECK_THROW(M_MFMw1_t(F::fc.at(M_name)), FieldInterpretationError); + BOOST_CHECK_THROW(M_MFMw2_t(F::fc.at(M_name)), FieldInterpretationError); + BOOST_CHECK_THROW(M_MFMw2_t(F::fc.at(M_name)), FieldInterpretationError); + BOOST_CHECK_THROW(M_MFMw3_t(F::fc.at(M_name)), FieldInterpretationError); + BOOST_CHECK_THROW(M_SFM_t(F::fc.at(M_name_w)), std::out_of_range); + } + + /* ---------------------------------------------------------------------- */ + //! Check whether fields can be initialized + using mult_collections_t = boost::mpl::list, + FC_multi_fixture<2, 3, true>, + FC_multi_fixture<3, 3, true>>; + using mult_collections_f = boost::mpl::list, + FC_multi_fixture<2, 3, false>, + FC_multi_fixture<3, 3, false>>; + + BOOST_FIXTURE_TEST_CASE_TEMPLATE(init_test_glob, F, mult_collections_t, F) { + Ccoord_t size; + Ccoord_t loc{}; + for (auto && s : size) { + s = 3; + } + BOOST_CHECK_NO_THROW(F::fc.initialise(size, loc)); + } + + BOOST_FIXTURE_TEST_CASE_TEMPLATE(init_test_loca, F, mult_collections_f, F) { + testGoodies::RandRange rng; + for (int i = 0; i < 7; ++i) { + Ccoord_t pixel; + for (auto && s : pixel) { + s = rng.randval(0, 7); + } + F::fc.add_pixel(pixel); + } + + BOOST_CHECK_NO_THROW(F::fc.initialise()); + } + + BOOST_FIXTURE_TEST_CASE_TEMPLATE(init_test_loca_with_push_back, F, + mult_collections_f, F) { + constexpr auto mdim{F::mdim()}; + constexpr int nb_pix{7}; + testGoodies::RandRange rng{}; + using ftype = internal::TypedSizedFieldBase; + using stype = Eigen::Array; + auto & field = static_cast(F::fc["Tensorfield Real o4"]); + field.push_back(stype()); + for (int i = 0; i < nb_pix; ++i) { + Ccoord_t pixel; + for (auto && s : pixel) { + s = rng.randval(0, 7); + } + F::fc.add_pixel(pixel); + } + + BOOST_CHECK_THROW(F::fc.initialise(), FieldCollectionError); + for (int i = 0; i < nb_pix - 1; ++i) { + field.push_back(stype()); + } + BOOST_CHECK_NO_THROW(F::fc.initialise()); + } + + BOOST_AUTO_TEST_SUITE_END(); + +} // namespace muSpectre diff --git a/tests/test_field_collections_2.cc b/tests/test_field_collections_2.cc new file mode 100644 index 0000000..a5645c8 --- /dev/null +++ b/tests/test_field_collections_2.cc @@ -0,0 +1,308 @@ +/** + * @file test_field_collections_2.cc + * + * @author Till Junge + * + * @date 23 Nov 2017 + * + * @brief Continuation of tests from test_field_collection.cc, split for faster + * compilation + * + * Copyright © 2017 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with µSpectre; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Additional permission under GNU GPL version 3 section 7 + * + * If you modify this Program, or any covered work, by linking or combining it + * with proprietary FFT implementations or numerical libraries, containing parts + * covered by the terms of those libraries' licenses, the licensors of this + * Program grant you additional permission to convey the resulting work. + */ + +#include "test_field_collections.hh" +#include "common/field_map_dynamic.hh" + +namespace muSpectre { + BOOST_AUTO_TEST_SUITE(field_collection_tests); + + BOOST_FIXTURE_TEST_CASE_TEMPLATE(iter_field_test, F, iter_collections, F) { + using FC_t = typename F::Parent::FC_t; + using Tensor4Map = TensorFieldMap; + Tensor4Map T4map{F::fc["Tensorfield Real o4"]}; + TypedFieldMap dyn_map{F::fc["Tensorfield Real o4"]}; + F::fc["Tensorfield Real o4"].set_zero(); + + for (auto && tens : T4map) { + BOOST_CHECK_EQUAL(Real(Eigen::Tensor(tens.abs().sum().eval())()), + 0); + } + for (auto && tens : T4map) { + tens.setRandom(); + } + + for (auto && tup : akantu::zip(T4map, dyn_map)) { + auto & tens = std::get<0>(tup); + auto & dyn = std::get<1>(tup); + constexpr Dim_t nb_comp{ipow(F::mdim(), order)}; + Eigen::Map> tens_arr(tens.data()); + Real error{(dyn - tens_arr).matrix().norm()}; + BOOST_CHECK_EQUAL(error, 0); + } + + using Tensor2Map = + TensorFieldMap; + using MSqMap = + MatrixFieldMap; + using ASqMap = + ArrayFieldMap; + using A2Map = ArrayFieldMap; + using WrongMap = ArrayFieldMap; + Tensor2Map T2map{F::fc["Tensorfield Real o2"]}; + MSqMap Mmap{F::fc["Tensorfield Real o2"]}; + ASqMap Amap{F::fc["Tensorfield Real o2"]}; + A2Map DynMap{F::fc["Dynamically sized Field"]}; + auto & fc_ref{F::fc}; + BOOST_CHECK_THROW(WrongMap{fc_ref["Dynamically sized Field"]}, + FieldInterpretationError); + auto t2_it = T2map.begin(); + auto t2_it_end = T2map.end(); + auto m_it = Mmap.begin(); + auto a_it = Amap.begin(); + for (; t2_it != t2_it_end; ++t2_it, ++m_it, ++a_it) { + t2_it->setRandom(); + auto && m = *m_it; + bool comp = (m == a_it->matrix()); + BOOST_CHECK(comp); + } + + size_t counter{0}; + for (auto val : DynMap) { + ++counter; + val += val.Ones() * counter; + } + + counter = 0; + for (auto val : DynMap) { + ++counter; + val -= val.Ones() * counter; + auto error{val.matrix().norm()}; + BOOST_CHECK_LT(error, tol); + } + + using ScalarMap = ScalarFieldMap; + ScalarMap s_map{F::fc["integer Scalar"]}; + for (Uint i = 0; i < s_map.size(); ++i) { + s_map[i] = i; + } + + counter = 0; + for (const auto & val : s_map) { + BOOST_CHECK_EQUAL(counter++, val); + } + } + + BOOST_FIXTURE_TEST_CASE_TEMPLATE(ccoord_indexing_test, F, glob_iter_colls, + F) { + using FC_t = typename F::Parent::FC_t; + using ScalarMap = ScalarFieldMap; + ScalarMap s_map{F::fc["integer Scalar"]}; + for (Uint i = 0; i < s_map.size(); ++i) { + s_map[i] = i; + } + + for (size_t i = 0; i < CcoordOps::get_size(F::fc.get_sizes()); ++i) { + BOOST_CHECK_EQUAL( + CcoordOps::get_index(F::fc.get_sizes(), F::fc.get_locations(), + CcoordOps::get_ccoord(F::fc.get_sizes(), + F::fc.get_locations(), i)), + i); + } + } + + BOOST_FIXTURE_TEST_CASE_TEMPLATE(iterator_methods_test, F, iter_collections, + F) { + using FC_t = typename F::Parent::FC_t; + using Tensor4Map = TensorFieldMap; + Tensor4Map T4map{F::fc["Tensorfield Real o4"]}; + using it_t = typename Tensor4Map::iterator; + std::ptrdiff_t diff{ + 3}; // arbitrary, as long as it is smaller than the container size + + // check constructors + auto itstart = T4map.begin(); // standard way of obtaining iterator + auto itend = T4map.end(); // ditto + + it_t it1{T4map}; + it_t it2{T4map, false}; + it_t it3{T4map, size_t(diff)}; + BOOST_CHECK(itstart == itstart); + BOOST_CHECK(itstart != itend); + BOOST_CHECK_EQUAL(itstart, it1); + BOOST_CHECK_EQUAL(itend, it2); + + // check ostream operator + std::stringstream response; + response << it3; + BOOST_CHECK_EQUAL( + response.str(), + std::string("iterator on field 'Tensorfield Real o4', entry ") + + std::to_string(diff)); + + // check move and assigment constructor (and operator+) + it_t it_repl{T4map}; + it_t itmove = std::move(T4map.begin()); + it_t it4 = it1 + diff; + it_t it7 = it4 - diff; + + BOOST_CHECK_EQUAL(itmove, it1); + BOOST_CHECK_EQUAL(it4, it3); + BOOST_CHECK_EQUAL(it7, it1); + + // check increments/decrements + BOOST_CHECK_EQUAL(it1++, it_repl); // post-increment + BOOST_CHECK_EQUAL(it1, it_repl + 1); + BOOST_CHECK_EQUAL(--it1, it_repl); // pre -decrement + BOOST_CHECK_EQUAL(++it1, it_repl + 1); // pre -increment + BOOST_CHECK_EQUAL(it1--, it_repl + 1); // post-decrement + BOOST_CHECK_EQUAL(it1, it_repl); + + // dereference and member-of-pointer check + Eigen::Tensor Tens = *it1; + Eigen::Tensor Tens2 = *itstart; + Eigen::Tensor check = (Tens == Tens2).all(); + BOOST_CHECK_EQUAL(static_cast(check()), true); + + BOOST_CHECK_NO_THROW(itstart->setZero()); + + // check access subscripting + auto T3a = *it3; + auto T3b = itstart[diff]; + BOOST_CHECK( + static_cast(Eigen::Tensor((T3a == T3b).all())())); + + // div. comparisons + BOOST_CHECK_LT(itstart, itend); + BOOST_CHECK(!(itend < itstart)); + BOOST_CHECK(!(itstart < itstart)); + + BOOST_CHECK_LE(itstart, itend); + BOOST_CHECK_LE(itstart, itstart); + BOOST_CHECK(!(itend <= itstart)); + + BOOST_CHECK_GT(itend, itstart); + BOOST_CHECK(!(itend > itend)); + BOOST_CHECK(!(itstart > itend)); + + BOOST_CHECK_GE(itend, itstart); + BOOST_CHECK_GE(itend, itend); + BOOST_CHECK(!(itstart >= itend)); + + // check assignment increment/decrement + BOOST_CHECK_EQUAL(it1 += diff, it3); + BOOST_CHECK_EQUAL(it1 -= diff, itstart); + + // check cell coordinates + using Ccoord = Ccoord_t; + Ccoord a{itstart.get_ccoord()}; + Ccoord b{0}; + + // Weirdly, boost::has_left_shift::value is false for + // Ccoords, even though the operator is implemented :(, hence we can't write + // BOOST_CHECK_EQUAL(a, b); + // Workaround: + bool check2 = (a == b); + BOOST_CHECK(check2); + } + + BOOST_FIXTURE_TEST_CASE_TEMPLATE(const_tensor_iter_test, F, iter_collections, + F) { + using FC_t = typename F::Parent::FC_t; + using Tensor4Map = TensorFieldMap; + Tensor4Map T4map{F::fc["Tensorfield Real o4"]}; + + using T_t = typename Tensor4Map::T_t; + Eigen::TensorMap Tens2(T4map[0].data(), F::Parent::sdim(), + F::Parent::sdim(), F::Parent::sdim(), + F::Parent::sdim()); + + for (auto it = T4map.cbegin(); it != T4map.cend(); ++it) { + // maps to const tensors can't be initialised with a const pointer this + // sucks + auto && tens = *it; + auto && ptr = tens.data(); + + static_assert( + std::is_pointer>::value, + "should be getting a pointer"); + // static_assert(std::is_const>::value, + // "should be const"); + + // If Tensor were written well, above static_assert should pass, and the + // following check shouldn't. If it get triggered, it means that a newer + // version of Eigen now does have const-correct + // TensorMap. This means that const-correct field maps + // are then also possible for tensors + BOOST_CHECK(!std::is_const>::value); + } + } + + BOOST_FIXTURE_TEST_CASE_TEMPLATE(const_matrix_iter_test, F, iter_collections, + F) { + using FC_t = typename F::Parent::FC_t; + using MatrixMap = MatrixFieldMap; + MatrixMap Mmap{F::fc["Matrixfield Complex sdim x mdim"]}; + + for (auto it = Mmap.cbegin(); it != Mmap.cend(); ++it) { + // maps to const tensors can't be initialised with a const pointer this + // sucks + auto && mat = *it; + auto && ptr = mat.data(); + + static_assert( + std::is_pointer>::value, + "should be getting a pointer"); + // static_assert(std::is_const>::value, + // "should be const"); + + // If Matrix were written well, above static_assert should pass, and the + // following check shouldn't. If it get triggered, it means that a newer + // version of Eigen now does have const-correct + // MatrixMap. This means that const-correct field maps + // are then also possible for matrices + BOOST_CHECK(!std::is_const>::value); + } + } + + BOOST_FIXTURE_TEST_CASE_TEMPLATE(const_scalar_iter_test, F, iter_collections, + F) { + using FC_t = typename F::Parent::FC_t; + using ScalarMap = ScalarFieldMap; + ScalarMap Smap{F::fc["integer Scalar"]}; + + for (auto it = Smap.cbegin(); it != Smap.cend(); ++it) { + auto && scal = *it; + static_assert( + std::is_const>::value, + "referred type should be const"); + static_assert(std::is_lvalue_reference::value, + "Should have returned an lvalue ref"); + } + } + + BOOST_AUTO_TEST_SUITE_END(); + +} // namespace muSpectre diff --git a/tests/test_field_collections_3.cc b/tests/test_field_collections_3.cc new file mode 100644 index 0000000..0fd7e5d --- /dev/null +++ b/tests/test_field_collections_3.cc @@ -0,0 +1,163 @@ +/** + * @file test_field_collections_3.cc + * + * @author Till Junge + * + * @date 19 Dec 2017 + * + * @brief Continuation of tests from test_field_collection_2.cc, split for + * faster compilation + * + * Copyright © 2017 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with µSpectre; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Additional permission under GNU GPL version 3 section 7 + * + * If you modify this Program, or any covered work, by linking or combining it + * with proprietary FFT implementations or numerical libraries, containing parts + * covered by the terms of those libraries' licenses, the licensors of this + * Program grant you additional permission to convey the resulting work. + */ + +#include "test_field_collections.hh" +#include "tests/test_goodies.hh" +#include "common/tensor_algebra.hh" + +#include + +namespace muSpectre { + + BOOST_AUTO_TEST_SUITE(field_collection_tests); + + /* ---------------------------------------------------------------------- */ + BOOST_FIXTURE_TEST_CASE_TEMPLATE(assignment_test, Fix, iter_collections, + Fix) { + auto t4map = Fix::t4_field.get_map(); + auto t2map = Fix::t2_field.get_map(); + auto scmap = Fix::sc_field.get_map(); + auto m2map = Fix::m2_field.get_map(); + + const auto t4map_val{Matrices::Isymm()}; + t4map = t4map_val; + const auto t2map_val{Matrices::I2()}; + t2map = t2map_val; + const Int scmap_val{1}; + scmap = scmap_val; + Eigen::Matrix m2map_val; + m2map_val.setRandom(); + m2map = m2map_val; + const size_t nb_pts{Fix::fc.size()}; + + testGoodies::RandRange rnd{}; + BOOST_CHECK_EQUAL((t4map[rnd.randval(0, nb_pts - 1)] - t4map_val).norm(), + 0.); + BOOST_CHECK_EQUAL((t2map[rnd.randval(0, nb_pts - 1)] - t2map_val).norm(), + 0.); + BOOST_CHECK_EQUAL((scmap[rnd.randval(0, nb_pts - 1)] - scmap_val), 0.); + BOOST_CHECK_EQUAL((m2map[rnd.randval(0, nb_pts - 1)] - m2map_val).norm(), + 0.); + } + + /* ---------------------------------------------------------------------- */ + BOOST_FIXTURE_TEST_CASE_TEMPLATE(Eigentest, Fix, iter_collections, Fix) { + auto t4eigen = Fix::t4_field.eigen(); + auto t2eigen = Fix::t2_field.eigen(); + + BOOST_CHECK_EQUAL(t4eigen.rows(), ipow(Fix::mdim(), 4)); + BOOST_CHECK_EQUAL(t4eigen.cols(), Fix::t4_field.size()); + + using T2_t = typename Eigen::Matrix; + T2_t test_mat; + test_mat.setRandom(); + Eigen::Map> test_map( + test_mat.data()); + t2eigen.col(0) = test_map; + + BOOST_CHECK_EQUAL((Fix::t2_field.get_map()[0] - test_mat).norm(), 0.); + } + + /* ---------------------------------------------------------------------- */ + BOOST_FIXTURE_TEST_CASE_TEMPLATE(field_proxy_test, Fix, iter_collections, + Fix) { + Eigen::VectorXd t4values{Fix::t4_field.eigenvec()}; + + using FieldProxy_t = TypedField; + + //! create a field proxy + FieldProxy_t proxy("proxy to 'Tensorfield Real o4'", Fix::fc, t4values, + Fix::t4_field.get_nb_components()); + + Eigen::VectorXd wrong_size_not_multiple{ + Eigen::VectorXd::Zero(t4values.size() + 1)}; + BOOST_CHECK_THROW(FieldProxy_t("size not a multiple of nb_components", + Fix::fc, wrong_size_not_multiple, + Fix::t4_field.get_nb_components()), + FieldError); + + Eigen::VectorXd wrong_size_but_multiple{Eigen::VectorXd::Zero( + t4values.size() + Fix::t4_field.get_nb_components())}; + BOOST_CHECK_THROW(FieldProxy_t("size wrong multiple of nb_components", + Fix::fc, wrong_size_but_multiple, + Fix::t4_field.get_nb_components()), + FieldError); + + using Tensor4Map = + T4MatrixFieldMap; + Tensor4Map ref_map{Fix::t4_field}; + Tensor4Map proxy_map{proxy}; + + for (auto tup : akantu::zip(ref_map, proxy_map)) { + auto & ref = std::get<0>(tup); + auto & prox = std::get<1>(tup); + BOOST_CHECK_EQUAL((ref - prox).norm(), 0); + } + } + + /* ---------------------------------------------------------------------- */ + BOOST_FIXTURE_TEST_CASE_TEMPLATE(field_proxy_of_existing_field, Fix, + iter_collections, Fix) { + Eigen::Ref t4values{Fix::t4_field.eigenvec()}; + using FieldProxy_t = TypedField; + + //! create a field proxy + FieldProxy_t proxy("proxy to 'Tensorfield Real o4'", Fix::fc, t4values, + Fix::t4_field.get_nb_components()); + + using Tensor4Map = + T4MatrixFieldMap; + Tensor4Map ref_map{Fix::t4_field}; + Tensor4Map proxy_map{proxy}; + for (auto tup : akantu::zip(ref_map, proxy_map)) { + auto & ref = std::get<0>(tup); + auto & prox = std::get<1>(tup); + prox += prox.Identity(); + BOOST_CHECK_EQUAL((ref - prox).norm(), 0); + } + } + + /* ---------------------------------------------------------------------- */ + BOOST_FIXTURE_TEST_CASE_TEMPLATE(typed_field_getter, Fix, mult_collections, + Fix) { + constexpr auto mdim{Fix::mdim()}; + auto & fc{Fix::fc}; + auto & field = fc.template get_typed_field("Tensorfield Real o4"); + BOOST_CHECK_EQUAL(field.get_nb_components(), ipow(mdim, fourthOrder)); + } + + BOOST_AUTO_TEST_SUITE_END(); + +} // namespace muSpectre diff --git a/tests/test_geometry.cc b/tests/test_geometry.cc new file mode 100644 index 0000000..1beb9cd --- /dev/null +++ b/tests/test_geometry.cc @@ -0,0 +1,182 @@ +/** + * file test_geometry.cc + * + * @author Till Junge + * + * @date 19 Apr 2018 + * + * @brief Tests for tensor rotations + * + * @section LICENSE + * + * Copyright © 2018 Till Junge + * + * µSpectre is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3, or (at + * your option) any later version. + * + * µSpectre 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with µSpectre; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Additional permission under GNU GPL version 3 section 7 + * + * If you modify this Program, or any covered work, by linking or combining it + * with proprietary FFT implementations or numerical libraries, containing parts + * covered by the terms of those libraries' licenses, the licensors of this + * Program grant you additional permission to convey the resulting work. + */ + +#include "common/geometry.hh" +#include "tests.hh" +#include "test_goodies.hh" +#include "common/T4_map_proxy.hh" + +#include +#include + +#include + +namespace muSpectre { + + BOOST_AUTO_TEST_SUITE(geometry); + + /* ---------------------------------------------------------------------- */ + template + struct RotationFixture { + static constexpr Dim_t Dim{Dim_}; + using Vec_t = Eigen::Matrix; + using Mat_t = Eigen::Matrix; + using Ten_t = T4Mat; + using Angles_t = Eigen::Matrix; + using Rot_t = Rotator; + static constexpr RotationOrder EulerOrder{Rot}; + static constexpr Dim_t get_Dim() { return Dim_; } + RotationFixture() : rotator{euler} {} + + testGoodies::RandRange rr{}; + Angles_t euler{2 * pi * Angles_t::Random()}; + Vec_t v{Vec_t::Random()}; + Mat_t m{Mat_t::Random()}; + Ten_t t{Ten_t::Random()}; + Rot_t rotator; + }; + + /* ---------------------------------------------------------------------- */ + using fix_list = + boost::mpl::list, + RotationFixture>; + + /* ---------------------------------------------------------------------- */ + BOOST_FIXTURE_TEST_CASE_TEMPLATE(rotation_test, Fix, fix_list, Fix) { + using Vec_t = typename Fix::Vec_t; + using Mat_t = typename Fix::Mat_t; + using Ten_t = typename Fix::Ten_t; + + constexpr const Dim_t Dim{Fix::get_Dim()}; + + Vec_t & v{Fix::v}; + Mat_t & m{Fix::m}; + Ten_t & t{Fix::t}; + const Mat_t & R{Fix::rotator.get_rot_mat()}; + + Vec_t v_ref{R * v}; + Mat_t m_ref{R * m * R.transpose()}; + Ten_t t_ref{Ten_t::Zero()}; + for (int i = 0; i < Dim; ++i) { + for (int a = 0; a < Dim; ++a) { + for (int l = 0; l < Dim; ++l) { + for (int b = 0; b < Dim; ++b) { + for (int m = 0; m < Dim; ++m) { + for (int n = 0; n < Dim; ++n) { + for (int o = 0; o < Dim; ++o) { + for (int p = 0; p < Dim; ++p) { + get(t_ref, a, b, o, p) += R(a, i) * R(b, l) * + get(t, i, l, m, n) * R(o, m) * + R(p, n); + } + } + } + } + } + } + } + } + + Vec_t v_rotator(Fix::rotator.rotate(v)); + Mat_t m_rotator(Fix::rotator.rotate(m)); + Ten_t t_rotator(Fix::rotator.rotate(t)); + + auto v_error{(v_rotator - v_ref).norm() / v_ref.norm()}; + BOOST_CHECK_LT(v_error, tol); + + auto m_error{(m_rotator - m_ref).norm() / m_ref.norm()}; + BOOST_CHECK_LT(m_error, tol); + + auto t_error{(t_rotator - t_ref).norm() / t_ref.norm()}; + BOOST_CHECK_LT(t_error, tol); + if (t_error >= tol) { + std::cout << "t4_reference:" << std::endl << t_ref << std::endl; + std::cout << "t4_rotator:" << std::endl << t_rotator << std::endl; + } + + Vec_t v_back{Fix::rotator.rotate_back(v_rotator)}; + Mat_t m_back{Fix::rotator.rotate_back(m_rotator)}; + Ten_t t_back{Fix::rotator.rotate_back(t_rotator)}; + + v_error = (v_back - v).norm() / v.norm(); + BOOST_CHECK_LT(v_error, tol); + + m_error = (m_back - m).norm() / m.norm(); + BOOST_CHECK_LT(m_error, tol); + + t_error = (t_back - t).norm() / t.norm(); + BOOST_CHECK_LT(t_error, tol); + } + + /* ---------------------------------------------------------------------- */ + using threeD_list = + boost::mpl::list>; + + /* ---------------------------------------------------------------------- */ + BOOST_FIXTURE_TEST_CASE_TEMPLATE(rotation_matrix_test, Fix, threeD_list, + Fix) { + using Mat_t = typename Fix::Mat_t; + auto c{Eigen::cos(Fix::euler.array())}; + Real c_1{c[0]}, c_2{c[1]}, c_3{c[2]}; + auto s{Eigen::sin(Fix::euler.array())}; + Real s_1{s[0]}, s_2{s[1]}, s_3{s[2]}; + Mat_t rot_ref; + + switch (Fix::EulerOrder) { + case RotationOrder::ZXYTaitBryan: { + rot_ref << c_1 * c_3 - s_1 * s_2 * s_3, -c_2 * s_1, + c_1 * s_3 + c_3 * s_1 * s_2, c_3 * s_1 + c_1 * s_2 * s_3, c_1 * c_2, + s_1 * s_3 - c_1 * c_3 * s_2, -c_2 * s_3, s_2, c_2 * c_3; + + break; + } + default: { + BOOST_CHECK(false); + break; + } + } + auto err{(rot_ref - Fix::rotator.get_rot_mat()).norm()}; + BOOST_CHECK_LT(err, tol); + if (err >= tol) { + std::cout << "Reference:" << std::endl << rot_ref << std::endl; + std::cout << "Rotator:" << std::endl + << Fix::rotator.get_rot_mat() << std::endl; + } + } + + BOOST_AUTO_TEST_SUITE_END(); + +} // namespace muSpectre diff --git a/tests/test_goodies.hh b/tests/test_goodies.hh index 29815d8..9637e40 100644 --- a/tests/test_goodies.hh +++ b/tests/test_goodies.hh @@ -1,142 +1,228 @@ /** * @file test_goodies.hh * * @author Till Junge * * @date 27 Sep 2017 * * @brief helpers for testing * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #ifndef TESTS_TEST_GOODIES_HH_ #define TESTS_TEST_GOODIES_HH_ #include "common/tensor_algebra.hh" #include #include #include namespace muSpectre { namespace testGoodies { template struct dimFixture { constexpr static Dim_t dim{Dim}; }; using dimlist = boost::mpl::list, dimFixture, dimFixture>; /* ---------------------------------------------------------------------- */ template class RandRange { public: RandRange() : rd(), gen(rd()) {} template std::enable_if_t::value, dummyT> randval(T && lower, T && upper) { static_assert(std::is_same::value, "SFINAE"); auto distro = std::uniform_real_distribution(lower, upper); return distro(this->gen); } template std::enable_if_t::value, dummyT> randval(T && lower, T && upper) { static_assert(std::is_same::value, "SFINAE"); auto distro = std::uniform_int_distribution(lower, upper); return distro(this->gen); } private: std::random_device rd; std::default_random_engine gen; }; /** * explicit computation of linearisation of PK1 stress for an * objective Hooke's law. This implementation is not meant to be * efficient, but te reflect exactly the formulation in Curnier * 2000, "Méthodes numériques en mécanique des solides" for * reference and testing */ template decltype(auto) objective_hooke_explicit(Real lambda, Real mu, const Matrices::Tens2_t & F) { using Matrices::Tens2_t; using Matrices::Tens4_t; using Matrices::tensmult; using T2 = Tens2_t; using T4 = Tens4_t; T2 P; T2 I = P.Identity(); T4 K; // See Curnier, 2000, "Méthodes numériques en mécanique des // solides", p 252, (6.95b) Real Fjrjr = (F.array() * F.array()).sum(); T2 Fjrjm = F.transpose() * F; P.setZero(); for (Dim_t i = 0; i < Dim; ++i) { for (Dim_t m = 0; m < Dim; ++m) { P(i, m) += lambda / 2 * (Fjrjr - Dim) * F(i, m); for (Dim_t r = 0; r < Dim; ++r) { P(i, m) += mu * F(i, r) * (Fjrjm(r, m) - I(r, m)); } } } // See Curnier, 2000, "Méthodes numériques en mécanique des solides", p // 252 Real Fkrkr = (F.array() * F.array()).sum(); T2 Fkmkn = F.transpose() * F; T2 Fisjs = F * F.transpose(); K.setZero(); for (Dim_t i = 0; i < Dim; ++i) { for (Dim_t j = 0; j < Dim; ++j) { for (Dim_t m = 0; m < Dim; ++m) { for (Dim_t n = 0; n < Dim; ++n) { get(K, i, m, j, n) = (lambda * ((Fkrkr - Dim) / 2 * I(i, j) * I(m, n) + F(i, m) * F(j, n)) + mu * (I(i, j) * Fkmkn(m, n) + Fisjs(i, j) * I(m, n) - I(i, j) * I(m, n) + F(i, n) * F(j, m))); } } } } return std::make_tuple(P, K); } + /** + * takes a 4th-rank tensor and returns a copy with the last two + * dimensions switched. This is particularly useful to check for + * identity between a stiffness tensors computed the regular way + * and the Geers way. For testing, not efficient. + */ + template + inline auto right_transpose(const Eigen::MatrixBase & t4) + -> Eigen::Matrix { + constexpr Dim_t Rows{Derived::RowsAtCompileTime}; + constexpr Dim_t Cols{Derived::ColsAtCompileTime}; + constexpr Dim_t DimSq{Rows}; + using T = typename Derived::Scalar; + static_assert(Rows == Cols, "only square problems"); + constexpr Dim_t Dim{ct_sqrt(DimSq)}; + static_assert((Dim == twoD) or (Dim == threeD), + "only two- and three-dimensional problems"); + static_assert(ipow(Dim, 2) == DimSq, + "The array is not a valid fourth order tensor"); + T4Mat retval{T4Mat::Zero()}; + + /** + * Note: this looks like it's doing a left transpose, but in + * reality, np is rowmajor in all directions, so from numpy to + * eigen, we need to transpose left, center, and + * right. Therefore, if we want to right-transpose, then we need + * to transpose once left, once center, and *twice* right, which + * is equal to just left and center transpose. Center-transpose + * happens automatically when eigen parses the input (which is + * naturally written in rowmajor but interpreted into colmajor), + * so all that's left to do is (ironically) the subsequent + * left-transpose + */ + for (int i{0}; i < Dim; ++i) { + for (int j{0}; j < Dim; ++j) { + for (int k{0}; k < Dim; ++k) { + for (int l{0}; l < Dim; ++l) { + get(retval, i, j, k, l) = get(t4, j, i, k, l); + } + } + } + } + return retval; + } + + /** + * recomputes a colmajor representation of a fourth-rank rowmajor + * tensor. this is useful when comparing to reference results + * computed in numpy + */ + template + inline auto from_numpy(const Eigen::MatrixBase & t4_np) + -> Eigen::Matrix { + constexpr Dim_t Rows{Derived::RowsAtCompileTime}; + constexpr Dim_t Cols{Derived::ColsAtCompileTime}; + constexpr Dim_t DimSq{Rows}; + using T = typename Derived::Scalar; + static_assert(Rows == Cols, "only square problems"); + constexpr Dim_t Dim{ct_sqrt(DimSq)}; + static_assert((Dim == twoD) or (Dim == threeD), + "only two- and three-dimensional problems"); + static_assert(ipow(Dim, 2) == DimSq, + "The array is not a valid fourth order tensor"); + T4Mat retval{T4Mat::Zero()}; + T4Mat intermediate{T4Mat::Zero()}; + // transpose rows + for (int row{0}; row < DimSq; ++row) { + for (int i{0}; i < Dim; ++i) { + for (int j{0}; j < Dim; ++j) { + intermediate(row, i + Dim * j) = t4_np(row, i * Dim + j); + } + } + } + // transpose columns + for (int col{0}; col < DimSq; ++col) { + for (int i{0}; i < Dim; ++i) { + for (int j{0}; j < Dim; ++j) { + retval(i + Dim * j, col) = intermediate(i * Dim + j, col); + } + } + } + return retval; + } + } // namespace testGoodies } // namespace muSpectre #endif // TESTS_TEST_GOODIES_HH_ diff --git a/tests/test_material_hyper_elasto_plastic1.cc b/tests/test_material_hyper_elasto_plastic1.cc index cf11e4b..7c7b281 100644 --- a/tests/test_material_hyper_elasto_plastic1.cc +++ b/tests/test_material_hyper_elasto_plastic1.cc @@ -1,335 +1,425 @@ /** * @file test_material_hyper_elasto_plastic1.cc * * @author Till Junge * * @date 25 Feb 2018 * * @brief Tests for the large-strain Simo-type plastic law implemented * using MaterialMuSpectre * * Copyright © 2018 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #include "boost/mpl/list.hpp" +#include "materials/stress_transformations_Kirchhoff.hh" #include "materials/material_hyper_elasto_plastic1.hh" #include "materials/materials_toolbox.hh" #include "tests.hh" +#include "test_goodies.hh" namespace muSpectre { BOOST_AUTO_TEST_SUITE(material_hyper_elasto_plastic_1); template struct MaterialFixture { using Mat = Mat_t; constexpr static Real K{.833}; // bulk modulus constexpr static Real mu{.386}; // shear modulus constexpr static Real H{.004}; // hardening modulus constexpr static Real tau_y0{.003}; // initial yield stress constexpr static Real young{MatTB::convert_elastic_modulus< ElasticModulus::Young, ElasticModulus::Bulk, ElasticModulus::Shear>( K, mu)}; constexpr static Real poisson{MatTB::convert_elastic_modulus< ElasticModulus::Poisson, ElasticModulus::Bulk, ElasticModulus::Shear>( K, mu)}; MaterialFixture() : mat("Name", young, poisson, tau_y0, H) {} constexpr static Dim_t sdim{Mat_t::sdim()}; constexpr static Dim_t mdim{Mat_t::mdim()}; Mat_t mat; }; using mats = boost::mpl::list< MaterialFixture>, MaterialFixture>, MaterialFixture>>; BOOST_FIXTURE_TEST_CASE_TEMPLATE(test_constructor, Fix, mats, Fix) { BOOST_CHECK_EQUAL("Name", Fix::mat.get_name()); auto & mat{Fix::mat}; auto sdim{Fix::sdim}; auto mdim{Fix::mdim}; BOOST_CHECK_EQUAL(sdim, mat.sdim()); BOOST_CHECK_EQUAL(mdim, mat.mdim()); } BOOST_FIXTURE_TEST_CASE_TEMPLATE(test_evaluate_stress, Fix, mats, Fix) { // This test uses precomputed reference values (computed using // elasto-plasticity.py) for the 3d case only // need higher tol because of printout precision of reference solutions constexpr Real hi_tol{1e-8}; constexpr Dim_t mdim{Fix::mdim}, sdim{Fix::sdim}; constexpr bool has_precomputed_values{(mdim == sdim) && (mdim == threeD)}; constexpr bool verbose{false}; using Strain_t = Eigen::Matrix; using traits = MaterialMuSpectre_traits>; using LColl_t = typename traits::LFieldColl_t; using StrainStField_t = StateField>; using FlowStField_t = StateField>; // using StrainStRef_t = typename traits::LStrainMap_t::reference; // using ScalarStRef_t = typename traits::LScalarMap_t::reference; // create statefields LColl_t coll{}; coll.add_pixel({0}); coll.initialise(); auto & F_{make_statefield("previous gradient", coll)}; auto & be_{ make_statefield("previous elastic strain", coll)}; auto & eps_{make_statefield("plastic flow", coll)}; auto F_prev{F_.get_map()}; F_prev[0].current() = Strain_t::Identity(); auto be_prev{be_.get_map()}; be_prev[0].current() = Strain_t::Identity(); auto eps_prev{eps_.get_map()}; eps_prev[0].current() = 0; // elastic deformation Strain_t F{Strain_t::Identity()}; F(0, 1) = 1e-5; F_.cycle(); be_.cycle(); eps_.cycle(); Strain_t stress{ Fix::mat.evaluate_stress(F, F_prev[0], be_prev[0], eps_prev[0])}; if (has_precomputed_values) { Strain_t tau_ref{}; tau_ref << 1.92999522e-11, 3.86000000e-06, 0.00000000e+00, 3.86000000e-06, -1.93000510e-11, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, -2.95741950e-17; - Real error{(tau_ref - stress).norm()}; + Real error{(tau_ref - stress).norm() / tau_ref.norm()}; BOOST_CHECK_LT(error, hi_tol); Strain_t be_ref{}; be_ref << 1.00000000e+00, 1.00000000e-05, 0.00000000e+00, 1.00000000e-05, 1.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 1.00000000e+00; - error = (be_ref - be_prev[0].current()).norm(); + error = (be_ref - be_prev[0].current()).norm() / be_ref.norm(); BOOST_CHECK_LT(error, hi_tol); Real ep_ref{0}; error = ep_ref - eps_prev[0].current(); BOOST_CHECK_LT(error, hi_tol); } if (verbose) { std::cout << "τ =" << std::endl << stress << std::endl; std::cout << "F =" << std::endl << F << std::endl; std::cout << "Fₜ =" << std::endl << F_prev[0].current() << std::endl; std::cout << "bₑ =" << std::endl << be_prev[0].current() << std::endl; std::cout << "εₚ =" << std::endl << eps_prev[0].current() << std::endl; } F_.cycle(); be_.cycle(); eps_.cycle(); // plastic deformation F(0, 1) = .2; stress = Fix::mat.evaluate_stress(F, F_prev[0], be_prev[0], eps_prev[0]); if (has_precomputed_values) { Strain_t tau_ref{}; tau_ref << 1.98151335e-04, 1.98151335e-03, 0.00000000e+00, 1.98151335e-03, -1.98151335e-04, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 1.60615155e-16; - Real error{(tau_ref - stress).norm()}; + Real error{(tau_ref - stress).norm() / tau_ref.norm()}; BOOST_CHECK_LT(error, hi_tol); Strain_t be_ref{}; be_ref << 1.00052666, 0.00513348, 0., 0.00513348, 0.99949996, 0., 0., 0., 1.; - error = (be_ref - be_prev[0].current()).norm(); + error = (be_ref - be_prev[0].current()).norm() / be_ref.norm(); BOOST_CHECK_LT(error, hi_tol); Real ep_ref{0.11229988}; - error = ep_ref - eps_prev[0].current(); + error = (ep_ref - eps_prev[0].current()) / ep_ref; BOOST_CHECK_LT(error, hi_tol); } if (verbose) { std::cout << "Post Cycle" << std::endl; std::cout << "τ =" << std::endl << stress << std::endl << "F =" << std::endl << F << std::endl << "Fₜ =" << std::endl << F_prev[0].current() << std::endl << "bₑ =" << std::endl << be_prev[0].current() << std::endl << "εₚ =" << std::endl << eps_prev[0].current() << std::endl; } } BOOST_FIXTURE_TEST_CASE_TEMPLATE(test_evaluate_stiffness, Fix, mats, Fix) { // This test uses precomputed reference values (computed using // elasto-plasticity.py) for the 3d case only // need higher tol because of printout precision of reference solutions - constexpr Real hi_tol{1e-8}; + constexpr Real hi_tol{2e-7}; constexpr Dim_t mdim{Fix::mdim}, sdim{Fix::sdim}; constexpr bool has_precomputed_values{(mdim == sdim) && (mdim == threeD)}; constexpr bool verbose{has_precomputed_values && false}; using Strain_t = Eigen::Matrix; using Stiffness_t = T4Mat; using traits = MaterialMuSpectre_traits>; using LColl_t = typename traits::LFieldColl_t; using StrainStField_t = StateField>; using FlowStField_t = StateField>; // using StrainStRef_t = typename traits::LStrainMap_t::reference; // using ScalarStRef_t = typename traits::LScalarMap_t::reference; // create statefields LColl_t coll{}; coll.add_pixel({0}); coll.initialise(); auto & F_{make_statefield("previous gradient", coll)}; auto & be_{ make_statefield("previous elastic strain", coll)}; auto & eps_{make_statefield("plastic flow", coll)}; auto F_prev{F_.get_map()}; F_prev[0].current() = Strain_t::Identity(); auto be_prev{be_.get_map()}; be_prev[0].current() = Strain_t::Identity(); auto eps_prev{eps_.get_map()}; eps_prev[0].current() = 0; // elastic deformation Strain_t F{Strain_t::Identity()}; F(0, 1) = 1e-5; F_.cycle(); be_.cycle(); eps_.cycle(); Strain_t stress{}; Stiffness_t stiffness{}; + std::tie(stress, stiffness) = Fix::mat.evaluate_stress_tangent(F, F_prev[0], be_prev[0], eps_prev[0]); if (has_precomputed_values) { Strain_t tau_ref{}; tau_ref << 1.92999522e-11, 3.86000000e-06, 0.00000000e+00, 3.86000000e-06, -1.93000510e-11, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, -2.95741950e-17; - Real error{(tau_ref - stress).norm()}; + Real error{(tau_ref - stress).norm() / tau_ref.norm()}; BOOST_CHECK_LT(error, hi_tol); Strain_t be_ref{}; be_ref << 1.00000000e+00, 1.00000000e-05, 0.00000000e+00, 1.00000000e-05, 1.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 1.00000000e+00; - error = (be_ref - be_prev[0].current()).norm(); + error = (be_ref - be_prev[0].current()).norm() / be_ref.norm(); BOOST_CHECK_LT(error, hi_tol); Real ep_ref{0}; error = ep_ref - eps_prev[0].current(); BOOST_CHECK_LT(error, hi_tol); - Stiffness_t C4_ref{}; - C4_ref << 0.67383333, 0., 0., 0., 0.28783333, 0., 0., 0., 0.28783333, 0., - 0.193, 0., 0.193, 0., 0., 0., 0., 0., 0., 0., 0.193, 0., 0., 0., - 0.193, 0., 0., 0., 0.193, 0., 0.193, 0., 0., 0., 0., 0., 0.28783333, - 0., 0., 0., 0.67383333, 0., 0., 0., 0.28783333, 0., 0., 0., 0., 0., - 0.193, 0., 0.193, 0., 0., 0., 0.193, 0., 0., 0., 0.193, 0., 0., 0., - 0., 0., 0., 0., 0.193, 0., 0.193, 0., 0.28783333, 0., 0., 0., - 0.28783333, 0., 0., 0., 0.67383333; - error = (C4_ref - stiffness).norm(); + Stiffness_t temp; + temp << 1.34766667e+00, 3.86000000e-06, 0.00000000e+00, -3.86000000e-06, + 5.75666667e-01, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, + 5.75666667e-01, -3.61540123e-17, 3.86000000e-01, 0.00000000e+00, + 3.86000000e-01, 7.12911684e-17, 0.00000000e+00, 0.00000000e+00, + 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, + 3.86000000e-01, 0.00000000e+00, 0.00000000e+00, -1.93000000e-06, + 3.86000000e-01, 1.93000000e-06, 0.00000000e+00, -3.61540123e-17, + 3.86000000e-01, 0.00000000e+00, 3.86000000e-01, 7.12911684e-17, + 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, + 5.75666667e-01, -3.86000000e-06, 0.00000000e+00, 3.86000000e-06, + 1.34766667e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, + 5.75666667e-01, 0.00000000e+00, 0.00000000e+00, -1.93000000e-06, + 0.00000000e+00, 0.00000000e+00, 3.86000000e-01, 1.93000000e-06, + 3.86000000e-01, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, + 3.86000000e-01, 0.00000000e+00, 0.00000000e+00, -1.93000000e-06, + 3.86000000e-01, 1.93000000e-06, 0.00000000e+00, 0.00000000e+00, + 0.00000000e+00, -1.93000000e-06, 0.00000000e+00, 0.00000000e+00, + 3.86000000e-01, 1.93000000e-06, 3.86000000e-01, 0.00000000e+00, + 5.75666667e-01, 2.61999996e-17, 0.00000000e+00, 2.61999996e-17, + 5.75666667e-01, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, + 1.34766667e+00; + Stiffness_t K4b_ref{testGoodies::from_numpy(temp)}; + + error = (K4b_ref - stiffness).norm() / K4b_ref.norm(); BOOST_CHECK_LT(error, hi_tol); + if (not(error < hi_tol)) { + std::cout << "stiffness reference:\n" << K4b_ref << std::endl; + std::cout << "stiffness computed:\n" << stiffness << std::endl; + } } if (verbose) { std::cout << "C₄ =" << std::endl << stiffness << std::endl; } F_.cycle(); be_.cycle(); eps_.cycle(); // plastic deformation F(0, 1) = .2; std::tie(stress, stiffness) = Fix::mat.evaluate_stress_tangent(F, F_prev[0], be_prev[0], eps_prev[0]); if (has_precomputed_values) { Strain_t tau_ref{}; tau_ref << 1.98151335e-04, 1.98151335e-03, 0.00000000e+00, 1.98151335e-03, -1.98151335e-04, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 1.60615155e-16; - Real error{(tau_ref - stress).norm()}; + Real error{(tau_ref - stress).norm() / tau_ref.norm()}; BOOST_CHECK_LT(error, hi_tol); Strain_t be_ref{}; be_ref << 1.00052666, 0.00513348, 0., 0.00513348, 0.99949996, 0., 0., 0., 1.; - error = (be_ref - be_prev[0].current()).norm(); + error = (be_ref - be_prev[0].current()).norm() / be_ref.norm(); BOOST_CHECK_LT(error, hi_tol); Real ep_ref{0.11229988}; - error = ep_ref - eps_prev[0].current(); + error = (ep_ref - eps_prev[0].current()) / ep_ref; BOOST_CHECK_LT(error, hi_tol); - Stiffness_t C4_ref{}; - C4_ref << +4.23106224e-01, -4.27959704e-04, 0.00000000e+00, - -4.27959704e-04, 4.13218286e-01, 0.00000000e+00, 0.00000000e+00, - 0.00000000e+00, 4.13175490e-01, -4.27959704e-04, 7.07167743e-04, - 0.00000000e+00, 7.07167743e-04, 4.27959704e-04, 0.00000000e+00, - 0.00000000e+00, 0.00000000e+00, 2.79121029e-18, +0.00000000e+00, - 0.00000000e+00, 4.98676478e-03, 0.00000000e+00, 0.00000000e+00, - 0.00000000e+00, 4.98676478e-03, 0.00000000e+00, 0.00000000e+00, - -4.27959704e-04, 7.07167743e-04, 0.00000000e+00, 7.07167743e-04, - 4.27959704e-04, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, - 2.79121029e-18, +4.13218286e-01, 4.27959704e-04, 0.00000000e+00, - 4.27959704e-04, 4.23106224e-01, 0.00000000e+00, 0.00000000e+00, - 0.00000000e+00, 4.13175490e-01, +0.00000000e+00, 0.00000000e+00, - 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 4.98676478e-03, - 0.00000000e+00, 4.98676478e-03, 0.00000000e+00, +0.00000000e+00, - 0.00000000e+00, 4.98676478e-03, 0.00000000e+00, 0.00000000e+00, - 0.00000000e+00, 4.98676478e-03, 0.00000000e+00, 0.00000000e+00, - +0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, - 0.00000000e+00, 4.98676478e-03, 0.00000000e+00, 4.98676478e-03, - 0.00000000e+00, +4.13175490e-01, 2.79121029e-18, 0.00000000e+00, - 2.79121029e-18, 4.13175490e-01, 0.00000000e+00, 0.00000000e+00, - 0.00000000e+00, 4.23149020e-01; - error = (C4_ref - stiffness).norm(); + Stiffness_t temp{}; + temp << 8.46343327e-01, 1.11250597e-03, 0.00000000e+00, -2.85052074e-03, + 8.26305692e-01, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, + 8.26350980e-01, -8.69007382e-04, 1.21749295e-03, 0.00000000e+00, + 1.61379562e-03, 8.69007382e-04, 0.00000000e+00, 0.00000000e+00, + 0.00000000e+00, 5.58242059e-18, 0.00000000e+00, 0.00000000e+00, + 9.90756677e-03, 0.00000000e+00, 0.00000000e+00, -9.90756677e-04, + 1.01057181e-02, 9.90756677e-04, 0.00000000e+00, -8.69007382e-04, + 1.21749295e-03, 0.00000000e+00, 1.61379562e-03, 8.69007382e-04, + 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 5.58242059e-18, + 8.26305692e-01, -1.11250597e-03, 0.00000000e+00, 2.85052074e-03, + 8.46343327e-01, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, + 8.26350980e-01, 0.00000000e+00, 0.00000000e+00, -9.90756677e-04, + 0.00000000e+00, 0.00000000e+00, 1.01057181e-02, 9.90756677e-04, + 9.90756677e-03, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, + 9.90756677e-03, 0.00000000e+00, 0.00000000e+00, -9.90756677e-04, + 1.01057181e-02, 9.90756677e-04, 0.00000000e+00, 0.00000000e+00, + 0.00000000e+00, -9.90756677e-04, 0.00000000e+00, 0.00000000e+00, + 1.01057181e-02, 9.90756677e-04, 9.90756677e-03, 0.00000000e+00, + 8.26350980e-01, 0.00000000e+00, 0.00000000e+00, 1.38777878e-17, + 8.26350980e-01, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, + 8.46298039e-01; + + Stiffness_t K4b_ref{testGoodies::from_numpy(temp)}; + error = (K4b_ref - stiffness).norm() / K4b_ref.norm(); + + error = (K4b_ref - stiffness).norm() / K4b_ref.norm(); + BOOST_CHECK_LT(error, hi_tol); + if (not(error < hi_tol)) { + std::cout << "stiffness reference:\n" << K4b_ref << std::endl; + std::cout << "stiffness computed:\n" << stiffness << std::endl; + } + + // check also whether pull_back is correct + + Stiffness_t intermediate{stiffness}; + Stiffness_t zero_mediate{Stiffness_t::Zero()}; + for (int i{0}; i < mdim; ++i) { + for (int j{0}; j < mdim; ++j) { + for (int m{0}; m < mdim; ++m) { + const auto & k{i}; + const auto & l{j}; + // k,m inverted for right transpose + get(zero_mediate, i, j, k, m) -= stress(l, m); + get(intermediate, i, j, k, m) -= stress(l, m); + } + } + } + + temp << 8.46145176e-01, -8.69007382e-04, 0.00000000e+00, -2.85052074e-03, + 8.26305692e-01, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, + 8.26350980e-01, -2.85052074e-03, 1.41564428e-03, 0.00000000e+00, + 1.61379562e-03, 8.69007382e-04, 0.00000000e+00, 0.00000000e+00, + 0.00000000e+00, 5.58242059e-18, 0.00000000e+00, 0.00000000e+00, + 9.90756677e-03, 0.00000000e+00, 0.00000000e+00, -9.90756677e-04, + 1.01057181e-02, 9.90756677e-04, 0.00000000e+00, -8.69007382e-04, + 1.21749295e-03, 0.00000000e+00, 1.41564428e-03, -1.11250597e-03, + 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 5.58242059e-18, + 8.26305692e-01, -1.11250597e-03, 0.00000000e+00, 8.69007382e-04, + 8.46541479e-01, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, + 8.26350980e-01, 0.00000000e+00, 0.00000000e+00, -9.90756677e-04, + 0.00000000e+00, 0.00000000e+00, 1.01057181e-02, 9.90756677e-04, + 9.90756677e-03, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, + 9.90756677e-03, 0.00000000e+00, 0.00000000e+00, -9.90756677e-04, + 9.90756677e-03, -9.90756677e-04, 0.00000000e+00, 0.00000000e+00, + 0.00000000e+00, -9.90756677e-04, 0.00000000e+00, 0.00000000e+00, + 1.01057181e-02, -9.90756677e-04, 1.01057181e-02, 0.00000000e+00, + 8.26350980e-01, 0.00000000e+00, 0.00000000e+00, 1.38777878e-17, + 8.26350980e-01, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, + 8.46298039e-01; + + Stiffness_t K4c_ref{testGoodies::from_numpy(temp)}; + error = (K4b_ref + zero_mediate - K4c_ref).norm() / zero_mediate.norm(); + BOOST_CHECK_LT(error, hi_tol); // rel error on small difference between + // inexacly read doubles + if (not(error < hi_tol)) { + std::cout << "decrement reference:\n" << K4c_ref - K4b_ref << std::endl; + std::cout << "zero_mediate computed:\n" << zero_mediate << std::endl; + } + + error = (K4c_ref - intermediate).norm() / K4c_ref.norm(); BOOST_CHECK_LT(error, hi_tol); + if (not(error < hi_tol)) { + std::cout << "stiffness reference:\n" << K4c_ref << std::endl; + std::cout << "stiffness computed:\n" << intermediate << std::endl; + std::cout << "zero-mediate computed:\n" << zero_mediate << std::endl; + std::cout << "difference:\n" << K4c_ref - intermediate << std::endl; + } } if (verbose) { std::cout << "Post Cycle" << std::endl; std::cout << "C₄ =" << std::endl << stiffness << std::endl; } } + /* ---------------------------------------------------------------------- */ + BOOST_FIXTURE_TEST_CASE_TEMPLATE(stress_strain_test, Fix, mats, Fix) {} + BOOST_AUTO_TEST_SUITE_END(); } // namespace muSpectre diff --git a/tests/test_materials_toolbox.cc b/tests/test_materials_toolbox.cc index 9c030a8..97885a8 100644 --- a/tests/test_materials_toolbox.cc +++ b/tests/test_materials_toolbox.cc @@ -1,279 +1,371 @@ /** * @file test_materials_toolbox.cc * * @author Till Junge * * @date 05 Nov 2017 * * @brief Tests for the materials toolbox * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #include #include "tests.hh" #include "materials/materials_toolbox.hh" +#include "materials/stress_transformations_default_case.hh" +#include "materials/stress_transformations_PK1_impl.hh" +#include "materials/stress_transformations_PK2_impl.hh" +#include "materials/stress_transformations.hh" #include "common/T4_map_proxy.hh" #include "common/tensor_algebra.hh" #include "tests/test_goodies.hh" +#include + namespace muSpectre { BOOST_AUTO_TEST_SUITE(materials_toolbox) BOOST_FIXTURE_TEST_CASE_TEMPLATE(test_strain_conversion, Fix, testGoodies::dimlist, Fix) { constexpr Dim_t dim{Fix::dim}; using T2 = Eigen::Matrix; T2 F{(T2::Random() - .5 * T2::Ones()) / 20 + T2::Identity()}; // checking Green-Lagrange T2 Eref = .5 * (F.transpose() * F - T2::Identity()); T2 E_tb = MatTB::convert_strain( Eigen::Map>(F.data())); Real error = (Eref - E_tb).norm(); BOOST_CHECK_LT(error, tol); // checking Left Cauchy-Green Eref = F * F.transpose(); E_tb = MatTB::convert_strain(F); error = (Eref - E_tb).norm(); BOOST_CHECK_LT(error, tol); // checking Right Cauchy-Green Eref = F.transpose() * F; E_tb = MatTB::convert_strain(F); error = (Eref - E_tb).norm(); BOOST_CHECK_LT(error, tol); // checking Hencky (logarithmic) strain Eref = F.transpose() * F; Eigen::SelfAdjointEigenSolver EigSolv(Eref); Eref.setZero(); for (size_t i{0}; i < dim; ++i) { auto && vec = EigSolv.eigenvectors().col(i); auto && val = EigSolv.eigenvalues()(i); Eref += .5 * std::log(val) * vec * vec.transpose(); } E_tb = MatTB::convert_strain(F); error = (Eref - E_tb).norm(); BOOST_CHECK_LT(error, tol); auto F_tb = MatTB::convert_strain( F); error = (F - F_tb).norm(); BOOST_CHECK_LT(error, tol); } BOOST_FIXTURE_TEST_CASE_TEMPLATE(dumb_tensor_mult_test, Fix, testGoodies::dimlist, Fix) { constexpr Dim_t dim{Fix::dim}; using T4 = T4Mat; T4 A, B, R1, R2; A.setRandom(); B.setRandom(); R1 = A * B; R2.setZero(); for (Dim_t i = 0; i < dim; ++i) { for (Dim_t j = 0; j < dim; ++j) { for (Dim_t a = 0; a < dim; ++a) { for (Dim_t b = 0; b < dim; ++b) { for (Dim_t k = 0; k < dim; ++k) { for (Dim_t l = 0; l < dim; ++l) { get(R2, i, j, k, l) += get(A, i, j, a, b) * get(B, a, b, k, l); } } } } } } auto error{(R1 - R2).norm()}; BOOST_CHECK_LT(error, tol); } BOOST_FIXTURE_TEST_CASE_TEMPLATE(test_PK1_stress, Fix, testGoodies::dimlist, Fix) { using Matrices::Tens2_t; using Matrices::Tens4_t; using Matrices::tensmult; constexpr Dim_t dim{Fix::dim}; using T2 = Eigen::Matrix; using T4 = T4Mat; testGoodies::RandRange rng; T2 F = T2::Identity() * 2; // F.setRandom(); T2 E_tb = MatTB::convert_strain( Eigen::Map>(F.data())); Real lambda = 3; // rng.randval(1, 2); Real mu = 4; // rng.randval(1,2); T4 J = Matrices::Itrac(); T2 I = Matrices::I2(); T4 I4 = Matrices::Isymm(); T4 C = lambda * J + 2 * mu * I4; T2 S = Matrices::tensmult(C, E_tb); T2 Sref = lambda * E_tb.trace() * I + 2 * mu * E_tb; auto error{(Sref - S).norm()}; BOOST_CHECK_LT(error, tol); T4 K = Matrices::outer_under(I, S) + Matrices::outer_under(F, I) * C * Matrices::outer_under(F.transpose(), I); // See Curnier, 2000, "Méthodes numériques en mécanique des solides", p 252 T4 Kref; Real Fkrkr = (F.array() * F.array()).sum(); T2 Fkmkn = F.transpose() * F; T2 Fisjs = F * F.transpose(); Kref.setZero(); for (Dim_t i = 0; i < dim; ++i) { for (Dim_t j = 0; j < dim; ++j) { for (Dim_t m = 0; m < dim; ++m) { for (Dim_t n = 0; n < dim; ++n) { get(Kref, i, m, j, n) = (lambda * ((Fkrkr - dim) / 2 * I(i, j) * I(m, n) + F(i, m) * F(j, n)) + mu * (I(i, j) * Fkmkn(m, n) + Fisjs(i, j) * I(m, n) - I(i, j) * I(m, n) + F(i, n) * F(j, m))); } } } } error = (Kref - K).norm(); BOOST_CHECK_LT(error, tol); T2 P = MatTB::PK1_stress( F, S); T2 Pref = F * S; error = (P - Pref).norm(); BOOST_CHECK_LT(error, tol); auto && stress_tgt = MatTB::PK1_stress( F, S, C); T2 P_t = std::move(std::get<0>(stress_tgt)); T4 K_t = std::move(std::get<1>(stress_tgt)); error = (P_t - Pref).norm(); BOOST_CHECK_LT(error, tol); error = (K_t - Kref).norm(); BOOST_CHECK_LT(error, tol); auto && stress_tgt_trivial = MatTB::PK1_stress(F, P, K); T2 P_u = std::move(std::get<0>(stress_tgt_trivial)); T4 K_u = std::move(std::get<1>(stress_tgt_trivial)); error = (P_u - Pref).norm(); BOOST_CHECK_LT(error, tol); error = (K_u - Kref).norm(); BOOST_CHECK_LT(error, tol); T2 P_g; T4 K_g; std::tie(P_g, K_g) = testGoodies::objective_hooke_explicit(lambda, mu, F); error = (P_g - Pref).norm(); BOOST_CHECK_LT(error, tol); error = (K_g - Kref).norm(); BOOST_CHECK_LT(error, tol); } BOOST_AUTO_TEST_CASE(elastic_modulus_conversions) { // define original input constexpr Real E{123.456}; constexpr Real nu{.3}; using MatTB::convert_elastic_modulus; // derived values constexpr Real K{ convert_elastic_modulus(E, nu)}; constexpr Real lambda{ convert_elastic_modulus(E, nu)}; constexpr Real mu{ convert_elastic_modulus(E, nu)}; // recover original inputs Real comp = convert_elastic_modulus(K, mu); Real err = E - comp; BOOST_CHECK_LT(err, tol); comp = convert_elastic_modulus(K, mu); err = nu - comp; BOOST_CHECK_LT(err, tol); comp = convert_elastic_modulus(lambda, mu); err = E - comp; BOOST_CHECK_LT(err, tol); // check inversion resistance Real compA = convert_elastic_modulus(K, mu); Real compB = convert_elastic_modulus(mu, K); BOOST_CHECK_EQUAL(compA, compB); // check trivial self-returning comp = convert_elastic_modulus(K, mu); BOOST_CHECK_EQUAL(K, comp); comp = convert_elastic_modulus(K, mu); BOOST_CHECK_EQUAL(mu, comp); + + // check alternative calculation of computed values + + comp = convert_elastic_modulus( + K, mu); // alternative for "Shear" + BOOST_CHECK_LE(std::abs((comp - lambda) / lambda), tol); + } + + template + struct FiniteDifferencesHolder { + constexpr static FiniteDiff value{FinDiff}; + }; + + using FinDiffList = + boost::mpl::list, + FiniteDifferencesHolder, + FiniteDifferencesHolder>; + + /* ---------------------------------------------------------------------- */ + BOOST_FIXTURE_TEST_CASE_TEMPLATE(numerical_tangent_test, Fix, FinDiffList, + Fix) { + constexpr Dim_t Dim{twoD}; + using T4_t = T4Mat; + using T2_t = Eigen::Matrix; + + bool verbose{false}; + + T4_t Q{}; + Q << 1., 2., 0., 0., 0., 1.66666667, 0., 0., 0., 0., 2.33333333, 0., 0., 0., + 0., 3.; + if (verbose) { + std::cout << Q << std::endl << std::endl; + } + + T2_t B{}; + B << 2., 3.33333333, 2.66666667, 4.; + if (verbose) { + std::cout << B << std::endl << std::endl; + } + + auto fun = [&](const T2_t & x) -> T2_t { + using cmap_t = Eigen::Map>; + using map_t = Eigen::Map>; + cmap_t x_vec{x.data()}; + T2_t ret_val{}; + map_t(ret_val.data()) = Q * x_vec + cmap_t(B.data()); + return ret_val; + }; + + T2_t temp_res = fun(T2_t::Ones()); + if (verbose) { + std::cout << temp_res << std::endl << std::endl; + } + + T4_t numerical_tangent{MatTB::compute_numerical_tangent( + fun, T2_t::Ones(), 1e-2)}; + + if (verbose) { + std::cout << numerical_tangent << std::endl << std::endl; + } + + Real error{(numerical_tangent - Q).norm() / Q.norm()}; + + BOOST_CHECK_LT(error, tol); + if (not(error < tol)) { + switch (Fix::value) { + case FiniteDiff::backward: { + std::cout << "backward difference: " << std::endl; + break; + } + case FiniteDiff::forward: { + std::cout << "forward difference: " << std::endl; + break; + } + case FiniteDiff::centred: { + std::cout << "centered difference: " << std::endl; + break; + } + } + + std::cout << "error = " << error << std::endl; + std::cout << "numerical tangent:\n" << numerical_tangent << std::endl; + std::cout << "reference:\n" << Q << std::endl; + } } BOOST_AUTO_TEST_SUITE_END(); } // namespace muSpectre diff --git a/tests/tests.hh b/tests/tests.hh index 5b5a81a..4cb70de 100644 --- a/tests/tests.hh +++ b/tests/tests.hh @@ -1,48 +1,49 @@ /** * @file tests.hh * * @author Till Junge * * @date 10 May 2017 * * @brief common defs for tests * * Copyright © 2017 Till Junge * * µSpectre is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3, or (at * your option) any later version. * * µSpectre 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 * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with µSpectre; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * * Boston, MA 02111-1307, USA. * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining it * with proprietary FFT implementations or numerical libraries, containing parts * covered by the terms of those libraries' licenses, the licensors of this * Program grant you additional permission to convey the resulting work. */ #include "common/common.hh" #include #include #ifndef TESTS_TESTS_HH_ #define TESTS_TESTS_HH_ namespace muSpectre { - constexpr Real tol = 1e-14 * 100; // it's in percent + constexpr Real tol = 1e-14 * 100; // it's in percent + constexpr Real finite_diff_tol = 1e-7; // it's in percent } // namespace muSpectre #endif // TESTS_TESTS_HH_