diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml index cadacd55cf..f1175e899a 100644 --- a/.github/workflows/build_docs.yml +++ b/.github/workflows/build_docs.yml @@ -6,6 +6,11 @@ on: branches: [main] pull_request: branches: [main] + types: + - opened + - reopened + - synchronize + - closed # This is important for the ghpage preview to clean up after itself # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -24,26 +29,39 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.8" + - run: sudo apt install graphviz - run: pip install -r requirements.txt + - name: Install PyTorch + run: pip install torch==2.2.2 - run: | cmake \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_UNITY_BUILD=OFF \ - -DNEML2_UNIT=ON \ - -DNEML2_REGRESSION=OFF \ - -DNEML2_VERIFICATION=OFF \ - -DNEML2_BENCHMARK=OFF \ - -DNEML2_PROFILING=OFF \ - -DNEML2_PYBIND=OFF \ + -DCMAKE_UNITY_BUILD=ON \ + -DNEML2_TESTS=OFF \ + -DNEML2_RUNNER=OFF \ + -DNEML2_PYBIND=ON \ -DNEML2_DOC=ON \ . - - run: make doc-syntax -j 2 - - run: make doc-html - - run: cat doc/doxygen.log + - run: cmake --build . --target html -j 2 -- + - run: cat doc/doxygen.html.log + - run: cat doc/doxygen.python.log + - name: Preview GitHub Pages + if: ${{ github.event_name == 'pull_request' }} + uses: rossjrw/pr-preview-action@v1 + with: + source-dir: doc/build/html - name: Deploy to GitHub Pages if: ${{ github.event_name == 'push' }} uses: JamesIves/github-pages-deploy-action@v4.4.1 with: branch: gh-pages folder: doc/build/html - single-commit: true + clean-exclude: pr-preview/ + force: false + - name: Scold users about missing docs + if: ${{ github.event_name == 'pull_request'}} + uses: thollander/actions-comment-pull-request@v2 + with: + filePath: doc/syntax_error.log + comment_tag: doc_scold + GITHUB_TOKEN: ${{ secrets.PR_ACTION }} diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index cea81d2ba4..e681d70ecb 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -30,7 +30,7 @@ jobs: - uses: psf/black@stable with: options: "--check -v" - src: "python/neml2 tests/python" + src: "python/neml2 python/tests" build-test: name: Build and test Python bindings needs: black @@ -53,32 +53,19 @@ jobs: run: pip install -r requirements.txt - name: Install PyTorch run: pip install torch==2.2.2 - - run: | - cmake \ - -DCMAKE_BUILD_TYPE=Debug \ - -DCMAKE_UNITY_BUILD=ON \ - -DNEML2_UNIT=OFF \ - -DNEML2_REGRESSION=OFF \ - -DNEML2_VERIFICATION=OFF \ - -DNEML2_BENCHMARK=OFF \ - -DNEML2_PROFILING=OFF \ - -DNEML2_PYBIND=ON \ - -DNEML2_DOC=OFF \ - -B build \ - . - - run: cd build && make -j 2 - - run: PYTHONPATH=build/python pytest --junitxml=build/python/pytest.xml tests + - run: pip install -v . + - run: pytest --junitxml=pytest.xml python/tests - name: Publish Test Results uses: EnricoMi/publish-unit-test-result-action@v2 if: matrix.os == 'ubuntu-latest' with: - files: build/python/pytest.xml + files: pytest.xml check_name: Python Binding Test Results (${{ matrix.os }}) - name: Publish Test Results uses: EnricoMi/publish-unit-test-result-action/composite@v2 if: matrix.os == 'macos-12' with: - files: build/python/pytest.xml + files: pytest.xml check_name: Python Binding Test Results (${{ matrix.os }}) sdist: name: Build source distribution @@ -93,45 +80,8 @@ jobs: with: name: package-sdist path: dist/*.tar.gz - wheels: - needs: build-test - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-12] - runs-on: ${{ matrix.os }} - steps: - - name: Backup docker - if: matrix.os == 'ubuntu-latest' - run: sudo mv /var/lib/docker ${GITHUB_WORKSPACE}/docker - - name: Maximize build space - if: matrix.os == 'ubuntu-latest' - uses: easimon/maximize-build-space@master - with: - remove-dotnet: "true" - remove-android: "true" - remove-haskell: "true" - remove-codeql: "true" - build-mount-path: "/var/lib/docker/" - - name: Remount docker - if: matrix.os == 'ubuntu-latest' - run: sudo sh -c "mv ${GITHUB_WORKSPACE}/docker/* /var/lib/docker" - - uses: actions/checkout@v4 - with: - submodules: recursive - - uses: jwlawson/actions-setup-cmake@v2 - with: - cmake-version: "3.23" - - name: Build wheels - uses: pypa/cibuildwheel@v2.16.5 - - name: Upload wheels - if: github.event_name == 'release' && github.event.action == 'published' - uses: actions/upload-artifact@v4 - with: - name: package-wheels-${{ matrix.os }}-${{ strategy.job-index }} - path: ./wheelhouse/*.whl PyPI: - needs: [sdist, wheels] + needs: sdist environment: pypi permissions: id-token: write @@ -140,7 +90,7 @@ jobs: steps: - uses: actions/download-artifact@v4 with: - pattern: package-* + pattern: package-sdist path: dist merge-multiple: true - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0a40130567..f2932d0e67 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -68,20 +68,17 @@ jobs: cmake \ -DCMAKE_BUILD_TYPE=${{ matrix.btype }} \ -DCMAKE_UNITY_BUILD=${{ matrix.unity }} \ - -DNEML2_UNIT=ON \ - -DNEML2_REGRESSION=ON \ - -DNEML2_VERIFICATION=ON \ - -DNEML2_BENCHMARK=OFF \ - -DNEML2_PROFILING=OFF \ + -DNEML2_TESTS=ON \ + -DNEML2_RUNNER=OFF \ -DNEML2_PYBIND=OFF \ -DNEML2_DOC=OFF \ . - run: make -j 2 - - run: cd tests && ./unit_tests -r junit > unit_tests.xml + - run: cd tests && ./unit/unit_tests -r junit > unit_tests.xml continue-on-error: true - - run: cd tests && ./regression_tests -r junit > regression_tests.xml + - run: cd tests && ./regression/regression_tests -r junit > regression_tests.xml continue-on-error: true - - run: cd tests && ./verification_tests -r junit > verification_tests.xml + - run: cd tests && ./verification/verification_tests -r junit > verification_tests.xml continue-on-error: true - name: Publish Test Results uses: EnricoMi/publish-unit-test-result-action@v2 @@ -117,11 +114,9 @@ jobs: cmake \ -DCMAKE_BUILD_TYPE=${{ matrix.btype }} \ -DCMAKE_UNITY_BUILD=${{ matrix.unity }} \ - -DNEML2_UNIT=ON \ - -DNEML2_REGRESSION=ON \ - -DNEML2_VERIFICATION=ON \ - -DNEML2_BENCHMARK=ON \ - -DNEML2_PROFILING=ON \ + -DNEML2_TESTS=ON \ + -DNEML2_RUNNER=ON \ + -DNEML2_RUNNER_AS_PROFILER=ON \ -DNEML2_PYBIND=OFF \ -DNEML2_DOC=OFF \ . @@ -147,11 +142,9 @@ jobs: cmake \ -DCMAKE_BUILD_TYPE=Coverage \ -DCMAKE_UNITY_BUILD=OFF \ - -DNEML2_UNIT=ON \ - -DNEML2_REGRESSION=ON \ - -DNEML2_VERIFICATION=ON \ - -DNEML2_BENCHMARK=OFF \ - -DNEML2_PROFILING=OFF \ + -DNEML2_TESTS=ON \ + -DNEML2_RUNNER=OFF \ + -DNEML2_PYBIND=OFF \ -DNEML2_DOC=OFF \ . - run: make -j 2 @@ -182,7 +175,6 @@ jobs: echo -e "\ #include \"neml2/base/Registry.h\"\n\ int main() {\n\ - neml2::Registry::print(std::cout);\n\ return 0;\n\ }\ " > main.cxx @@ -190,15 +182,16 @@ jobs: - name: Create a CMakeLists.txt file for testing purposes run: | echo -e "\ - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.23) project(FOO)\n\ add_subdirectory(neml2)\n\ add_executable(foo main.cxx)\n\ + add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0)\n\ target_link_libraries(foo neml2)\n\ " > CMakeLists.txt - run: cat CMakeLists.txt - name: Configure with CMake - run: cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DNEML2_DOC=OFF -B build . + run: cmake -DNEML2_TESTS=OFF -B build . - name: Compile run: cd build && make -j 2 - run: cd build && ./foo diff --git a/.gitignore b/.gitignore index d2129948ed..1110c183a4 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ neml2.egg-info src/neml2/base/config.h install conftest.py +.env diff --git a/.gitmodules b/.gitmodules index 614a0dc2d8..e69de29bb2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,13 +0,0 @@ -[submodule "extern/Catch2"] - path = extern/Catch2 - url = https://github.com/catchorg/Catch2.git - ignore = dirty -[submodule "extern/doxygen-awesome-css"] - path = extern/doxygen-awesome-css - url = https://github.com/jothepro/doxygen-awesome-css.git -[submodule "extern/hit/hit"] - path = extern/hit/hit - url = https://github.com/idaholab/hit.git -[submodule "extern/gperftools"] - path = extern/gperftools - url = https://github.com/gperftools/gperftools.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c35109e54..2ecc7336f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,13 +1,12 @@ +# ---------------------------------------------------------------------------- +# Project metadata +# ---------------------------------------------------------------------------- cmake_minimum_required(VERSION 3.23) - project(NEML2 VERSION 1.4.0 LANGUAGES CXX) -# Setup modules -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${NEML2_SOURCE_DIR}/cmake/Modules/") - -# Ninja only: limit number of jobs in a CI build -set_property(GLOBAL PROPERTY JOB_POOLS CI=6) - +# ---------------------------------------------------------------------------- +# Policy +# ---------------------------------------------------------------------------- # FindPython should return the first matching Python if(POLICY CMP0094) cmake_policy(SET CMP0094 NEW) @@ -23,72 +22,80 @@ if(POLICY CMP0148) cmake_policy(SET CMP0148 NEW) endif() -# Accept Release, Debug, and RelWithDebInfo, add Coverage build types -set(CMAKE_CXX_FLAGS_COVERAGE - "-O0 -fprofile-arcs -ftest-coverage" - CACHE STRING "Flags used by C++ compiler during coverage builds." - FORCE) - -# Set the default build type -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE "Debug" CACHE - STRING "Choose the type of build." FORCE) - - # Set the possible values of build type for cmake-gui - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS - "Debug" "Release" "MinSizeRel" "RelWithDebInfo" "Coverage") -endif() - -# Add the unity option to the cache -option(CMAKE_UNITY_BUILD "Use a unity build" OFF) - -# c++ 17 support +# ---------------------------------------------------------------------------- +# Project-level settings, options, and flags +# ---------------------------------------------------------------------------- +list(APPEND CMAKE_MODULE_PATH ${NEML2_SOURCE_DIR}/cmake/Modules) # CMake modules and macros +set(CMAKE_CXX_FLAGS_COVERAGE "-O0 -fprofile-arcs -ftest-coverage" CACHE STRING "Flags used by C++ compiler during coverage builds." FORCE) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +option(NEML2_TESTS "Build NEML2 tests" ON) +option(NEML2_RUNNER "Build a simple runner for benchmarking, profiling, debugging, etc." OFF) +option(NEML2_PYBIND "Build NEML2 Python bindings" OFF) +option(NEML2_DOC "Build NEML2 documentation (html)" OFF) + +# ---------------------------------------------------------------------------- +# Dependencies and 3rd party packages +# ---------------------------------------------------------------------------- +set(PYTORCH_VERSION "2.2.2") +set(DOXYGEN_VERSION "1.10.0") +set(DOXYGEN_AWESOME_VERSION "2.3.2") +set(PYBIND11_VERSION "2.12.0") +set(HIT_VERSION "100b575af08643b5e646cac8faff6c87dd1c15a7") +set(CATCH2_VERSION "3.5.4") +set(GPERFTOOLS_VERSION "2.15") +set(ARGPARSE_VERSION "3.0") +include(NEML2Dependencies) +find_package(Torch) # This gets redirected to our FindTorch.cmake + +# ---------------------------------------------------------------------------- +# PyTorch ships libraries with or without CXX11 ABI +# ---------------------------------------------------------------------------- +if(Torch_CXX11_ABI) + add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=1) +else() + add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0) +endif() -find_package(Torch) - -# Install rpath, important for a relocatable install -option(NEML2_WHEELS "Customize the build for Python wheels" OFF) +# ---------------------------------------------------------------------------- +# For relocatable install +# ---------------------------------------------------------------------------- +if(UNIX AND APPLE) + set(EXEC_DIR "@loader_path") +elseif(UNIX AND NOT APPLE) + set(EXEC_DIR "$ORIGIN") +endif() -if(NEML2_WHEELS) - if(UNIX) - if(APPLE) - set(CMAKE_INSTALL_RPATH "@loader_path;@loader_path/lib;@loader_path/../torch/lib;@loader_path/../../torch/lib") - else() - set(CMAKE_INSTALL_RPATH "$ORIGIN;$ORIGIN/lib;$ORIGIN/../torch/lib;$ORIGIN/../../torch/lib") - endif() - endif() -else() - if(UNIX) - if(APPLE) - set(CMAKE_INSTALL_RPATH "@loader_path;@loader_path/lib;${Torch_LINK_DIRECTORIES}") - else() - set(CMAKE_INSTALL_RPATH "$ORIGIN;$ORIGIN/lib;${Torch_LINK_DIRECTORIES}") - endif() - endif() +# ---------------------------------------------------------------------------- +# Build types +# ---------------------------------------------------------------------------- +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build." FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo" "Coverage") endif() -# base library +# ---------------------------------------------------------------------------- +# Subdirectories +# ---------------------------------------------------------------------------- +# base neml2 library add_subdirectory(src/neml2) -# testing -option(NEML2_TESTS "Build NEML2 tests" ON) - +# tests if(NEML2_TESTS) add_subdirectory(tests) endif() -# Doxygen -option(NEML2_DOC "Build NEML2 documentation: doxygen" OFF) - -if(NEML2_DOC) - add_subdirectory(doc) +# runner +if(NEML2_RUNNER) + add_subdirectory(runner) endif() -# Python binding -option(NEML2_PYBIND "Build NEML2 Python binding" OFF) - +# Python bindings if(NEML2_PYBIND) add_subdirectory(python) endif() + +# Documentation +if(NEML2_DOC) + add_subdirectory(doc) +endif() diff --git a/README.md b/README.md index 50f4964565..5634c00b43 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,46 @@ -# Overview +# Overview {#overview} [![Documentation](https://github.com/reverendbedford/neml2/actions/workflows/build_docs.yml/badge.svg?branch=main)](https://reverendbedford.github.io/neml2/) [![tests](https://github.com/reverendbedford/neml2/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/reverendbedford/neml2/actions/workflows/tests.yml) -### The New Engineering Material model Library, version 2 +## The New Engineering Material model Library, version 2 -NEML2 is an offshoot of [NEML](https://github.com/Argonne-National-Laboratory/neml), an earlier constitutive modeling code developed at Argonne National Laboratory. -Like NEML, NEML2 provides a flexible, modular way to build constitutive models from smaller blocks. -Unlike NEML, NEML2 vectorizes the constitutive update to efficiently run on GPUs. NEML2 is built on top of [PyTorch](https://pytorch.org/cppdocs/) -to provide GPU support, but this also means that NEML2 models have all the features of a PyTorch module. So, for example, users can take derivatives of the model with respect to parameters using automatic differentiation. +NEML2 is an offshoot of [NEML](https://github.com/Argonne-National-Laboratory/neml), an earlier material modeling code developed at Argonne National Laboratory. +Like its predecessor, NEML2 provides a flexible, modular way to build material models from smaller blocks. +Unlike its predecessor, NEML2 vectorizes the material update to efficiently run on GPUs. NEML2 is built on top of [PyTorch](https://pytorch.org/cppdocs/) to provide GPU support, and this also means that NEML2 models have all the features of a PyTorch module (i.e. `torch.nn.module`). So, for example, users can take derivatives of the model with respect to parameters using automatic differentiation. NEML2 is provided as open source software under a MIT [license](https://raw.githubusercontent.com/reverendbedford/neml2/main/LICENSE). -### Build and installation +> **Disclaimer** +> +> NEML2 is _not_ a database of material models. There are many example material models in the library for testing and verification purposes. These models do not represent the response of any actual material. -Building should be as easy as cloning the repository, configuring with CMake, building with `make`, and testing with `make test`. -By default NEML2 will download the current CPU-only version of torch. To instead use a system torch set CMake options `-DLIBTORCH_DIR=/path/to/your/torch`. If you use the default build you will get a CPU-only version of torch and performance might suffer compared to a CUDA version. +### Quick installation -### NEML2 features and design philosophy +Building should be as easy as cloning the repository, configuring with CMake, compiling with `make`, and installing with `make install`. Refer to the [installation guide](https://reverendbedford.github.io/neml2/install.html) for more detailed instructions and finer control over various dependencies as well as other build customization. -- **Modular constitutive models**: NEML2 material models are modular – they are built up from smaller pieces into a complete model. For example, a model might piece together a temperature-dependent elasticity model, a yield surface, a flow rule, and several hardening rules. Each of these submodels is independent of the other objects so that, for example, switching from conventional J2 plasticity to a non J2 theory requires only a one line change in an input file, if the model is already implemented, or a relatively small amount of coding to add the new yield surface if it has not been implemented. All of these objects are interchangeable. For example, the damage, viscoplastic, and rate-independent plasticity models all use the same yield (flow) surfaces, hardening rules, elasticity models, and so on. -- **Extensible constitutive models**: The library is structured so that adding a new feature to an existing material model should be as simple as possible and require as little code as possible. As part of this philosophy, the library only requires new components provide a few partial derivatives and NEML uses this information to assemble the Jacobian needed to do a fully implement, backward Euler integration of the ordinary differential equations comprising the model form and to provide the algorithmic tangent needed to integrate the model into an implicit finite element framework. Moreover, in NEML2 implementations can forgo providing these partial derivatives and NEML2 will calculate them with automatic differentiation -- albeit at a significant performance cost. -- **Friendly user interfaces**: There are two general ways to create and interface with NEML2 material models: the python bindings and the compiled library with [HIT](https://github.com/idaholab/moose/tree/next/framework/contrib/hit) input. The python bindings are generally used for creating, fitting, and debugging new material models. In python, a material model is built up object-by-object and assembled into a complete mathematical constitutive relation. NEML2 provides several python drivers for exercising these material models in simple loading configurations. These drivers include common test types, like uniaxial tension tests and strain-controlled cyclic fatigue tests along with more esoteric drivers supporting simplified models of high temperature pressure vessels, like n-bar models and generalized plane-strain axisymmetry. NEML2 provides a full Abaqus UMAT interface and examples of how to link the compiled library into C, C++, or Fortran codes. These interfaces can be used to call NEML2 models from finite element codes. When using the compiled library, NEML2 models can be created and archived using a hierarchical HIT format. -- **Strict quality assurance**: NEML2 is developed under a strict quality assurance program. Because the NEML2 distribution does not provide full, parameterized models for any actual materials, ensuring the quality of the library is a verification problem – testing to make sure that NEML2 is correctly implementing the mathematical models – rather than a validation problem of comparing the results of a model to an actual test. This verification is done with extensive unit testing. This unit testing verifies every mathematical function and every derivative in the library is correctly implemented. -- **CPU/GPU Vectorization**: NEML2 models can be vectorized, meaning that a large batch of constitutive models can be evaluated simultaneously. The vectorized model can be evaluated both on CPU and on GPU, with a unified, intuitive user interface. -- **Flexible model composition**: NEML2 offers a more flexible way of composing models. Each individual model only defines the forward operator (and optionally its derivative) with a given set of inputs and outputs, without knowing anything a priori about how it is going to be used. When a set of models are *composed* together to form a composite model, dependencies among different models are automatically detected, registered, and resolved. The user has *complete control* over how NEML2 evaluates a set of models. -- **Faster evaluation of chained models**: As a result the dependency resolution mentioned above, an optimal order of evaluating the composed model is used to perform the forward operation -- every model in the dependency graph is evaluated once and only once, avoiding any redundant calculations. -- **General implicit update**: NEML2 offers a general interface for defining implicit models, unlike NEML which requires the implicit function to be in the form of an ODE. +### Features and design philosophy -### Disclaimer +#### Vectorization -NEML2 does not provide a database of models for any particular class of materials. There are many example materials contained in the library release. These models are included entirely for illustrative purposes and do not represent the response of any actual material. Right now these models are solid mechanics constitutive models, providing the stress/strain response of materials. However, NEML2 is general enough to build models of any type. +NEML2 models can be vectorized, meaning that a large _batch_ of material models can be evaluated simultaneously. The vectorized models can be evaluated on both CPU and GPU. Moreover, NEML2 provides a unified implementation and user interface, for both developers and end users, that work on all supported devices. + +#### Multiphysics coupling + +NEML2 is not tied to any underlying problem physics, e.g., solid mechanics, heat transfer, fluid dynamics, electromagnetics, etc. Current modules cover thermal and mechanical material models, but the framework *can* be used to implement a wider range of constitutive models. For coupled problems, NEML2 will return the exact, coupled Jacobian entries required to achieve optimal convergence. + +#### Modularity and flexibility + +NEML2 material models are modular – they are built up from smaller pieces into a complete model. NEML2 offers an extremely flexible way of composing models. Each individual model only defines the forward operator (and optionally its derivative) with a given set of inputs and outputs. When a set of models are *composed* together to form a composite model, dependencies among different models are automatically detected, registered, and resolved. The user has *complete control* over how NEML2 evaluates a set of models. + +#### Extensibility + +The library is structured so that adding a new feature to an existing material model should be as simple as possible and require as little code as possible. In line with this philosophy, the library only requires new components to provide a few partial derivatives, and NEML2 uses this information to automatically assemble the overall Jacobian using chain rule and provide the algorithmic tangent needed to integrate the model into an implicit finite element framework. Moreover, in NEML2, implementations can forgo providing these partial derivatives, and NEML2 will calculate them with automatic differentiation. + +#### Friendly user interfaces + +There are two general ways of interfacing with NEML2 material models: the compiled C++ library and the Python bindings. In both interfaces, creation and archival of NEML2 models rely on input files written in the hierarchical [HIT](https://github.com/idaholab/moose/tree/master/framework/contrib/hit) format. NEML2 models created using the Pytbon bindings are fully compatible with PyTorch's automatic differentiation, meaning that NEML2 material models can seamlessly work with popular machine learning frameworks. + +#### Strict quality assurance + +NEML2 is developed under a strict quality assurance program. Because the NEML2 distributions do not provide full, parameterized models for any actual materials, ensuring the quality of the library is a verification problem – testing to make sure that NEML2 is correctly implementing the mathematical models – rather than a validation problem of comparing the results of a model to an experiment. In NEML2, this verification is done with extensive unit testing. Additional regression tests are set up for each combination of material model to ensure result consistency across releases. diff --git a/cmake-variants.yaml b/cmake-variants.yaml index fd60c93762..19e7ec7d96 100644 --- a/cmake-variants.yaml +++ b/cmake-variants.yaml @@ -2,60 +2,40 @@ libneml2: default: development choices: development: - short: Development - long: Build for development + short: Development (C++) + long: Build for C++ development buildType: Debug settings: NEML2_TESTS: ON - NEML2_UNIT: ON - NEML2_REGRESSION: ON - NEML2_VERIFICATION: ON - NEML2_BENCHMARK: OFF - NEML2_PROFILING: OFF - profiling: - short: Benchmarking - long: Build for benchmarking and profiling - buildType: Release + NEML2_RUNNER: OFF + NEML2_PYBIND: OFF + NEML2_DOC: OFF + development-python: + short: Development (C++ and Python) + long: Build for C++ and Python binding development + buildType: Debug settings: NEML2_TESTS: ON - NEML2_UNIT: OFF - NEML2_REGRESSION: OFF - NEML2_VERIFICATION: OFF - NEML2_BENCHMARK: ON - NEML2_PROFILING: ON - release: - short: Release - long: Build for release - buildType: RelWithDebInfo + NEML2_RUNNER: OFF + NEML2_PYBIND: ON + NEML2_DOC: OFF + benchmarking: + short: Benchmarking + long: Build the runner for benchmarking and profiling + buildType: Release settings: NEML2_TESTS: OFF - -pyneml2: - default: none - choices: - none: - short: No pybind - long: Not building python binding - settings: + NEML2_RUNNER: ON + NEML2_RUNNER_AS_PROFILER: ON NEML2_PYBIND: OFF - NEML2_WHEELS: OFF - binding: - short: Pybind - long: Build python binding - settings: - NEML2_PYBIND: ON - NEML2_WHEELS: OFF - -doc: - default: no - choices: - no: - short: No doc - long: No documentation target - settings: NEML2_DOC: OFF - yes: - short: Doc - long: Add documentation targets + production: + short: Production + long: Build for production release + buildType: RelWithDebInfo settings: + CMAKE_UNITY_BUILD: ON + NEML2_TESTS: OFF + NEML2_RUNNER: OFF + NEML2_PYBIND: ON NEML2_DOC: ON diff --git a/cmake/Modules/FindTorch.cmake b/cmake/Modules/FindTorch.cmake index c14999840f..d15c2636ce 100644 --- a/cmake/Modules/FindTorch.cmake +++ b/cmake/Modules/FindTorch.cmake @@ -1,8 +1,3 @@ -set(PYTORCH_VERSION "2.2.2") - -# We will rely on FetchContent to download libTorch -include(FetchContent) - # If LIBTORCH_DIR is not defined, we should make some effort to provide a sensible default. # I have 2 plans below... @@ -25,41 +20,15 @@ endif() # Plan B: If we are on Unix systems, we could default to downloading a CPU-only # libTorch. if(NOT DEFINED LIBTORCH_DIR) - if(UNIX) - if(NOT APPLE) - FetchContent_Declare(torch URL https://download.pytorch.org/libtorch/cpu/libtorch-shared-with-deps-${PYTORCH_VERSION}%2Bcpu.zip) - else() - if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "arm64") - set(APPLE_SILICON ON) - FetchContent_Declare(torch URL https://download.pytorch.org/libtorch/cpu/libtorch-macos-arm64-${PYTORCH_VERSION}.zip) - else() - set(APPLE_SILICON OFF) - FetchContent_Declare(torch URL https://download.pytorch.org/libtorch/cpu/libtorch-macos-x86_64-${PYTORCH_VERSION}.zip) - endif() - endif() - - message(STATUS "Downloading libTorch, this may take a few minutes.") - FetchContent_MakeAvailable(torch) - - if(NOT torch_SOURCE_DIR) - message(FATAL_ERROR "Failed to donwload libTorch") - else() - set(LIBTORCH_DIR ${torch_SOURCE_DIR}) - endif() - - else() - message(STATUS "We only download a default libTorch (CPU) on Unix systems. This is not a Unix system.") - endif() + message(STATUS "Configuring Torch") + FetchContent_MakeAvailable(torch) + set(LIBTORCH_DIR ${torch_SOURCE_DIR}) endif() # At this point, if LIBTORCH_DIR is still not set, then both plan A and plan B have failed :( if(NOT DEFINED LIBTORCH_DIR) message(FATAL_ERROR - "LIBTORCH_DIR is not set, and we could not find/download a compatible libTorch. " - "There are two ways to fix this error:\n" - " 1. Manually download libTorch and set LIBTORCH_DIR while running cmake.\n" - " 2. Install Python and the PyTorch package.\n" - "If you are on a Unix-based system and ran into this error, please submit a bug report.") + "LIBTORCH_DIR is not set. Please refer to https://reverendbedford.github.io/neml2/install.html for more information.") else() message(STATUS "Using libTorch at: ${LIBTORCH_DIR}") include(NEML2TorchConfig) diff --git a/cmake/Modules/NEML2Dependencies.cmake b/cmake/Modules/NEML2Dependencies.cmake new file mode 100644 index 0000000000..b6a445c5d8 --- /dev/null +++ b/cmake/Modules/NEML2Dependencies.cmake @@ -0,0 +1,64 @@ +include(FetchContent) # For downloading dependencies + +# PyTorch +if(UNIX) + if(NOT APPLE) + FetchContent_Declare(torch URL https://download.pytorch.org/libtorch/cpu/libtorch-shared-with-deps-${PYTORCH_VERSION}%2Bcpu.zip) + else() + if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "arm64") + FetchContent_Declare(torch URL https://download.pytorch.org/libtorch/cpu/libtorch-macos-arm64-${PYTORCH_VERSION}.zip) + else() + FetchContent_Declare(torch URL https://download.pytorch.org/libtorch/cpu/libtorch-macos-x86_64-${PYTORCH_VERSION}.zip) + endif() + endif() +endif() + +# Doxygen for documentation +string(REPLACE "." "_" DOXYGEN_RELEASE ${DOXYGEN_VERSION}) +FetchContent_Declare( + doxygen + URL https://github.com/doxygen/doxygen/releases/download/Release_${DOXYGEN_RELEASE}/doxygen-${DOXYGEN_VERSION}.linux.bin.tar.gz +) + +# Doxygen html stylesheet +FetchContent_Declare( + doxygen-awesome-css + GIT_REPOSITORY https://github.com/jothepro/doxygen-awesome-css.git + GIT_TAG v${DOXYGEN_AWESOME_VERSION} +) + +# Pybind11 for Python bindings +FetchContent_Declare( + pybind11 + GIT_REPOSITORY https://github.com/pybind/pybind11.git + GIT_TAG v${PYBIND11_VERSION} +) + +# HIT for parsing input files +FetchContent_Declare( + hit + GIT_REPOSITORY https://github.com/idaholab/hit.git + GIT_TAG ${HIT_VERSION} + SOURCE_DIR ${NEML2_BINARY_DIR}/_deps/hit/hit +) + +# Catch2 for testing +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v${CATCH2_VERSION} +) + +# gperftools for profiling +FetchContent_Declare( + gperftools + GIT_REPOSITORY https://github.com/gperftools/gperftools.git + GIT_TAG gperftools-${GPERFTOOLS_VERSION} +) + +# C++ implementation of argparse +FetchContent_Declare( + argparse + GIT_REPOSITORY https://github.com/p-ranav/argparse.git + GIT_TAG v${ARGPARSE_VERSION} +) diff --git a/cmake/Modules/NEML2TorchConfig.cmake b/cmake/Modules/NEML2TorchConfig.cmake index 9af004ea45..45618b607d 100644 --- a/cmake/Modules/NEML2TorchConfig.cmake +++ b/cmake/Modules/NEML2TorchConfig.cmake @@ -1,7 +1,7 @@ # libTorch comes with two flavors: one with cxx11 abi, one without. # We should be consistent with whatever is detected from the libTorch. try_compile(Torch_CXX11_ABI - ${NEML2_SOURCE_DIR}/cmake/detect_torch_cxx11_abi/build + ${NEML2_BINARY_DIR}/cmake/detect_torch_cxx11_abi/build ${NEML2_SOURCE_DIR}/cmake/detect_torch_cxx11_abi TEST CMAKE_FLAGS "-DLIBTORCH_DIR=${LIBTORCH_DIR}" diff --git a/cmake/Modules/NEML2UnityGroup.cmake b/cmake/Modules/NEML2UnityGroup.cmake index fb62e9dc08..83aaed4e99 100644 --- a/cmake/Modules/NEML2UnityGroup.cmake +++ b/cmake/Modules/NEML2UnityGroup.cmake @@ -1,9 +1,8 @@ -# ################################################### -# MACROs for registering UNITY groups -# ################################################### +# ---------------------------------------------------------------------------- +# Macros for registering unity groups +# ---------------------------------------------------------------------------- macro(_src_subdirs result curdir) file(GLOB_RECURSE children ${curdir}/*.cxx) - set(dirlist "") foreach(child ${children}) get_filename_component(child_dir ${child} DIRECTORY) @@ -14,7 +13,7 @@ macro(_src_subdirs result curdir) set(${result} ${dirlist}) endmacro() -macro(_set_unity_group name root subdir) +macro(_set_unity_group root subdir) file(RELATIVE_PATH UNITY_NAME_RAW ${root} ${subdir}) if(NOT UNITY_NAME_RAW STREQUAL "") @@ -24,13 +23,12 @@ macro(_set_unity_group name root subdir) if(NOT UNITY_NAME MATCHES "CMakeFiles") file(GLOB_RECURSE UNITY_FILES ${subdir}/*.cxx) set_source_files_properties(${UNITY_FILES} PROPERTIES UNITY_GROUP ${UNITY_NAME}) - message(STATUS "${name}: group unity for ${UNITY_NAME}") endif() endif() endif() endmacro() -macro(register_unity_group target name rel_root) +macro(register_unity_group target rel_root) get_target_property(UNITY_ENABLED ${target} UNITY_BUILD) if(UNITY_ENABLED) @@ -38,7 +36,7 @@ macro(register_unity_group target name rel_root) _src_subdirs(SUBDIRS ${ABS_ROOT}) foreach(subdir ${SUBDIRS}) - _set_unity_group(${name} ${ABS_ROOT} ${subdir}) + _set_unity_group(${ABS_ROOT} ${subdir}) endforeach() set_target_properties(${target} PROPERTIES UNITY_BUILD_MODE GROUP) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index f62bd4539a..c9d96bdc54 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -1,73 +1,90 @@ -if(NOT DEFINED DOXYGEN_EXECUTABLE) - if(UNIX AND NOT APPLE) - FetchContent_Declare( - doxygen - URL https://github.com/doxygen/doxygen/releases/download/Release_1_9_8/doxygen-1.9.8.linux.bin.tar.gz - ) - message(STATUS "Downloading doxygen, this may take a few minutes.") - FetchContent_MakeAvailable(doxygen) - set(DOXYGEN_EXECUTABLE ${doxygen_SOURCE_DIR}/bin/doxygen) - else() - message(FATAL_ERROR "We only download a default doxygen on linux for CI purposes. For other operating systems, please specify DOXYGEN_EXECUTABLE.") - endif() -endif() +# ---------------------------------------------------------------------------- +# Dependencies and 3rd party packages +# ---------------------------------------------------------------------------- +message(STATUS "Configuring Doxygen") +FetchContent_MakeAvailable(doxygen) +set(DOXYGEN_EXECUTABLE ${doxygen_SOURCE_DIR}/bin/doxygen) +FetchContent_MakeAvailable(doxygen-awesome-css) find_package(Python COMPONENTS Interpreter) - -# Generate Doxyfile for HTML -message(STATUS "Generating Doxyfile for HTML") -file(WRITE ${NEML2_BINARY_DIR}/doc/DoxyfileHTML.in "") -file(READ ${NEML2_SOURCE_DIR}/doc/config/Doxyfile.in CONTENTS) -file(APPEND ${NEML2_BINARY_DIR}/doc/DoxyfileHTML.in ${CONTENTS}) -file(READ ${NEML2_SOURCE_DIR}/doc/config/HTML.in CONTENTS) -file(APPEND ${NEML2_BINARY_DIR}/doc/DoxyfileHTML.in ${CONTENTS}) -configure_file( - ${NEML2_BINARY_DIR}/doc/DoxyfileHTML.in - ${NEML2_BINARY_DIR}/doc/DoxyfileHTML.sh +execute_process( + COMMAND_ERROR_IS_FATAL ANY + COMMAND ${Python_EXECUTABLE} ${NEML2_SOURCE_DIR}/scripts/check_python_dep.py ${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt ) -# Generate Doxyfile for LATEX -message(STATUS "Generating Doxyfile for LATEX") -file(WRITE ${NEML2_BINARY_DIR}/doc/DoxyfileLATEX.in "") -file(READ ${NEML2_SOURCE_DIR}/doc/config/Doxyfile.in CONTENTS) -file(APPEND ${NEML2_BINARY_DIR}/doc/DoxyfileLATEX.in ${CONTENTS}) -file(READ ${NEML2_SOURCE_DIR}/doc/config/LATEX.in CONTENTS) -file(APPEND ${NEML2_BINARY_DIR}/doc/DoxyfileLATEX.in ${CONTENTS}) -configure_file( - ${NEML2_BINARY_DIR}/doc/DoxyfileLATEX.in - ${NEML2_BINARY_DIR}/doc/DoxyfileLATEX.sh -) +# ---------------------------------------------------------------------------- +# Macro for generating and configuring Doxyfile +# ---------------------------------------------------------------------------- +macro(generate_doxyfile output inputs) + file(WRITE ${output}.in "") + + foreach(input ${inputs}) + file(READ ${input} _content) + file(APPEND ${output}.in ${_content}) + endforeach() + + configure_file(${output}.in ${output}.sh) -add_custom_target(doc-syntax - DEPENDS unit_tests + file(REMOVE ${output}.in) +endmacro() + +# ---------------------------------------------------------------------------- +# Extract all input file syntax +# ---------------------------------------------------------------------------- +add_executable(syntax-cpp-exe syntax.cxx) +target_link_libraries(syntax-cpp-exe PRIVATE neml2) +add_custom_target(syntax-cpp + DEPENDS syntax-cpp-exe WORKING_DIRECTORY ${NEML2_BINARY_DIR}/doc - COMMAND ${NEML2_BINARY_DIR}/tests/unit_tests Registry -c syntax -r compact - COMMAND ${Python_EXECUTABLE} ${NEML2_SOURCE_DIR}/scripts/syntax_to_md.py syntax.yml content/_99_syntax.md - COMMENT "Generate NEML2 syntax and convert to markdown" + COMMAND ${NEML2_BINARY_DIR}/doc/syntax-cpp-exe + COMMAND ${Python_EXECUTABLE} ${NEML2_SOURCE_DIR}/scripts/syntax_to_md.py syntax.yml content/syntax syntax_error.log + VERBATIM +) + +# ---------------------------------------------------------------------------- +# Extract all Python API +# ---------------------------------------------------------------------------- +add_custom_target(syntax-python + DEPENDS base tensors models math + WORKING_DIRECTORY ${NEML2_BINARY_DIR}/python + COMMAND ${CMAKE_COMMAND} -E make_directory ${NEML2_BINARY_DIR}/doc/content/python + COMMAND PYTHONPATH=. pybind11-stubgen -o ${NEML2_BINARY_DIR}/doc/content/python neml2 + COMMAND PYTHONPATH=. pybind11-stubgen -o ${NEML2_BINARY_DIR}/doc/content/python neml2.math + COMMAND ${Python_EXECUTABLE} ${NEML2_SOURCE_DIR}/scripts/fixup_pystub.py ${NEML2_BINARY_DIR}/doc/content/python/neml2 VERBATIM ) -add_custom_target(doc-html - DEPENDS doc-syntax +# ---------------------------------------------------------------------------- +# HTML +# ---------------------------------------------------------------------------- +generate_doxyfile(${NEML2_BINARY_DIR}/doc/DoxyfileHTML "config/Doxyfile.in;config/HTML.in") +generate_doxyfile(${NEML2_BINARY_DIR}/doc/DoxyfilePython "config/Doxyfile.in;config/HTML.in;config/Python.in") +add_custom_target(html ALL + DEPENDS syntax-cpp syntax-python WORKING_DIRECTORY ${NEML2_BINARY_DIR}/doc COMMAND ${DOXYGEN_EXECUTABLE} -q DoxyfileHTML.sh - COMMENT "Generate Doxygen HTML" + COMMAND ${DOXYGEN_EXECUTABLE} -q DoxyfilePython.sh VERBATIM ) -add_custom_target(doc-latex - DEPENDS doc-syntax +# ---------------------------------------------------------------------------- +# LaTeX +# ---------------------------------------------------------------------------- +generate_doxyfile(${NEML2_BINARY_DIR}/doc/DoxyfileLaTeX "config/Doxyfile.in;config/LaTeX.in") +add_custom_target(latex + DEPENDS syntax-cpp WORKING_DIRECTORY ${NEML2_BINARY_DIR}/doc - COMMAND ${DOXYGEN_EXECUTABLE} -q DoxyfileLATEX.sh - COMMENT "Generate Doxygen LATEX" + COMMAND ${DOXYGEN_EXECUTABLE} -q DoxyfileLaTeX.sh VERBATIM ) -add_custom_target(doc-pdf - DEPENDS doc-latex +# ---------------------------------------------------------------------------- +# PDF +# ---------------------------------------------------------------------------- +add_custom_target(pdf + DEPENDS latex WORKING_DIRECTORY ${NEML2_BINARY_DIR}/doc/build/latex COMMAND ${CMAKE_COMMAND} -E copy_directory ${NEML2_SOURCE_DIR}/doc/content/asset ${NEML2_BINARY_DIR}/doc/build/latex COMMAND latexmk -pdf refman.tex -outdir=${NEML2_BINARY_DIR}/doc/build/pdf -silent - COMMENT "Generate Doxygen PDF" VERBATIM ) diff --git a/doc/config/Doxyfile.in b/doc/config/Doxyfile.in index 0532308cc0..536cdd5def 100644 --- a/doc/config/Doxyfile.in +++ b/doc/config/Doxyfile.in @@ -7,7 +7,7 @@ DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = NEML2 PROJECT_NUMBER = 1.4.0 OUTPUT_DIRECTORY = build -TOC_INCLUDE_HEADINGS = 2 +TOC_INCLUDE_HEADINGS = 3 #--------------------------------------------------------------------------- # Build related configuration options @@ -24,7 +24,6 @@ LAYOUT_FILE = ${NEML2_SOURCE_DIR}/doc/config/DoxygenLayout.xml # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- WARN_NO_PARAMDOC = YES -WARN_LOGFILE = ${NEML2_BINARY_DIR}/doc/doxygen.log #--------------------------------------------------------------------------- # Configuration options related to the input files diff --git a/doc/config/DoxygenLayout.xml b/doc/config/DoxygenLayout.xml index b6e2a4a808..354c223a86 100644 --- a/doc/config/DoxygenLayout.xml +++ b/doc/config/DoxygenLayout.xml @@ -1,14 +1,29 @@ - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/config/DoxygenLayoutPython.xml b/doc/config/DoxygenLayoutPython.xml new file mode 100644 index 0000000000..d678dc5104 --- /dev/null +++ b/doc/config/DoxygenLayoutPython.xml @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/config/HTML.in b/doc/config/HTML.in index d9b7abfcb9..8bf5334601 100644 --- a/doc/config/HTML.in +++ b/doc/config/HTML.in @@ -2,12 +2,16 @@ # Configuration options related to the HTML output #--------------------------------------------------------------------------- GENERATE_HTML = YES +DISABLE_INDEX = NO +FULL_SIDEBAR = NO HTML_OUTPUT = html -HTML_EXTRA_STYLESHEET = ${NEML2_SOURCE_DIR}/extern/doxygen-awesome-css/doxygen-awesome.css \ - ${NEML2_SOURCE_DIR}/extern/doxygen-awesome-css/doxygen-awesome-sidebar-only.css +HTML_EXTRA_STYLESHEET = ${doxygen-awesome-css_SOURCE_DIR}/doxygen-awesome.css\ + ${doxygen-awesome-css_SOURCE_DIR}/doxygen-awesome-sidebar-only.css +HTML_COLORSTYLE = LIGHT GENERATE_TREEVIEW = YES USE_MATHJAX = YES MATHJAX_VERSION = MathJax_3 MATHJAX_FORMAT = HTML-CSS MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@3 -MATHJAX_EXTENSIONS = ams physics +MATHJAX_EXTENSIONS = ams physics boldsymbol +WARN_LOGFILE = ${NEML2_BINARY_DIR}/doc/doxygen.html.log diff --git a/doc/config/LATEX.in b/doc/config/LaTeX.in similarity index 92% rename from doc/config/LATEX.in rename to doc/config/LaTeX.in index c352ea6c57..27c85f78f5 100644 --- a/doc/config/LATEX.in +++ b/doc/config/LaTeX.in @@ -8,6 +8,7 @@ LATEX_HEADER = ${NEML2_SOURCE_DIR}/doc/config/ANLReportHeader.tex LATEX_FOOTER = ${NEML2_SOURCE_DIR}/doc/config/ANLReportFooter.tex LATEX_EXTRA_STYLESHEET = ${NEML2_SOURCE_DIR}/doc/config/ANLReportExtra.sty LATEX_HIDE_INDICES = YES +WARN_LOGFILE = ${NEML2_BINARY_DIR}/doc/doxygen.latex.log #--------------------------------------------------------------------------- # Configuration options related to the dot tool diff --git a/doc/config/Python.in b/doc/config/Python.in new file mode 100644 index 0000000000..73afe6cc3c --- /dev/null +++ b/doc/config/Python.in @@ -0,0 +1,8 @@ +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output for Python bindings +#--------------------------------------------------------------------------- +HTML_OUTPUT = html/python +FILE_PATTERNS = *.pyi +EXTENSION_MAPPING = pyi=Python +WARN_LOGFILE = ${NEML2_BINARY_DIR}/doc/doxygen.python.log +LAYOUT_FILE = ${NEML2_SOURCE_DIR}/doc/config/DoxygenLayoutPython.xml diff --git a/doc/content/_01_install.md b/doc/content/_01_install.md deleted file mode 100644 index 203ba39e9e..0000000000 --- a/doc/content/_01_install.md +++ /dev/null @@ -1,152 +0,0 @@ -# Getting Started {#install} - -[TOC] - -## Choose the compute platform - -NEML2 can be compiled to support two compute platforms: -- CPU -- CUDA - -Choose the CPU compute platform if you only need to run NEML2 using CPUs. Choose the CUDA compute platform if you plan to take advantage of the GPU(s) of the computer. - -## Dependencies - -NEML2 depends on the following drivers/packages/libraries: -| CPU | CUDA | Dependency | Version | -| :---: | :---: | :----------- | :------ | -| x | x | C++ compiler | >=17 | -| x | x | CMake | >=3.1 | -| x | x | libTorch | | -| | x | CUDA toolkit | | - -See [notes on obtaining the dependencies](@ref NotesOnObtainingTheDependencies) for some guidance. - -For developers, some additional dependencies are recommended (regardless of the compute platform): -- [Doxygen](https://www.doxygen.nl/), the documentation generator -- [clang-format](https://clang.llvm.org/docs/ClangFormat.html), the C++ code formatter - -## Install NEML2 - -First, obtain the NEML2 source code. - -``` -git clone https://github.com/reverendbedford/neml2.git -cd neml2 -git checkout main -git submodule update --recursive --init -``` - -Then, configure NEML2 and generate the Makefile. - -``` -cmake -DCMAKE_PREFIX_PATH=/path/to/torch/share/cmake . -``` -where `/path/to/torch/share/cmake` is the path to the `share/cmake` directory of your libTorch installation. - -Finally, compile NEML2. - -``` -make -j N -``` -where `N` is the number of processors to use for parallel compilation. - -After the compilation finishes, you can run the tests to make sure the build was successful: -``` -make test -``` - -## Notes on obtaining the dependencies {#NotesOnObtainingTheDependencies} - -Feel free to create a ticket at [https://github.com/reverendbedford/neml2/issues](https://github.com/reverendbedford/neml2/issues) if you run into issues about installing the dependencies. - -Typically, the C++ compiler, a reasonably modern CMake, Doxygen, and clang-format can be obtained via the system package manager. - -libTorch can be downloaded from the official PyTorch website: [https://pytorch.org/get-started/locally/](https://pytorch.org/get-started/locally/). Choose the compute platform in consistency with the NEML2 compute platform. Also take a note of the version of the libTorch-compatible CUDA if you chose CUDA as the compute platform. Note that there are two versions of libTorch: "Pre-cxx11 ABI" and "cxx11 ABI". Both versions are supported by NEML2. If you are unsure, we recommend the one with "cxx11 ABI". - -> In the future we may provide an option to automatically install a compatible version of libTorch. - -There are many ways of installing the NVIDIA driver and the CUDA toolkit. We will not try to make a recommendation here. However, do make sure the NVIDIA driver is compatible with your GPU. It is also recommended to install a CUDA toolkit with the same version number as the libTorch CUDA version. - -## Quick Start {#user} - -NEML2 uses the [HIT](https://github.com/idaholab/hit) format, a simple hierarchical text language, for model specification. More generally speaking, HIT is the canonical language used in NEML2 for serialization, deserialization, and archival purposes. - -The NEML2 input files have extension `.i`. An example input file is shown below -```python -[Tensors] - [end_time] - type = LogSpaceTensor - start = -1 - end = 5 - steps = 20 - [] - [times] - type = LinSpaceTensor - end = end_time - steps = 100 - [] - [max_strain] - type = InitializedSymR2 - values = '0.1 -0.05 -0.05' - nbatch = 20 - [] - [strains] - type = LinSpaceTensor - end = max_strain - steps = 100 - [] -[] - -[Drivers] - [driver] - type = SolidMechanicsDriver - model = 'model' - times = 'times' - prescribed_strains = 'strains' - save_as = 'result.pt' - [] - [regression] - type = TransientRegression - driver = 'driver' - reference = 'gold/result.pt' - [] -[] - -[Solvers] - [newton] - type = Newton - [] -[] - -[Models] - [implicit_rate] - type = ComposedModel - models = 'mandel_stress vonmises yield normality flow_rate Eprate Erate Eerate elasticity integrate_stress' - [] - [model] - type = ImplicitUpdate - implicit_model = 'implicit_rate' - solver = 'newton' - [] -[] -``` -There are four top-level sections: -- `[Tensors]`: Tensors specified from within the input file. -- `[Solvers]`: Solvers for solving an implicit model. -- `[Models]`: NEML2 constitutive models. -- `[Drivers]`: Drivers used to evaluate/test models. - -The user has full control over the sub-sections under the top-level sections, called _objects_. The sub-section name is used as the name of the object. Each object reserves a special field named "type". NEML2 parses the value in the "type" field and constructs the corresponding object at run time. The syntax and options for all objects are listed in the [syntax documentation](@ref syntax). - -Currently, NEML2 does not maintain a set of examples. However, the regression tests shall serve as decent input file templates. The regression tests are located in `/tests/regression` in the repository. - - -## Next steps - -Depending on your specific use case, the following resources might be useful: - -- [Mathematical conventions](@ref math) introduces the common mathematical notations used throughout this documentation. -- [Implementation](@ref impl) describes the basics of the library architecture and design philosophy. They can be a good starting point if you want to create your own material model within the NEML2 framework. -- [Model development](@ref devel) is a step-by-step guide for building a constitutive model. It uses the small deformation \f$J_2\f$ isotropic viscoplasticity as an example. -- [Class list](https://reverendbedford.github.io/neml2/annotated.html) is a complete list of class doxygen documentations. diff --git a/doc/content/_02_math.md b/doc/content/_02_math.md deleted file mode 100644 index e3a6886f88..0000000000 --- a/doc/content/_02_math.md +++ /dev/null @@ -1,79 +0,0 @@ -# Mathematical Conventions {#math} - -[TOC] - -## Typography - -The manual uses a lower case, standard font \f$x\f$ to represent a scalar, a bold lower case to represent a vector -\f$\mathbf{x}\f$, a bold upper case to represent a second order tensor \f$\mathbf{x}\f$, and a bold upper case Fraktur to represent a fourth order tensor \f$\mathbf{\mathfrak{X}}\f$. - -There are certain exceptions to the upper case/lower case convention for commonly used notation. For example the stress and strain tensors are denoted \f$\mathbf{\sigma}\f$ and \f$\mathbf{\varepsilon}\f$, respectively. - -Internally in NEML2 all tensor operations are implemented as [Mandel](#mandel) dot products. However, the documentation writes the full tensor form of the equations. - -NEML2 models work in three dimensions. This means second order tensors can be expressed by 3-by-3 arrays and -fourth order tensors are 3-by-3-by-3-by-3 arrays. - -## Mandel notation {#mandel} - -NEML2 uses the Mandel notation to convert symmetric second and fourth order tensors to vectors and matrices. -The convention transforms the second order tensor -\f[ - \left[\begin{array}{ccc} - \sigma_{11} & \sigma_{12} & \sigma_{13}\\ - \sigma_{12} & \sigma_{22} & \sigma_{23}\\ - \sigma_{13} & \sigma_{23} & \sigma_{33} - \end{array}\right] -\f] -to -\f[ - \left[\begin{array}{cccccc} - \sigma_{11} & \sigma_{22} & \sigma_{33} & \sqrt{2}\sigma_{23} & - \sqrt{2}\sigma_{13} & \sqrt{2}\sigma_{12}\end{array}\right] -\f] -and, after transformation, a fourth order tensor \f$\mathbf{\mathfrak{C}}\f$ becomes -\f[ - \left[\begin{array}{cccccc} - C_{1111} & C_{1122} & C_{1133} & \sqrt{2}C_{1123} & \sqrt{2}C_{1113} & \sqrt{2}C_{1112}\\ - C_{1122} & C_{2222} & C_{2233} & \sqrt{2}C_{2223} & \sqrt{2}C_{2213} & \sqrt{2}C_{2212}\\ - C_{1133} & C_{2233} & C_{3333} & \sqrt{2}C_{3323} & \sqrt{2}C_{3313} & \sqrt{2}C_{3312}\\ - \sqrt{2}C_{1123} & \sqrt{2}C_{2223} & \sqrt{2}C_{3323} & 2C_{2323} & 2C_{2313} & 2C_{2312}\\ - \sqrt{2}C_{1113} & \sqrt{2}C_{2213} & \sqrt{2}C_{3313} & 2C_{2313} & 2C_{1313} & 2C_{1312}\\ - \sqrt{2}C_{1112} & \sqrt{2}C_{2212} & \sqrt{2}C_{3312} & 2C_{2312} & 2C_{1312} & 2C_{1212} - \end{array}\right]. -\f] -The inner product of two symmetric second order tensors \f$\mathbf{A}\f$ and \f$\mathbf{B}\f$ can be conveniently expressed in their Mandel notations \f$\hat{\mathbf{a}}\f$ and \f$\hat{\mathbf{b}}\f$ as -\f[ - \mathbf{A}:\mathbf{B}=\hat{\mathbf{a}}\cdot\hat{\mathbf{b}} -\f] - -which expresses the utility of this convention. - -Similarly, given the symmetric fourth order tensor \f$\mathbf{\mathfrak{C}}\f$ and its equivalent Mandel matrix \f$\hat{\mathbf{C}}\f$ contraction over two adjacent indices -\f[ - \mathbf{A}=\mathbf{\mathfrak{C}}:\mathbf{B} -\f] -simply becomes matrix-vector multiplication -\f[ - \hat{\mathbf{a}}=\hat{\mathbf{C}}\cdot\hat{\mathbf{b}}. -\f] - -The Mandel convention is relatively uncommon in finite element software, and so the user must be careful to convert back and forth from the Mandel convention to whichever convention the calling software uses. The Abaqus UMAT interface provided with NEML2 demonstrates how to make this conversion before and after each call. - -## Commonly used operators - -The deviator of a second order tensor is denoted: -\f[ - \operatorname{dev}\left(\mathbf{X}\right) = \mathbf{X} - \frac{1}{3} - \operatorname{tr}\left(\mathbf{X}\right) \mathbf{I} -\f] -with \f$\operatorname{tr}\f$ the trace and \f$\mathbf{I}\f$ the identity tensor. - -## Concatenation of flattened quantities - -When describing collections of objects the manual uses square brackets. For example, -\f[ - \left[ \begin{array}{ccc} s & \mathbf{X} & \mathbf{v} \end{array}\right] -\f] -indicates a collection of a scalar \f$s\f$, a vector representing a second order tensor \f$\mathbf{X}\f$ in Mandel notation, and a vector \f$\mathbf{v}\f$. This notation indicates the implementation is concatenating the quantities into a flat, 1D array (in this case with length \f$1 + 6 + 3 = 10\f$) preserving the original order. - diff --git a/doc/content/_03_impl.md b/doc/content/_03_impl.md deleted file mode 100644 index bff46426a3..0000000000 --- a/doc/content/_03_impl.md +++ /dev/null @@ -1,270 +0,0 @@ -# Implementation {#impl} - -[TOC] - -## NEML2 tensor types {#primitive} - -Currently, libTorch is the only supported tensor backend in NEML2. Therefore, all tensor types in NEML2 directly inherit from `torch::Tensor`. In the future, support for other tensor backend libraries may be added, but the public-facing interfaces will remain largely the same. - -### BatchTensor - -[BatchTensor](@ref neml2::BatchTensor) is a general purpose tensor type for batched `torch::Tensor`s. With a view towards vectorization, the same set of operations can be "simultaneously" applied to a large "batch" of (logically the same) tensors. To provide a unified user interface for dealing with such batched operation, NEML2 assumes that the *first* \f$N\f$ dimensions of a tensor are batched dimensions, and the following dimensions are the base (logical) dimensions. - -> Unlike libTorch, NEML2 explicitly distinguishes between batch dimensions and base (logical) dimensions. - -The `BatchTensor` is templated on the number of batch dimensions \f$N\f$. Although the number of batched dimensions is known at compile time, the size of each dimension is not. The batch dimensions can be reshaped at runtime. For example, a `BatchTensor` can be created as -```cpp -BatchTensor<2> A = torch::rand({1, 1, 5, 2}); -``` -where `A` is a tensor with 2 batch dimensions. The batch sizes of `A` is `(1, 1)`: -```cpp -auto batch_sz = A.batch_sizes(); -// batch_sz == {1, 1} -``` -and the base (logical) sizes of `A` is `(5, 2)`: -```cpp -auto base_sz = A.base_sizes(); -// batch_sze == {5, 2} -``` -The base tensor can be reshaped (e.g., expanded and copied) at runtime along its batch dimensions using -```cpp -BatchTensor<2> B = A.batch_expand_copy({3, 4}); -auto new_batch_sz = B.batch_sizes(); -// new_batch_sz == {3, 4} -``` - -### FixedDimTensor - -[FixedDimTensor](@ref neml2::FixedDimTensor) inherits from `BatchTensor`. It is additionally templated on the sizes of the base dimensions. For example, -```cpp -static_assert(FixedDimTensor<2, 6>::const_base_sizes == {6}); -``` - -### Primitive tensor types - -All primitive tensor types inherit from `FixedDimTensor` with a *single* batch dimension. Currently implemented primitive tensor types include -- [Scalar](@ref neml2::Scalar), a (batched) scalar quantity derived from the specialization `FixedDimTensor<1, 1>` -- [SymR2](@ref neml2::SymR2), a (batched) symmetric second order tensor derived from the specialization `FixedDimTensor<1, 6>` -- [SymSymR4](@ref neml2::SymSymR4), a (batched) symmetric fourth order tensor derived from the specialization `FixedDimTensor<1, 6, 6>` - -Furthermore, all primitive tensor types can be "registered" as variables on a `LabeledAxis`, which will be discussed in the following section on [labeled view](@ref labeledview). - -## Tensor view and label {#labeledview} - -### Tensor view - -In defining the forward operator of a constitutive model, many logically different tensors representing inputs, outputs, residuals, and Jacobians have to be created, copied, and destroyed on the fly. These operations occupy a significant amount of computing time, especially on GPUs. - -To address this challenge, NEML2 creates *views*, instead of copies, of tensors whenever possible. As its name suggests, the view of a tensor is a possibly different interpretation of the underlying data. Quoting the PyTorch documentation: - -> For a tensor to be viewed, the new view size must be compatible with its original size and stride, i.e., each new view dimension must either be a subspace of an original dimension, or only span across original dimensions \f$d, d+1, ..., d+k\f$ that satisfy the following contiguity-like condition that \f$\forall i = d,...,d+k-1\f$, -> \f[ -> \text{stride}[i] = \text{stride}[i+1] \times \text{size}[i+1] -> \f] -> Otherwise, it will not be possible to view self tensor as shape without copying it. - -### Working with tensor views - -In NEML2, to index a view of a `BatchTensor`, use [base_index](@ref neml2::BatchTensor::base_index) for indexing the base dimensions and [batch_index](@ref neml2::BatchTensor::batch_index) for indexing the batch dimensions: -```cpp -BatchTensor<1, 3> A = torch::tensor({{2, 3, 4}, {-1, -2, 3}, {6, 9, 7}}); -// A = [[ 2 3 4] -// [ -1 -2 3] -// [ 6 9 7]] -using namespace torch::indexing; -BatchTensor<1, 3> B = A.batch_index({Slice(0, 2)}); -// B = [[ 2 3 4] -// [ -1 -2 3]] -BatchTensor<1, 3> C = A.base_index({Slice(1, 3)}); -// C = [[ 3 4] -// [ -2 3] -// [ 9 7]] -``` -To modify a view of a `BatchTensor`, use [base_index_put](@ref neml2::BatchTensor::base_index_put) or [batch_index_put](@ref neml2::BatchTensor::batch_index_put): -```cpp -A.base_index_put({Slice(1, 3)}, torch::ones({3, 2})); -// A = [[ 2 1 1] -// [ -1 1 1] -// [ 6 1 1]] -A.batch_index_put({Slice(0, 2)}, torch::zeros({2, 3})); -// A = [[ 0 0 0] -// [ 0 0 0] -// [ 6 1 1]] -``` -A detailed explanation on tensor indexing APIs is available as part of the official [PyTorch documentation](https://pytorch.org/cppdocs/notes/tensor_indexing.html). - -### Labeled tensor view - -In the context of constitutive modeling, often times views of tensors have practical/physical meanings. For example, given a logically 1D tensor with base size 9, its underlying data in an arbitrary batch may look like -``` -equivalent plastic strain 2.1 - cauchy stress -2.1 - 0 - 1.3 - -1.1 - 2.5 - 2.5 - temperature 102.9 - time 3.6 -``` -where component 0 stores the scalar-valued equivalent plastic strain, components 1-6 store the tensor-valued cauchy stress (recall that we use the [Mandel](@ref mandel) notation for symmetric second order tensors), component 7 stores the scalar-valued temperature, and component 8 stores the scalar-valued time. - -The string indicating the physical meaning of the view, e.g., "cauchy stress", is called a "label", and the view of the tensor indexed by a label is called a "labeled view", i.e., -``` - cauchy stress -2.1 - 0 - 1.3 - -1.1 - 2.5 - 2.5 -``` - -NEML2 provides a data structure named [LabeledAxis](@ref neml2::LabeledAxis) to facilitate the creation and modification of labels, and a data structure named [LabeledTensor](@ref neml2::LabeledTensor) to facilitate the creation and modification of labeled views. - -### LabeledAxis - -The [LabeledAxis](@ref neml2::LabeledAxis) contains all information regarding how an axis of a `LabeledTensor` is labeled. The following naming convention is used: -- Item: A labelable chunk of data -- Variable: An item that is also of a [NEML2 primitive tensor type](@ref primitive) -- Sub-axis: An item of type `LabeledAxis` - -So yes, an axis can be labeled recursively, e.g., - -``` - 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 -/// |-----------| |-----| | | | | | | -/// a b | | | | | | -/// |-------------------| |--------------| |--| |--| -/// sub a b c -``` -The above example represents an axis of size 15. This axis has 4 items: `a`, `b`, `c`, and `sub`. -- "a" is a variable of storage size 6 (possibly of type `SymR2`). -- "b" is a variable of type `Scalar`. -- "c" is a variable of type `Scalar`. -- "sub" is a sub-axis of type `LabeledAxis`. "sub" by itself represents an axis of size 7, containing 2 items: - - "a" is a variable of storage size 6. - - "b" is a variable of type `Scalar`. - -Duplicate labels are *not* allowed on the same level of the axis, e.g. "a", "b", "c", and "sub" share the same level and so must be different. However, items on different levels of an axis can share the same label, e.g., "a" on the sub-axis "sub" has the same label as "a" on the main axis. In NEML2 convention, item names are always fully qualified, and a sub-axis is prefixed with a left slash, e.g. item "b" on the sub-axis "sub" can be denoted as "sub/b" on the main axis. - -> A label cannot contain: white spaces, quotes, left slash (`/`), or new line. - -> Due to performance considerations, a `LabeledAxis` can only be modified, e.g., adding/removing variables and sub-axis, at the time a model is constructed. After the model construction phase, the `LabeledAxis` associated with that model can no longer be modified over the entire course of the simulation. - -Refer to the doxygen documentation for a complete list of APIs for creating and modifying a [LabeledAxis](@ref neml2::LabeledAxis). - -### LabeledTensor - -[LabeledTensor](@ref neml2::LabeledTensor) is the primary data structure in NEML2 for working with labeled tensor views. Each `LabeledTensor` consists of one `BatchTensor` and one or more `LabeledAxis`s. The `LabeledTensor` is templated on the batch dimension \f$N\f$ and the base dimension \f$D\f$. [LabeledVector](@ref neml2::LabeledVector) (derived from `LabeledTensor<1, 1>`) and [LabeledMatrix](@ref neml2::LabeledMatrix) (derived from `LabeledTensor<1, 2>`) are the two most widely used data structures in NEML2. - -`LabeledTensor` handles the creation, modification, and accessing of labeled tensors. Recall that all primitive data types in a labeled tensor are flattened, e.g., a symmetric fourth order tensor of type `SymSymR4` with batch size `(5)` and base size `(6, 6)` are flattened to have base size `(36)` in the labeled tensor. The doxygen documentation provides a complete list of APIs. The commonly used methods are -- [operator()](@ref neml2::LabeledTensor::operator()()) for retrieving a labeled view into the raw (flattened) data without reshaping -- [get](@ref neml2::LabeledTensor::get) for retrieving a labeled view and reshaping it to the correct shape -- [set](@ref neml2::LabeledTensor::set) for setting values for a labeled view -- [slice](@ref neml2::LabeledTensor::slice) for slicing a sub-axis along a specific base dimension -- [block](@ref neml2::LabeledTensor::block) for sub-indexing the `LabeledTensor` with \f$D\f$ sub-axis names - -## Model definition {#modeldefinition} - -A NEML2 model is a function (in the context of mathematics) -\f[ - f: \mathbb{R}^m \to \mathbb{R}^n -\f] -mapping from the input space \f$\mathbb{R}^m\f$ of dimension \f$m\f$ to the output space \f$\mathbb{R}^n\f$ of dimension \f$n\f$. Recall that \f$\left[ \cdot \right]\f$ is the [flatten-concatenation operator](@ref math). The input vector is the concatenation of \f$p\f$ flattened variables, i.e., -\f[ - x = \left[ x_i \right]_{i=1}^p \in \mathbb{R}^m, \quad \sum_{i=1}^p \lvert x_i \rvert = m, -\f] -where \f$\lvert x \rvert\f$ denotes the modulus of flattened variable \f$x\f$. Similarly, the output vector is the concatenation of \f$q\f$ flattened variables, i.e., -\f[ - y = \left[ y_i \right]_{i=1}^q \in \mathbb{R}^n, \quad \sum_{i=1}^q \lvert y_i \rvert = n. -\f] - -Translating the above mathematical definition into NEML2 is straightforward. -- A model following this definition derives from [Model](@ref neml2::Model). -- [declare_input_variable](@ref neml2::Model::declare_input_variable) declares an input variable \f$x_i\f$ in the input space \f$\mathbb{R}^m\f$. -- [declare_output_variable](@ref neml2::Model::declare_output_variable) declares an output variable \f$y_i\f$ in the output space \f$\mathbb{R}^n\f$. -- [set_value](@ref neml2::Model::set_value) is a method defining the forward operator \f$f\f$. - -Both `declare_input_variable` and `declare_output_variable` are templated on the variable type -- recall that only a variable of the NEML2 primitive tensor type can be registered. Furthermore, both calls return a convenient accessor of type [VariableName](@ref neml2::VariableName) which can be later used to retrieve/modify the labeled view of the input/output vector. - -> Declaration of the variables don't immediately set up the layout of the input/output `LabeledAxis`. The method [setup](@ref neml2::Model::setup) should be explicitly called in order to set up the memory layout of the `LabeledAxis`s. **Note that [setup](@ref neml2::Model::setup) must be called after all the variables have been added, and before the forward operator of the `Model` can be used.** - -## Model composition {#modelcomposition} - -Quoting [Wikipedia](https://en.wikipedia.org/wiki/Function_composition): -> In mathematics, function composition is an operation \f$\circ\f$ that takes two functions \f$f\f$ and \f$g\f$, and produces a function \f$h = g \circ f\f$ such that \f$h(x) = g(f(x))\f$. - -Since NEML2 `Model` is a function (in the context of mathematics) at its core, it should be possible, in theory, to compose different NEML2 `Model`s into a new NEML2 `Model`. The [ComposedModel](@ref neml2::ComposedModel) is precisely for that purpose. - -Similar to the statement "a composed function is a function" in the context of mathematics, in NEML2, the equivalent statement "a ComposedModel is a Model" also holds. In addition, the `ComposedModel` provides four key features to help simplify the composition and reduces computational cost: -- Automatic dependency registration -- Automatic input/output identification -- Automatic dependency resolution -- Automatic chain rule - -### A symbolic example - -To demonstrate the utility of the four key features of `ComposedModel`, let us consider the composition of three functions \f$f\f$, \f$g\f$, and \f$h\f$: -\f{align*} - y_1 &= f(x_1, x_2), \\ - y_2 &= g(y_1, x_3), \\ - y &= h(y_1, y_2, x_4). -\f} - -### Automatic dependency registration - -It is obvious to us that the function \f$h\f$ _depends_ on functions \f$f\f$ and \f$g\f$ because the input of \f$h\f$ depends on the outputs of \f$f\f$ and \f$g\f$. Such dependency is automatically identified and registered while composing a `ComposedModel` in NEML2. This procedure is called "automatic dependency registration". - -In order to identify dependencies among different `Model`s, we keep track of the set of _consumed_ variables, \f$\mathcal{I}_i\f$, and a set of _provided_ variables, \f$\mathcal{O}_i\f$, for each `Model` \f$f_i\f$. When a set of models (functions) are composed together, `Model` \f$f_i\f$ is said to _depend_ on \f$f_j\f$ if and only if \f$\exists x\f$ such that -\f[ - x \in \mathcal{I}_i \wedge x \in \mathcal{O}_j. -\f] - -### Automatic input/output identification - -The only possible composition \f$r\f$ of these three functions is -\f[ - y = r(x_1, x_2, x_3, x_4) := h(f(x_1, x_2), g(f(x_1, x_2), x_3), x_4). -\f] -The input variables of the composed function \f$r\f$ are \f$[x_1, x_2, x_3, x_4]\f$ (or their flattened concatenation), and the output variable of the composed function is simply \f$y\f$. The input/output variables are automatically identified while composing a `ComposedModel` in NEML2. This procedure is referred to as "automatic input/output identification". - -In a `ComposedModel`, a _leaf_ model is a model which does not depend on any other model, and a _root_ model is a model which is not depent upon by any other model. A `ComposedModel` may have multiple leaf models and multiple root models. An input variable is said to be a _root_ input variable if it is not consumed by any other model, i.e. \f$x \in \mathcal{I}_i\f$ is a root input variable if and only if -\f[ - x \notin \mathcal{O}_j, \quad \forall i \neq j. -\f] -Similarly, an output variable is said to be a _leaf_ output variable if it is not provided by any other model, i.e. \f$x \in \mathcal{O}_i\f$ is a leaf output variable if an only if -\f[ - x \notin \mathcal{I}_j, \quad \forall i \neq j. -\f] -The input variables of a `ComposedModel` is the union of the set of all the root input variables, and the output variables of a `ComposedModel` is the set of all the leaf output variables. - -### Automatic dependency resolution - -To evaluate the forward operator of the composed model \f$r\f$, one has to first evaluate model \f$f\f$, then model \f$g\f$, and finally model \f$h\f$. The process of sorting out such evaluation order is called "dependency resolution". - -While it is possible to sort the evaluation order "by hand" for this simple example composition, it is generally not a trivial task for practical compositions with more involved dependencies. To that end, NEML2 uses [topological sort](https://en.wikipedia.org/wiki/Topological_sorting) to sort the model evaluation order, such that by the time each model is evaluated, all of its dependent models have already been evaluated. - -### Automatic chain rule - -Chain rule can be applied to evaluate the derivative of the forward operator with respect to the input variables, i.e., -\f{align*} - \frac{\partial y}{\partial x_1} &= \left( \frac{\partial y}{\partial y_1} + \frac{\partial y}{\partial y_2} \frac{\partial y_2}{\partial y_1} \right) \frac{\partial y_1}{\partial x_1}, \\ - \frac{\partial y}{\partial x_2} &= \left( \frac{\partial y}{\partial y_1} + \frac{\partial y}{\partial y_2} \frac{\partial y_2}{\partial y_1} \right) \frac{\partial y_1}{\partial x_2}, \\ - \frac{\partial y}{\partial x_3} &= \frac{\partial y}{\partial y_2} \frac{\partial y_2}{\partial x_3}, \\ - \frac{\partial y}{\partial x_4} &= \frac{\partial y}{\partial x_4}. -\f} -Spelling out this chain rule can be cumbersome and error-prone, especially for more complicated model compositions. The evaluation of the chain rule is automated in NEML2, and the user is only responsible for implementing the partial derivatives of each model. For example, in the implementation of `Model` \f$f\f$, the user only need to define the partial derivatives -\f[ - \frac{\partial y_1}{\partial x_1}, \quad \frac{\partial y_1}{\partial x_2}; -\f] -similarly, `Model` \f$g\f$ only defines -\f[ - \frac{\partial y_2}{\partial y_1}, \quad \frac{\partial y_2}{\partial x_3} -\f] -and `Model` \f$h\f$ only defines -\f[ - \frac{\partial y}{\partial y_1}, \quad \frac{\partial y}{\partial y_2}, \quad \frac{\partial y}{\partial x_4}. -\f] -The assembly of the partial derivatives into the total derivative \f$\partial y / \partial \mathbf{x}\f$ using the chain rule is handled by NEML2. This design serves as the fundation for a modular model implementation: -- Each model _does not_ need to know its composition with others. -- The same model partial derivatives can be reused in _any_ composition. diff --git a/doc/content/_04_devel.md b/doc/content/_04_devel.md deleted file mode 100644 index 297aef0db3..0000000000 --- a/doc/content/_04_devel.md +++ /dev/null @@ -1,278 +0,0 @@ -# Model Development {#devel} - -[TOC] - -This tutorial serves as a developer-facing step-by-step guide for creating, testing, and using a constitutive model. A simple small deformation \f$J_2\f$ viscoplasticity model with isotropic hardening is used as the example in this tutorial. For convenience, the mathematical equations for the constitutive model are summarized below -\f{align*} - \mathbf{M} &= \mathbf{\sigma}, \\ - \bar{\sigma} &= \frac{3}{2} \lVert \operatorname{dev}(\mathbf{M}) \rVert, \\ - k &= H \bar{\varepsilon}^p, \\ - f^p &= \bar{\sigma} - \sigma_y - k, \\ - \mathbf{N}_M &= \partial f^p / \partial \mathbf{M}, \\ - N_k &= \partial f^p / \partial k, \\ - \dot{\gamma} &= \left( \frac{\abs{f^p}}{\eta} \right)^n \operatorname{heav}\left( f^p \right), \\ - \dot{\mathbf{\varepsilon}}^p &= \dot{\gamma} \mathbf{N}_M, \\ - \dot{\bar{\varepsilon}}^p &= \dot{\gamma} N_k, \\ - \dot{\mathbf{\varepsilon}} &= \frac{\mathbf{\varepsilon} - \mathbf{\varepsilon}_n}{t - t_n}, \\ - \dot{\mathbf{\varepsilon}}^e &= \dot{\mathbf{\varepsilon}} - \dot{\mathbf{\varepsilon}}^p, \\ - \dot{\mathbf{\sigma}} &= K \operatorname{vol}\left( \dot{\mathbf{\varepsilon}}^e \right) + G\operatorname{dev}\left( \dot{\mathbf{\varepsilon}}^e \right), \\ - \mathbf{r} &= - \begin{Bmatrix} - \mathbf{\sigma} - \mathbf{\sigma}_n - \dot{\mathbf{\sigma}} (t - t_n) \\ - \bar{\varepsilon}^p - \bar{\varepsilon}^p_n - \dot{\bar{\varepsilon}}^p (t - t_n) - \end{Bmatrix}, \\ - \left( \mathbf{\sigma}, \bar{\varepsilon}^p \right) &= \operatorname{sol}\left( \mathbf{r} = \mathbf{0} \right). -\f} -where \f$\mathbf{\sigma}\f$ is the Cauchy stress, \f$\mathbf{M}\f$ is the Mandel stress, \f$\bar{\sigma}\f$ is the effective stress, \f$\bar{\varepsilon}^p\f$ is the equivalent plastic strain, \f$k\f$ is the isotropic hardening, \f$f^p\f$ is the plastic yield function, \f$\mathbf{N}_M\f$ is the plastic flow direction, \f$N_k\f$ is the isotropic hardening direction, \f$\dot{\gamma}\f$ is the plastic flow rate, \f$\mathbf{\varepsilon}^p\f$ is the plastic strain, \f$\mathbf{\varepsilon}\f$ is the strain, \f$t\f$ is the time, and \f$\mathbf{\varepsilon}^e\f$ is the elastic strain. - -Each of the equation above is a function mapping some input variables to some output variables, hence can be defined as a NEML2 model. The example constitutive model under consideration is the composition of these models. Recall that the input variables of a composed model is the set of root input variables, and the output variables of a composed model is the set of leaf output variables. By inspection, the input variables for this composed model are -\f[ - \mathbf{\varepsilon}, t, \mathbf{\varepsilon}_n, t_n, \mathbf{\sigma}_n, \bar{\varepsilon}^p_n, -\f] -and the output variables for this composed model are -\f[ - \mathbf{\sigma}, \bar{\varepsilon}^p. -\f] - -Finally, a constitutive model is completely defined up to a set of _parameters_. In this example, the parameters of the composed model under consideration are -\f[ - H, \sigma_y, \eta, n, K, G. -\f] - -## Naming conventions - -Recall that NEML2 models operates on _labeled tensors_, and that the collection of labels (with their corresponding layout) is called an labeled axis ([LabeledAxis](@ref neml2::LabeledAxis)). NEML2 predefines 6 sub-axes to categorize all the input, output and intermediate variables: -- State \f$\mathcal{S}\f$: Variables on the "state" sub-axis collectively characterize the current _state_ of the material subject to given external forces. The state variables are usually the output of a physically meaningful constitutive model. -- Forces \f$\mathcal{F}\f$: Variables on the "forces" sub-axis define the _external_ forces that drive the response of the material. -- Old state \f$\mathcal{S}_n\f$: The state variables _prior to_ the current constitutive update. In the time-discrete setting, these are the state variables from the previous time step. -- Old forces \f$\mathcal{F}_n\f$: The external forces _prior to_ the current constitutive update. In the time-discrete setting, these are the forces from the previous time step. -- Residual \f$\mathcal{R}\f$: The residual defines an _implicit_ model/function. An implicit model is updated by solving for the state variables that result in zero residual. -- Trial state \f$\tilde{\mathcal{S}}\f$: The state variables during an implicit update that result in a nonzero residual. - -In NEML2, the following naming conventions are recommended: -- User-facing variables and parameters should be _as descriptive as possible_. For example, the equivalent plastic strain is named "equivalent_plastic_strain". Note that white spaces, quotes, and left slashes are not allowed in the names. Underscores are recommended as an replacement for white spaces. -- Developer-facing variables and parameters should use simple alphanumeric symbols. For example, the equivalent plastic strain is named "ep" in consistency with most of the existing literature. -- Developner-facing member variables and parameters should use the same alphanumeric symbols. For example, the member variable for the equivalent plastic strain is named `ep`. However, if the member variable is protected or private, it is recommended to prefix it with an underscore, i.e. `_ep`. -- Struct names and class names should use `PascalCase`. -- Function names should use `snake_case`. - -## Tutorial 1: Variable declaration and registration - -The development of every model begins with the declaration and registration of its input and output variables. Here, we first define an abstract base class that will be later used to define the linear isotropic hardening relation. The abstract base class defines the isotropic hardening relation -\f[ - k = f\left( \bar{\varepsilon}^p \right), -\f] -mapping the equivalent plastic strain to the isotropic hardening. Recall that NEML2 emphasizes modularity, and so the definition of this model _does not_ concern any other model in the final composition. The base class is named `IsotropicHardening` following the naming convention. The header file [IsotropicHardening.h](@ref neml2::IsotropicHardening) is displayed below -```cpp -#pragma once - -#include "neml2/models/Model.h" - -namespace neml2 -{ -class IsotropicHardening : public Model -{ -public: - static OptionSet expected_options(); - - IsotropicHardening(const OptionSet & options); - - const VariableName equivalent_plastic_strain; - const VariableName isotropic_hardening; -}; -} // namespace neml2 -``` -Since isotropic hardening _is a_ model, the class inherits from `Model`. The user-facing expected parameters are defined by the static method `expected_options`. NEML2 handles the parsing of user parameters and pass them to the constructor. The input variable of the model is the equivalent plastic strain, and the output variable of the model is the isotropic hardening. Their corresponding variable accessors are stored as public member variables `equivalent_plastic_strain` and `isotropic_hardening`, respectively. - -The expected parameters and the constructor are defined as -```cpp -#include "neml2/models/solid_mechanics/IsotropicHardening.h" - -namespace neml2 -{ -OptionSet -IsotropicHardening::expected_options() -{ - OptionSet options = Model::expected_options(); - options.set("equivalent_plastic_strain") = {{"state", "internal", "ep"}}; - options.set("isotropic_hardening") = {{"state", "internal", "k"}}; - return options; -} - -IsotropicHardening::IsotropicHardening(const OptionSet & options) - : Model(options), - equivalent_plastic_strain(declare_input_variable( - options.get("equivalent_plastic_strain"))), - isotropic_hardening( - declare_output_variable(options.get("isotropic_hardening"))) -{ - setup(); -} -} // namespace neml2 -``` -Both the equivalent plastic strain and the isotropic hardening are internal state, denoted as "state/internal/ep" and "state/internal/k", respectively. The `IsotropicHardening` is constructed by extracting user-specified parameters. Note how `declare_input_variable` and `declare_output_variable` are used to declare and register the input and output variables. - -## Tutorial 2: Parameter declaration and registraion - -Now that the abstract base class `IsotropicHardening` has been implemented, we are ready to define our first concrete NEML2 model that describes a linear isotropic hardening relation -\f[ - k = H \bar{\varepsilon}^p. -\f] -Note that \f$H\f$ is a model parameter. Following the naming convention, the concrete class is named `LinearIsotropicHardening`. The header file is displayed below. -```cpp -#pragma once - -#include "neml2/models/solid_mechanics/IsotropicHardening.h" - -namespace neml2 -{ -class LinearIsotropicHardening : public IsotropicHardening -{ -public: - static OptionSet expected_options(); - - LinearIsotropicHardening(const OptionSet & options); - -protected: - /// Simple linear map between equivalent strain and hardening - void set_value(const LabeledVector & in, - LabeledVector * out, - LabeledMatrix * dout_din = nullptr, - LabeledTensor3D * d2out_din2 = nullptr) const override; - - Scalar _K; -}; -} // namespace neml2 -``` -It derives from the abstract base class `IsotropicHardening` and implements the virtual method `set_value` as the forward operator. The model parameter \f$H\f$ is stored as a protected member variable `_K`. The model implementation is shown below. -```cpp -#include "neml2/models/solid_mechanics/LinearIsotropicHardening.h" - -namespace neml2 -{ -register_NEML2_object(LinearIsotropicHardening); - -OptionSet -LinearIsotropicHardening::expected_options() -{ - OptionSet options = IsotropicHardening::expected_options(); - options.set>("hardening_modulus"); - return options; -} - -LinearIsotropicHardening::LinearIsotropicHardening(const OptionSet & options) - : IsotropicHardening(options), - _K(declare_parameter("K", "hardening_modulus")) -{ -} - -void -LinearIsotropicHardening::set_value(const LabeledVector & in, - LabeledVector * out, - LabeledMatrix * dout_din, - LabeledTensor3D * d2out_din2) const -{ - if (out) - out->set(_K * in(equivalent_plastic_strain), isotropic_hardening); - - if (dout_din) - dout_din->set(_K, isotropic_hardening, equivalent_plastic_strain); - - if (d2out_din2) - { - // zero - } -} -} // namespace neml2 -``` -Note that an additional option named "hardening_modulus" is requested from the user. The model parameter is registered using the API `declare_parameter`. In the `set_value` method, the current value of the input variable equivalent plastic strain is queried by -```cpp -in(equivalent_plastic_strain) -``` -and the isotropic hardening is computed as -```cpp -_K * in(equivalent_plastic_strain) -``` -The computed result is copied into the model output `out` by -```cpp -out->set(_K * in(equivalent_plastic_strain), isotropic_hardening); -``` -In addition, the first derivative of the forward operator is defined as -```cpp -dout_din->set(_K, isotropic_hardening, equivalent_plastic_strain); -``` -Finally, the model is registed in the NEML2 model factory using the macro -```cpp -register_NEML2_object(LinearIsotropicHardening); -``` - -## Tutorial 3: Testing - -It is of paramount importance to ensure the correctness of the implementation. NEML2 offers 5 types of tests with different purposes: - -### Catch2 test - -A Catch2 test refers to a test directly written in C++ source code within the Catch2 framework. It offers the highest level of flexibility, but requires more effort to set up. To understand how a Catch2 test works, please refer to the [official Catch2 documentation](https://github.com/catchorg/Catch2/blob/v2.x/docs/tutorial.md). - -### Model unit test - -A model unit test examines the outputs of a `Model` given a predefined set of inputs. Model unit tests can be directly designed using the input file syntax with the `ModelUnitTest` type. A variety of checks can be turned on and off. To list a few: `check_first_derivatives` compares the implemented first order derivatives of the model against finite-differencing results, and passes only if the two derivatives are within tolerances specified with `derivative_abs_tol` and `derivative_rel_tol`; `check_cuda` repeats all checks twice: once on CPU and once on GPU (if available), and passes only if the two evaluations yield same results within tolerances. - -All input files for model unit tests should be stored inside `tests/unit/models`. Every input file with the `.i` extension will be automatically discovered and executed. To run all the model unit tests, use the following commands -``` -cd tests -./unit_tests models -``` - -To run a specific model unit test, use the `-c` command line option followed by the relative location of the input file, i.e. -``` -cd tests -./unit_tests models -c solid_mechanics/LinearIsotropicElasticity.i -``` - -### Model regression test - -A model regression test runs a `Model` using a user specified driver. The results are compared against a predefined reference (stored on the disk inside the repository). The test passes only if the current results are the same as the predefined reference (again within specified tolerances). The regression tests ensure the consistency of implementations across commits. Currently, `TransientRegression` is the only supported type of regression test. - -Each input file for model regression tests should be stored inside a separate folder inside `tests/regression`. Every input file with the `.i` extension will be automatically discovered and executed. To run all the model regression tests, use the `regression_tests` executable followed by the physics module, i.e. -``` -cd tests -./regression_tests "solid mechanics" -``` -To run a specific model regression test, use the `-c` command line option followed by the relative location of the input file, i.e. -``` -cd tests -./regression_tests "solid mechanics" -c viscoplasticity/chaboche/model.i -``` -Note that the regression test expectes a option `reference` which specifies the relative location to the reference solution. - -### Model verification test - -The model verification test is similar to the model regression test in terms of workflow. The difference is the a verification test defines the reference solution using NEML, the predecessor of NEML2. Since NEML was developed with strict software assurance, the verification tests ensure that the migration from NEML to NEML2 is as smooth as it can be. - -Each input file for model verification tests should be stored inside a separate folder inside `tests/verification`. Every input file with the `.i` extension will be automatically discovered and executed. To run all the model verification tests, use the `verification_tests` executable followed by the physics module, i.e. -``` -cd tests -./verification_tests "solid mechanics" -``` - -To run a specific model verification test, use the `-c` command line option followed by the relative location of the input file, i.e. -``` -cd tests -./verification_tests "solid mechanics" -c chaboche/chaboche.i -``` -The regression test compares variables (specified using the `variables` option) against reference values (specified using the `references` option). The reference variables can be read using input objects with type `VTestTimeSeries`. - -### Benchmarking - -The benchmark tests can be authored within the [Catch2 microbenchmarking framework](https://github.com/catchorg/Catch2/blob/v2.x/docs/benchmarks.md). Before any benchmarks can be executed, the clock's resolution is estimated. A few other environmental artifacts are also estimated at this point, like the cost of calling the clock function, but they almost never have any impact in the results. The user code is executed a few times to obtain an estimate of the amount of runs that should be in each sample. This also has the potential effect of bringing relevant code and data into the caches before the actual measurement starts. Finally, all the samples are collected sequentially by performing the number of runs estimated in the previous step for each sample. - -To run a benchmark test, use the `benchmark.sh` script inside the `scripts` directory with 3 positional arguments: -``` -./scripts/benchmark.sh Chaboche 5 timings -``` -The first positional argument specifies the name of the benchmark test to run. The second positional argument specifies the number of samples to repeat in each iteration. The third positional argument specifies the output directory of the benchmark results. - -The Chaboche benchmark test is repeated with different batch sizes and on different devices (in this case CPU and GPU). The final benchmark results are summarized in the following figure. - -![Chaboche benchmark results](@ref timings.png){html: width=50%, latex: width=10cm} diff --git a/doc/content/dev.md b/doc/content/dev.md new file mode 100644 index 0000000000..ac900842f8 --- /dev/null +++ b/doc/content/dev.md @@ -0,0 +1,231 @@ +# Developer Guide {#dev} + +[TOC] + +## Writing a custom model {#custom-model} + +The following tutorials serve as a developer-facing step-by-step guide for creating and testing a custom material model. A simple linear isotropic hardening is used as the example in this tutorial. The model can be mathematically written as +\f{align*} + k &= H \bar{\varepsilon}^p, +\f} +where \f$\bar{\varepsilon}^p\f$ is the equivalent plastic strain and \f$k\f$ is the isotropic hardening. The input variable for this model is +\f$\boldsymbol{\varepsilon}\f$, the output variable for this model is \f$k\f$, and the parameters of the model is \f$H\f$. + +### Naming conventions {#naming-conventions} + +Recall that NEML2 models operates on _labeled tensors_, and that the collection of labels (with their corresponding layout) is called an labeled axis ([LabeledAxis](@ref neml2::LabeledAxis)). NEML2 predefines 5 sub-axes to categorize all the input, output and intermediate variables: +- State \f$\mathcal{S}\f$: Variables on the "state" sub-axis collectively characterize the current _state_ of the material subject to given external forces. The state variables are usually the output of a physically meaningful material model. +- Forces \f$\mathcal{F}\f$: Variables on the "forces" sub-axis define the _external_ forces that drive the response of the material. +- Old state \f$\mathcal{S}_n\f$: The state variables _prior to_ the current material update. In the time-discrete setting, these are the state variables from the previous time step. +- Old forces \f$\mathcal{F}_n\f$: The external forces _prior to_ the current material update. In the time-discrete setting, these are the forces from the previous time step. +- Residual \f$\mathcal{R}\f$: The residual defines an _implicit_ model/function. An implicit model is updated by solving for the state variables that result in zero residual. + +In NEML2, the following naming conventions are recommended: +- User-facing variables and option names should be _as descriptive as possible_. For example, the equivalent plastic strain is named "equivalent_plastic_strain". Note that white spaces, quotes, and left slashes are not allowed in the names. Underscores are recommended as an replacement for white spaces. +- Developer-facing variables and option names should use simple alphanumeric symbols. For example, the equivalent plastic strain is named "ep" in consistency with most of the existing literature. +- Developner-facing member variables and option names should use the same alphanumeric symbols. For example, the member variable for the equivalent plastic strain is named `ep`. However, if the member variable is protected or private, it is recommended to prefix it with an underscore, i.e. `_ep`. +- Struct names and class names should use `PascalCase`. +- Function names should use `snake_case`. + +### Declaring variables {#declaring-variables} + +The development of every model begins with the declaration and registration of its input and output variables. Here, we first define an abstract base class that will be later used to define the linear isotropic hardening relation. The abstract base class defines the isotropic hardening relation +\f[ + k = f\left( \bar{\varepsilon}^p \right), +\f] +mapping the equivalent plastic strain to the isotropic hardening. The base class is named `IsotropicHardening` following the [naming conventions](@ref naming-conventions). The header file [IsotropicHardening.h](@ref neml2::IsotropicHardening) is displayed below +```cpp +#pragma once + +#include "neml2/models/Model.h" + +namespace neml2 +{ +class IsotropicHardening : public Model +{ +public: + static OptionSet expected_options(); + + IsotropicHardening(const OptionSet & options); + +protected: + /// Equivalent plastic strain + const Variable & _ep; + + /// Isotropic hardening + Variable & _h; +}; +} // namespace neml2 +``` +Since isotropic hardening _is a_ model, the class inherits from `Model`. The user-facing expected options are defined by the static method `expected_options`. NEML2 handles the parsing of user-specified options and pass them to the constructor (see [Input file syntax](@ref input-file-syntax) on how the input file works). The input variable of the model is the equivalent plastic strain, and the output variable of the model is the isotropic hardening. Their corresponding variable value references are stored as `_ep` and `_h`, respectively, again following the [naming conventions](@ref naming-conventions). + +The expected options and the constructor are defined as +```cpp +#include "neml2/models/solid_mechanics/IsotropicHardening.h" + +namespace neml2 +{ +OptionSet +IsotropicHardening::expected_options() +{ + OptionSet options = Model::expected_options(); + options.set("equivalent_plastic_strain") = VariableName("state", "internal", "ep"); + options.set("isotropic_hardening") = VariableName("state", "internal", "k"); + return options; +} + +IsotropicHardening::IsotropicHardening(const OptionSet & options) + : Model(options), + _ep(declare_input_variable("equivalent_plastic_strain")), + _h(declare_output_variable("isotropic_hardening")) +{ +} +} // namespace neml2 +``` +Recall that variable names on `LabeledAxis` are always fully qualified, the equivalent plastic strain and the isotropic hardening are denoted as "state/internal/ep" and "state/internal/k", respectively. An instance of the class is constructed by extracting user-specified options (of type `OptionSet`). Note how `declare_input_variable` and `declare_output_variable` are used to declare and register the input and output variables. + +### Declaring parameters {#declaring-parameters} + +Now that the abstract base class `IsotropicHardening` has been implemented, we are ready to define our first concrete NEML2 model that describes a linear isotropic hardening relation +\f[ + k = H \bar{\varepsilon}^p. +\f] +Note that \f$H\f$ is a model parameter. Following the naming convention, the concrete class is named `LinearIsotropicHardening`. The header file is displayed below. +```cpp +#pragma once + +#include "neml2/models/solid_mechanics/IsotropicHardening.h" + +namespace neml2 +{ +/** + * @brief Simple linear map between equivalent strain and hardening + * + */ +class LinearIsotropicHardening : public IsotropicHardening +{ +public: + static OptionSet expected_options(); + + LinearIsotropicHardening(const OptionSet & options); + +protected: + void set_value(bool out, bool dout_din, bool d2out_din2) override; + + const Scalar & _K; +}; +} // namespace neml2 +``` +It derives from the abstract base class `IsotropicHardening` and implements the method `set_value` as the forward operator. The model parameter \f$H\f$ is stored as a protected member variable `_K`. The model implementation is shown below. +```cpp +#include "neml2/models/solid_mechanics/LinearIsotropicHardening.h" + +namespace neml2 +{ +register_NEML2_object(LinearIsotropicHardening); + +OptionSet +LinearIsotropicHardening::expected_options() +{ + OptionSet options = IsotropicHardening::expected_options(); + options.set>("hardening_modulus"); + return options; +} + +LinearIsotropicHardening::LinearIsotropicHardening(const OptionSet & options) + : IsotropicHardening(options), + _K(declare_parameter("K", "hardening_modulus")) +{ +} + +void +LinearIsotropicHardening::set_value(bool out, bool dout_din, bool d2out_din2) +{ + if (out) + _h = _K * _ep; + + if (dout_din) + _h.d(_ep) = _K; + + if (d2out_din2) + { + // zero + } +} +} // namespace neml2 +``` +Note that an additional option named "hardening_modulus" is requested from the user. The model parameter is registered using the API `declare_parameter`. In the `set_value` method, the current value of the input variable, equivalent plastic strain, is stored in the member `_ep`, and so the isotropic hardening can be computed as +```cpp +_K * _ep +``` +The computed result is copied into the model output variable `_h` by +```cpp +_h = _K * _ep; +``` +In addition, the first derivative of the forward operator is defined as +```cpp +_h.d(_ep) = _K; +``` +Last but not the least, the model is registed in the NEML2 model factory using the macro +```cpp +register_NEML2_object(LinearIsotropicHardening); +``` +so that an instance of the class can be created at runtime. + +## Testing {#dev-testing} + +It is of paramount importance to ensure the correctness of the implementation. NEML2 offers 5 types of tests with different purposes. + +### Catch tests {#catch-tests} + +A Catch test refers to a test directly written in C++ source code within the Catch2 framework. It offers the highest level of flexibility, but requires more effort to set up. To understand how a Catch2 test works, please refer to the [official Catch2 documentation](https://github.com/catchorg/Catch2/blob/v2.x/docs/tutorial.md). + +### Unit tests {#unit-tests} + +A model unit test examines the outputs of a `Model` given a predefined set of inputs. Model unit tests can be directly designed using the input file syntax with the `ModelUnitTest` type. A variety of checks can be turned on and off based on input file options. To list a few: `check_first_derivatives` compares the implemented first order derivatives of the model against finite-differencing results, and the test is marked as passing only if the two derivatives are within tolerances specified with `derivative_abs_tol` and `derivative_rel_tol`; if `check_cuda` is set to `true`, all checks are repeated twice, once on CPU and once on GPU (if available), and pass only if the two evaluations yield same results within tolerances. + +All input files for model unit tests should be stored inside `tests/unit/models`. Every input file with the `.i` extension will be automatically discovered and executed. To run all the model unit tests, use the following commands +``` +cd tests +../build/unit/unit_tests models +``` + +To run a specific model unit test, use the `-c` command line option followed by the relative location of the input file, i.e. +``` +cd tests +../build/unit/unit_tests models -c solid_mechanics/LinearIsotropicElasticity.i +``` + +### Regression tests {#regression-tests} + +A model regression test runs a `Model` using a user specified driver. The results are compared against a predefined reference (stored on the disk checked into the repository). The test passes only if the current results are the same as the predefined reference (again within specified tolerances). The regression tests ensure the consistency of implementations across commits. Currently, `TransientRegression` is the only supported type of regression test. + +Each input file for model regression tests should be stored inside a separate folder inside `tests/regression`. Every input file with the `.i` extension will be automatically discovered and executed. To run all the model regression tests, use the `regression_tests` executable followed by the physics module, i.e. +``` +cd tests +../build/regression/regression_tests "solid mechanics" +``` +To run a specific model regression test, use the `-c` command line option followed by the relative location of the input file, i.e. +``` +cd tests +../build/regression/regression_tests "solid mechanics" -c viscoplasticity/chaboche/model.i +``` +Note that the regression test expects an option `reference` which specifies the relative location to the reference solution. + +### Verification tests {#verification-tests} + +The model verification test is similar to the model regression test in terms of workflow. The difference is the a verification test defines the reference solution using NEML, the predecessor of NEML2. Since NEML was developed with strict software assurance, the verification tests ensure that the migration from NEML to NEML2 does not cause any regression in software quality. + +Each input file for model verification tests should be stored inside a separate folder inside `tests/verification`. Every input file with the `.i` extension will be automatically discovered and executed. To run all the model verification tests, use the `verification_tests` executable followed by the physics module, i.e. +``` +cd tests +../build/verification/verification_tests "solid mechanics" +``` + +To run a specific model verification test, use the `-c` command line option followed by the relative location of the input file, i.e. +``` +cd tests +../build/verification/verification_tests "solid mechanics" -c chaboche/chaboche.i +``` +The regression test compares variables (specified using the `variables` option) against reference values (specified using the `references` option). The reference variables can be read using input objects with type `VTestTimeSeries`. + diff --git a/doc/content/getting_started.md b/doc/content/getting_started.md new file mode 100644 index 0000000000..7757f92c0c --- /dev/null +++ b/doc/content/getting_started.md @@ -0,0 +1,134 @@ +# Getting Started {#getting-started} + +[TOC] + +## Using NEML2 material models + +The user interface of NEML2 is designed in such a way that no programing experience is required to compose custom material models and define how they are solved. This is achieved using _input files_. The input files are simply text files with a specific format that NEML2 can understand. NEML2 can _deserialize_ an input file, i.e., parse and create material models specified within the input file. + +Since the input files are nothing more than text files saved on the disk, they can be used in any application that supports standard IO, easily exchanged among different devices running different operating systems, and archived for future reference. + +### Input file syntax {#input-file-syntax} + +Input files use the Hierarchical Input Text (HIT) format. The syntax looks like this: +```python +# Comments look like this +[block1] + # This is a comment + foo = 1 + bar = 3.14159 + baz = 'string value' + [nested_block] + # ... + [] +[] +``` +where key-value pairs are defined under (nested) blocks denoted by square brackets. A value can be an integer, floating-point number, string, or array (as indicated by single quotes). Note that the block indentation is recommended for clarity but is not required. + +All NEML2 capabilities that can be defined through the input file fall under a number of _systems_. Names of the top-level blocks specify the systems. For example, the following input file +```python +[Tensors] + [E] + # ... + [] +[] + +[Models] + [elasticity] + type = LinearIsotropicElasticity + youngs_modulus = 'E' + poisson_ratio = 0.3 + [] +[] +``` +defines a tensor named "E" under the `[Tensors]` block and a model named "elasticity" under the `[Models]` block. The [Syntax Documentation](@ref syntax-tensors) provides a complete list of objects that can be defined by an input file. The [System Documentation](@ref system-tensors) provides detailed explanation of each system. + +### Special syntax + +**Boolean**: Oftentimes the behavior of the object is preferrably controlled by a boolean flag. However, since the HIT format only allows (array of) integer, floating-point number, and string, a special syntax shall be reserved for boolean values. In NEML2 input files, a string with value "true" can be parsed into a boolean `true`, and a string with value "false" can be parsed into a boolean `false`. + +> On the other hand, other commonly used boolean flags such as "on"/"off", "1"/"0", "True"/"False", etc., cannot be parsed into boolean values. Trying to do so will trigger a `ParserException`. + +**Variable name**: NEML2 material models work with named variables to assign physical meanings to different slices of a tensor (see e.g. [Tensor Labeling](@ref tensor-labeling)). A fully qualified variable name can be parsed from a string, and the delimiter "/" signifies nested sub-axes. For example, the string "forces/t" can be parsed into a variable named "t" defined on the sub-axis named "forces". + +**Tensor shape**: Shape of a tensor can also be parsed from a string. The string must start with "(" and end with ")". An array of comma-separated integers must be enclosed by the parentheses. For example, the string "(5,6,7)" can be parsed into a shape tuple of value `(5, 6, 7)`. Note that white spaces are not allowed between the parentheses and could lead to undefined behavior. An empty array, i.e. "()", however, is allowed and fully supported. + +## Using NEML2 as a C++ library + +The following input file defines a linear isotropic elasticity material model: + +```python +[Models] + [model] + type = LinearIsotropicElasticity + youngs_modulus = 100 + poisson_ratio = 0.3 + strain = 'forces/E' + stress = 'state/S' + [] +[] +``` + +The input file defines two parameters: Young's modulus of 100 and Poisson's ratio of 0.3. While optional, the input file also sets the variable names of strain and stress to be "forces/E" and "state/S", respectively (refer to the documentation on [tensor labeling](@ref tensor-labeling) for variable naming conventions). + +Assuming the above input file is named "input_file.i", the C++ code snippet below parses the input file and loads the material model (into the heap). + +```cpp +#include "neml2/base/Parser.h" +#include "neml2/base/Factory.h" +#include "neml2/models/Model.h" +#include "neml2/tensors/tensors.h" + +using namespace neml2; + +int main() { + load_model("input.i"); + auto & model = Factory::get_object("Models", "model"); + + // ... + + return 0; +} +``` + +Suppose we want to perform 3 material updates simultaneously, the model should be initialized using the neml2::Model::reinit method with the correct batch shape (refer to the [tensor system documentation](@ref system-tensors) for more detailed explanation on the term "batch"): + +```cpp + model.reinit({3}); +``` + +Finally, the following code constructs the 3 input strains `in` and performs 3 material updates _simultaneously_. Output stresses are stored in the tensor `out`. + +```cpp + auto in = LabeledVector::empty({3}, {model.input_axis()}); + + in.batch_index_put({0}, SR2::fill(0.1, 0.2, 0.3, -0.1, -0.1, 0.2)); + in.batch_index_put({1}, SR2::fill(0.2, 0.2, 0.1, -0.1, -0.2, -0.5)); + in.batch_index_put({2}, SR2::fill(0.3, -0.2, 0.05, -0.1, -0.3, 0.1)); + + auto out = model.value(in); +``` + +## Using the NEML2 Python package (experimental) + +With the NEML2 Python package, the above input file can be directly used in a Python script. The Python APIs closely ressembles the C++ APIs. For example, the above C++ source code translates to the following Python script. + +```python +import torch +import neml2 +from neml2.tensors import SR2, LabeledVector + +model = neml2.load_model("input.i", "model") + +model.reinit(3) + +x = LabeledVector.empty(3, [model.input_axis()]) + +x.batch_index_put(0, SR2.fill([0.1, 0.2, 0.3, -0.1, -0.1, 0.2])) +x.batch_index_put(1, SR2.fill([0.2, 0.2, 0.1, -0.1, -0.2, -0.5])) +x.batch_index_put(2, SR2.fill([0.3, -0.2, 0.05, -0.1, -0.3, 0.1])) + +y = model.value(x) +``` + +As is the same in the equivalent C++ source code, the above Python script parses the input file named "input.i", loads the linear elasticity model named "model", constructs 3 strain tensors, and finally performs the 3 material updates simultaneously. diff --git a/doc/content/install.md b/doc/content/install.md new file mode 100644 index 0000000000..017fb73716 --- /dev/null +++ b/doc/content/install.md @@ -0,0 +1,171 @@ +# Installation Guide {#install} + +[TOC] + +## Prerequisites + +Compiling the NEML2 core library requires +- A C++ compiler with C++17 support +- CMake >= 3.23 + +## Dependencies + +### Required dependencies + +- [PyTorch](https://pytorch.org/get-started/locally/), version 2.2.2. + +Other PyTorch releases with a few minor versions around are likely to be compatible. In the PyTorch official download page, several download options are provided: conda, pip, libTorch, and source distribution. +- **Recommended**: If you choose to download PyTorch using conda or pip, the NEML2 CMake script can automatically detect and use the PyTorch installation. +- If you choose to download libTorch or build PyTorch from source, you will need to set `LIBTORCH_DIR` to be the location of libTorch when using CMake to configure NEML2. + +If no PyTorch installation can be detected and `LIBTORCH_DIR` is not set at configure time, the NEML2 CMake script will automatically download and use the libTorch obtained from the official website. Note, however, that this method only works on Linux and Mac systems. + +> The libTorch distributions from the official website come with two flavors: "Pre-cxx11 ABI" and "cxx11 ABI". Both variants are supported by NEML2. If you are unsure, we recommend the one with "cxx11 ABI". + +### Optional dependencies + +*No action is needed to manually obtain the optional dependencies.* The compatible optional dependencies will be automatically downloaded and configured by CMake depending on the build customization. + +- [HIT](https://github.com/idaholab/moose/tree/master/framework/contrib/hit) for input file parsing. +- [Catch2](https://github.com/catchorg/Catch2) for unit and regression testing. +- [gperftools](https://github.com/gperftools/gperftools) for profiling. +- [Doxygen](https://github.com/doxygen/doxygen) for building the documentation. +- [Doxygen Awesome](https://github.com/jothepro/doxygen-awesome-css) the documentation theme. +- [argparse](https://github.com/p-ranav/argparse) for command-line argument parsing. +- [pybind11](https://github.com/pybind/pybind11) for building Python bindings. +- Python packages + - pytest + - pandas + - matplotlib + - PyYAML + +## Build and install + +### C++ backend + +First, obtain the NEML2 source code. + +``` +git clone https://github.com/reverendbedford/neml2.git +cd neml2 +git checkout main +``` + +Then, configure NEML2. See [build customization](#build-customization) for possible configuration options. + +``` +cmake -B build . +``` + +Finally, compile NEML2. + +``` +cmake --build build -j N +``` +where `N` is the number of cores to use for parallel compilation. + +The compiled NEML2 can be installed as a system library. + +``` +cmake --install build +``` + +For more fine-grained control over the configure, build, and install commands, please refer to the [CMake documentation](https://cmake.org/cmake/help/latest/manual/cmake.1.html). + + +### Python package + +NEML2 also provides an _experimental_ Python package which provides bindings for the primitive tensors and parsers for deserializing and running material models. Package source distributions are available on PyPI, but package wheels are currently not built and uploaded to PyPI. + +To install the NEML2 Python package, run the following command at the repository's root. + +``` +pip install -v . +``` + +The command installs a package named `%neml2` to the site-packages directory, and so it can be imported in Python scripts using + +```python +import neml2 +``` + +For security reasons, static analysis tools and IDEs for Python usually refuse to extract function signature, type hints, etc. from bindary extensions such as the NEML2 Python bindings. As a workaround, "stubs" can be generated a priori to make them less opaque. The NEML2 python package works well with `pybind11-stubgen` for that purpose. Stubs can be generated using the following command: + +``` +pip install pybind11-stubgen +pybind11-stubgen neml2 +``` + +Refer to the [pybind11-stubgen documentation](https://pypi.org/project/pybind11-stubgen/) for more command-line options. Most static analysis tools and IDEs can understand the stubs and therefore provide the full set of features. + + +## Build customization {#build-customization} + +Additional configuration options can be passed via command line using the `-DOPTION` or `-DOPTION=ON` format. For example, + +``` +cmake -DNEML2_PYBIND=ON -B build . +``` +turns on the `NEML2_PYBIND` option, and additional targets for building the Python bindings will be created. Note that this would also download additional optional dependencies, e.g., pybind11, that are required to build the Python bindings. + +Commonly used configuration options are summarized below. Default options are underlined. + +| Option | Values (default) | Description | +| :----------------------- | :---------------------------------------------------------- | :---------------------------------------------------------------------------------------- | +| CMAKE_BUILD_TYPE | Debug, Release, MinSizeRel, RelWithDebInfo, Coverage | CMake [Reference](https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html) | +| CMAKE_INSTALL_PREFIX | | CMake [Reference](https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html) | +| CMAKE_UNITY_BUILD | | CMake [Reference](https://cmake.org/cmake/help/latest/variable/CMAKE_UNITY_BUILD.html) | +| NEML2_DTYPE | Float16, Float32, Float64 | Default floating point integral type used in the material models | +| NEML2_INT_DTYPE | Int8, Int16, Int32, Int64 | Default fixed point integral type used in the material models | +| NEML2_TESTS | ON, OFF | Master knob for including/excluding all tests | +| NEML2_UNIT | ON, OFF | Create the unit testing target | +| NEML2_REGRESSION | ON, OFF | Create the regression testing target | +| NEML2_VERIFICATION | ON, OFF | Create the verification testing target | +| NEML2_RUNNER | ON, OFF | Create a simple runner | +| NEML2_RUNNER_AS_PROFILER | ON, OFF | Make the runner a profiler by linking against gperftools | +| NEML2_DOC | ON, OFF | Create the documentation target | +| NEML2_PYBIND | ON, OFF | Create the Python bindings target | + +## CMake integration {#cmake-integration} + +Integrating NEML2 into a project that already uses CMake is fairly straightforward. The following CMakeLists.txt snippet links NEML2 into the target executable called `foo`: + +``` +add_subdirectory(neml2) + +add_executable(foo main.cxx) +target_link_libraries(foo neml2) +``` + +The above snippet assumes NEML2 is checked out to the directory %neml2, i.e., as a git submodule. +Alternatively, you may use CMake's `FetchContent` module to integrate NEML2 into your project: + +``` +FetchContent_Declare( + neml2 + GIT_REPOSITORY https://github.com/reverendbedford/neml2.git + GIT_TAG v1.4.0 +) +FetchContent_MakeAvailable(neml2) + +add_executable(foo main.cxx) +target_link_libraries(foo neml2) +``` + +## Testing {#testing} + +### C++ backend + +By default when `NEML2_TESTS` is set to `ON`, three test suites are built under the specified build directory: + +- `tests/unit/unit_tests`: Collection of tests to ensure individual objects are working correctly. +- `tests/regression/regression_tests`: Collection of tests to avoid regression. +- `tests/verification/verification_tests`: Collection of verification problems. + +The tests assume the working directory to be the `tests` directory relative to the repository root. For Visual Studio Code users, the [C++ TestMate](https://github.com/matepek/vscode-catch2-test-adapter) extension can be used to automatically discover and run tests. In the extension settings, the "Working Directory" variable should be modified to `${workspaceFolder}/tests`. + +### Python package + +A collection of tests are available under `python/tests` to ensure the NEML2 Python package is working correctly. For Visual Studio Code users, the [Python](https://github.com/Microsoft/vscode-python) extension can be used to automatically discover and run tests. In the extension settings, the "Pytest Enabled" variable shall be set to true. + +If the Python bindings are built (with `NEML2_PYBIND` set to `ON`) but are not installed to the site-packages directory, pytest will not be able to import the %neml2 package unless the environment variable `PYTHONPATH` is modified according to the specified build directory. For Visual Studio Code users, create a `.env` file in the repository's root and include an entry `PYTHONPATH=build/python` (assuming the build directory is `build`), and the Python extension will be able to import the NEML2 Python package. diff --git a/doc/content/physics/solid_mechanics.md b/doc/content/physics/solid_mechanics.md new file mode 100644 index 0000000000..331eac461e --- /dev/null +++ b/doc/content/physics/solid_mechanics.md @@ -0,0 +1,485 @@ +# Solid Mechanics {#solid-mechanics} + +[TOC] + +The solid mechanics physics module is a collection objects serving as building blocks for composing material models for solids. Each category of the material model is explained below, with both the mathematical formulations and example input files. + +## Elasticity + +Elasticity models describe the relationship between stress \f$ \boldsymbol{\sigma} \f$ and strain \f$ \boldsymbol{\varepsilon} \f$ without any history-dependent (internal) state variables. In general, the stress-strain relation can be written as + +\f[ + \boldsymbol{\sigma} = \mathbb{C} : \boldsymbol{\varepsilon} +\f] + +where \f$ \mathbb{C} \f$ is the fourth-order elasticity tensor. For linear isotropic elasticity, this relation can be simplified as + +\f[ + \boldsymbol{\sigma} = 3 K \operatorname{vol} \boldsymbol{\varepsilon} + 2 G \operatorname{dev} \boldsymbol{\varepsilon} +\f] + +where \f$ K \f$ is the bulk modulus, and \f$ G \f$ is the shear modulus. + +Below is an example input file that defines a linear elasticity model. + +```python +[Models] + [model] + type = LinearIsotropicElasticity + youngs_modulus = 100 + poisson_ratio = 0.3 + [] +[] +``` + +## Plasticity (macroscale) + +Generally speaking, plasticity models describe (oftentimes irreversible and dissipative) history-dependent deformation of solid materials. The plastic deformation is governed by the plastic loading/unloading conditions (or more generally the Karush-Kuhn-Tucker conditions): + +\f{align*} + f^p \leq 0, \quad \dot{\gamma} \geq 0, \quad \dot{\gamma}f^p = 0, \\ +\f} + +where \f$ f^p \f$ is the yield function, and \f$ \gamma \f$ is the consistency parameter. + +### Consistent plasticity + +Consistent plasticity refers to the family of (macroscale) plasticity models that solve the plastic loading/unloading conditions (or the KKT conditions) exactly (up to machine precision). + +> Consistent plasticity models are sometimes considered rate-independent. But that is a misnomer as rate sensitivity can be baked into the yield function definition in terms of the rates of the internal variables. + +Residual associated with the KKT conditions can be written as the complementarity condition + +\f{align*} + r = + \begin{cases} + \dot{\gamma}, & f^p < 0 \\ + f^p, & f^p \geq 0. + \end{cases} +\f} + +This complementarity condition is implemented by the object `RateIndependentPlasticFlowConstraint`. A complete example input file for consistent plasticity is shown below, and the composition and possible modifications are explained in the following subsections. + +``` +[Models] + [elastic_strain] + type = ElasticStrain + [] + [elasticity] + type = LinearIsotropicElasticity + youngs_modulus = 1e5 + poisson_ratio = 0.3 + [] + [vonmises] + type = SR2Invariant + invariant_type = 'VONMISES' + tensor = 'state/internal/S' + invariant = 'state/internal/s' + [] + [yield_function] + type = YieldFunction + yield_stress = 1000 + [] + [flow] + type = ComposedModel + models = 'vonmises yield_function' + [] + [normality] + type = Normality + model = 'flow' + function = 'state/internal/fp' + from = 'state/internal/S' + to = 'state/internal/NM' + [] + [Eprate] + type = AssociativePlasticFlow + [] + [integrate_Ep] + type = SR2BackwardEulerTimeIntegration + variable = 'internal/Ep' + [] + [consistency] + type = RateIndependentPlasticFlowConstraint + [] + [surface] + type = ComposedModel + models = "elastic_strain elasticity + vonmises yield_function normality Eprate + consistency integrate_Ep" + [] + [return_map] + type = ImplicitUpdate + implicit_model = 'surface' + solver = 'newton' + [] + [model] + type = ComposedModel + models = 'return_map elastic_strain elasticity' + additional_outputs = 'state/internal/Ep' + [] +[] +``` + +### Viscoplasticity + +Viscoplasticity models regularize the KKT conditions by introducing approximations to the constraints. A widely adopted approximation is the Perzyna model where rate sensitivity is baked into the approximation following a power-law relation: + +\f{align*} + \dot{\gamma} = \left( \dfrac{\left< f^p \right>}{\eta} \right)^n, +\f} + +where \f$ \eta \f$ is the reference stress and \f$ n \f$ is the power-law exponent. + +The Perzyna model is implemented by the object `PerzynaPlasticFlowRate`. A complete example input file for viscoplasticity is shown below, and the composition and possible modifications are explained in the following subsections. + +``` +[Models] + [elastic_strain] + type = SR2SumModel + from_var = 'forces/E state/internal/Ep' + to_var = 'state/internal/Ee' + coefficients = '1 -1' + [] + [elasticity] + type = LinearIsotropicElasticity + youngs_modulus = 1e5 + poisson_ratio = 0.3 + [] + [vonmises] + type = SR2Invariant + invariant_type = 'VONMISES' + tensor = 'state/internal/S' + invariant = 'state/internal/s' + [] + [yield_function] + type = YieldFunction + yield_stress = 5 + [] + [flow] + type = ComposedModel + models = 'vonmises yield_function' + [] + [normality] + type = Normality + model = 'flow' + function = 'state/internal/fp' + from = 'state/internal/S' + to = 'state/internal/NM' + [] + [flow_rate] + type = PerzynaPlasticFlowRate + reference_stress = 100 + exponent = 2 + [] + [Eprate] + type = AssociativePlasticFlow + [] + [integrate_Ep] + type = SR2BackwardEulerTimeIntegration + variable = 'internal/Ep' + [] + [implicit_rate] + type = ComposedModel + models = "isoharden elastic_strain elasticity + vonmises yield_function flow_rate + normality Eprate integrate_Ep" + [] + [return_map] + type = ImplicitUpdate + implicit_model = 'implicit_rate' + solver = 'newton' + [] + [model] + type = ComposedModel + models = 'return_map elastic_strain elasticity' + additional_outputs = 'state/internal/Ep' + [] +[] +``` + +### Effective stress + +The effective stress is a measure of stress describing how the plastic deformation "flows". For example, the widely-used \f$ J_2 \f$ plasticity uses the von Mises stress as the stress measure, i.e., + +\f{align*} + \bar{\sigma} &= \sqrt{3 J_2}, \\ + J_2 &= \frac{1}{2} \operatorname{dev} \boldsymbol{\sigma} : \operatorname{dev} \boldsymbol{\sigma}. +\f} + +Commonly used stress measures are defined using `SR2Invariant`. + +```python +[Models] + [vonmises] + type = SR2Invariant + invariant_type = 'VONMISES' + tensor = 'state/internal/S' + invariant = 'state/internal/s' + [] +[] +``` + +### Perfectly Plastic Yield function + +For perfectly plastic materials, the yield function only depends on the effective stress and a constant yield stress, i.e., the envelope does not shrink or expand depending on the loading history. + +\f{align*} + f^p &= \bar{\sigma} - \sigma_y. +\f} + +Below is an example input file defining a perfectly plastic yield function with \f$ J_2 \f$ flow. + +```python +[Models] + [vonmises] + type = SR2Invariant + invariant_type = 'VONMISES' + tensor = 'state/internal/S' + invariant = 'state/internal/s' + [] + [yield_function] + type = YieldFunction + yield_stress = 5 + [] +[] +``` + +### Isotropic hardening + +The equivalent plastic strain \f$ \bar{\varepsilon}^p \f$ is a scalar-valued internal variable that can be introduced to control the shape of the yield function. The isotropic strain hardening \f$ k \f$ is controlled by the accumulated equivalent plastic strain, and enters the yield function as + +\f{align*} + f^p &= \bar{\sigma} - \sigma_y - k(\bar{\varepsilon}^p). +\f} + +Below is an example input file defining a yield function with \f$ J_2 \f$ flow and linear isotropic hardening. + +```python +[Models] + [vonmises] + type = SR2Invariant + invariant_type = 'VONMISES' + tensor = 'state/internal/S' + invariant = 'state/internal/s' + [] + [isoharden] + type = LinearIsotropicHardening + hardening_modulus = 1000 + [] + [yield_function] + type = YieldFunction + yield_stress = 5 + isotropic_hardening = 'state/internal/k' + [] +[] +``` + +### Kinematic hardening + +The kinematic plastic strain \f$ \boldsymbol{K}^p \f$ is a tensor-valued internal variable that can be introduced to control the shape of the yield function. The kinematic hardening \f$ X \f$ is controlled by the accumulated kinematic plastic strain, and the effective stress is defined in terms of the over stress. In case of \f$ J_2 \f$, the effective stress can be rewritten as + +\f{align*} + \bar{\sigma} &= \sqrt{3 J_2}, \\ + J_2 &= \frac{1}{2} \operatorname{dev} \boldsymbol{\Xi} : \operatorname{dev} \boldsymbol{\Xi}, \\ + \boldsymbol{\Xi} &= \boldsymbol{\sigma} - \boldsymbol{X}. +\f} + +Below is an example input file defining a yield function with \f$ J_2 \f$ flow and linear kinematic hardening. + +```python +[Models] + [kinharden] + type = LinearKinematicHardening + hardening_modulus = 1000 + [] + [overstress] + type = SR2SumModel + from_var = 'state/internal/S state/internal/X' + to_var = 'state/internal/O' + coefficients = '1 -1' + [] + [vonmises] + type = SR2Invariant + invariant_type = 'VONMISES' + tensor = 'state/internal/O' + invariant = 'state/internal/s' + [] + [yield_function] + type = YieldFunction + yield_stress = 5 + [] +[] +``` + +### Back stress + +An alternative way of introducing hardening is through back stresses. Instead of modeling the accumulation of kinematic plastic strain, back stress models directly describe the evolution of back stress. An example input file defining a yield function with \f$ J_2 \f$ flow and two back stresses is shown below. + +```python +[Models] + [overstress] + type = SR2SumModel + from_var = 'state/internal/S state/internal/X1 state/internal/X2' + to_var = 'state/internal/O' + coefficients = '1 -1 -1' + [] + [vonmises] + type = SR2Invariant + invariant_type = 'VONMISES' + tensor = 'state/internal/O' + invariant = 'state/internal/s' + [] + [yield_function] + type = YieldFunction + yield_stress = 5 + [] +[] +``` + +### Mixed hardening + +Isotropic hardening, kinematic hardening, and back stresses are all optional and can be "mixed" in the definition of a yield function. The example input file below shows a yield function with \f$ J_2 \f$ flow, isotropic hardening, kinematic hardening, and two back stresses. + +```python +[Models] + [isoharden] + type = LinearIsotropicHardening + hardening_modulus = 1000 + [] + [kinharden] + type = LinearKinematicHardening + hardening_modulus = 1000 + back_stress = 'state/internal/X0' + [] + [overstress] + type = SR2SumModel + from_var = 'state/internal/S state/internal/X0 state/internal/X1 state/internal/X2' + to_var = 'state/internal/O' + coefficients = '1 -1 -1 -1' + [] + [vonmises] + type = SR2Invariant + invariant_type = 'VONMISES' + tensor = 'state/internal/O' + invariant = 'state/internal/s' + [] + [yield_function] + type = YieldFunction + yield_stress = 5 + isotropic_hardening = 'state/internal/k' + [] +[] +``` + +### Flow rules + +Flow rules are required to map from the consistency parameter \f$ \gamma \f$ to various internal variables describing the state of hardening, such as the equivalent plastic strain \f$ \bar{\varepsilon}^p \f$, the kinematic plastic strain \f$ \boldsymbol{K}^p \f$, and the back stress \f$ \boldsymbol{X} \f$. + +Associative flow rules define flow directions variationally according to the principle of maximum dissipation, i.e., + +\f{align*} + \dot{\boldsymbol{\varepsilon}}^p &= \dot{\gamma} \dfrac{\partial f^p}{\partial \boldsymbol{\sigma}}, \\ + \dot{\bar{\varepsilon}}^p &= -\dot{\gamma} \dfrac{\partial f^p}{\partial k}, \\ + \dot{\boldsymbol{K}}^p &= \dot{\gamma} \dfrac{\partial f^p}{\partial \boldsymbol{X}}. +\f} + +The example input file below defines associative \f$ J_2 \f$ flow rules + +```python + [flow] + ... + [] + [normality] + type = Normality + model = 'flow' + function = 'state/internal/fp' + from = 'state/internal/S state/internal/k state/internal/X' + to = 'state/internal/NM state/internal/Nk state/internal/NX' + [] + [eprate] + type = AssociativeIsotropicPlasticHardening + [] + [Kprate] + type = AssociativeKinematicPlasticHardening + [] + [Eprate] + type = AssociativePlasticFlow + [] +[] +``` + +In the above example, a model named "normality" is used to compute the associative flow directions, and the rates of the internal variables are mapped using the rate of the consistency parameter and each of the associative flow direction. The cross-referenced model named "flow" (omitted in the example) is the composition of models defining the yield function \f$ f^p \f$ in terms of the variational arguments \f$ \boldsymbol{\sigma} \f$, \f$ k \f$, and \f$ \boldsymbol{X} \f$. + + +## Crystal plasticity + +NEML2 adopts an incremental rate-form view of crystal plasticity. The fundemental kinematics derive from the rate expansion of the elastic-plastic multiplactive split: + +\f{align*} + F = F^e F^p +\f} + +where the spatial velocity gradient is then + +\f{align*} + l = \dot{F} F^{-1} = \dot{F}^e F^{e-1} + F^e \dot{F}^{p} {F}^{p-1} F^{e-1} +\f} + +The plastic deformation \f$ \bar{l}^p = \dot{F}^{p} {F}^{p-1} \f$ defines the crystal plasticity kinemtics and NEML2 assumes that the elastic stretch is small (\f$ F^e = \left(I + \varepsilon \right) R^e \f$) so that spatial velocity gradient becomes + +\f{align*} + l = \dot{\varepsilon} + \Omega^e - \Omega^e \varepsilon + \varepsilon \Omega^e + l^p + \varepsilon l^p - l^p \varepsilon +\f} + +defining \f$ l^p = R^e \bar{l}^p R^{eT} \f$ as the constitutive plastic velocity gradient rotated into the current configuration and \f$ \Omega^e = \dot{R}^e R^{eT} \f$ as the elastic spin and assuming that + +1. Terms quadratic in the elastic stretch (\f$ \varepsilon\f$) are small. +2. Terms quadratic in the rate of elastic stretch (\f$ \dot{\varepsilon} \f$) are also small. + +The first assumption is accurate for metal plasticity, the second assumption is more questionable if the material deforms at a fast strain rate. + +Define the current orientation of a crystal as the composition of its initial rotation from the crystal system to the lab frame and the elastic rotation, i.e. + +\f{align*} + Q = R^e Q_0 +\f} + +and note with this defintion we can rewrite the spin equation: + +\f{align*} + \Omega^e = \dot{R}^e R^{eT} = \dot{Q} Q_0^T Q_0 Q^T = \dot{Q} Q^T +\f} + +With this definition and the choice of kinematics above we can derive evolution equations for our fundemental constitutive quantities, the elastic stretch \f$ \varepsilon \f$ and the orientation $Q$ by splitting the spatial velocity gradient into symmetric and skew parts and rearranging the resulting equations: + +\f{align*} + \dot{\varepsilon}= d -d^p-\varepsilon w + w \varepsilon \\ + \dot{Q} = \left(w - w^p - \varepsilon d^p + d^p \varepsilon\right) Q . +\f} + +where \f$l = d + w\f$ and \f$l^p = d^p + w^p\f$ + +For most (or all) choices of crystal plasticity constitutive models we also need to define the Cauchy stress as: +\f{align*} + \sigma = C : \varepsilon +\f} + +with \f$ C \f$ a (generally) anisotropic crystal elasticity tensor rotated into the current configuration. + +The crystal plasticity examples in NEML2 integrate the elastic strain (and the constitutive internal variables) using a backward Euler integration rule and integrate the crystal orientation using either an implicit or explicit exponential rule. These integrate rules can either be coupled or decoupled, i.e. integrated together in a fully implicit manner or first integrate the strain and internal variables and then sequentially integrating the rotations. + +A full constitutive model must then define the plastic deformation $l^p$ and whatever internal variables are used in this definition. A wide variety of choices are possible, but the examples use the basic assumption of Asaro: + +\f{align*} + l^p = \sum_{i=1}^{n_{slip}} \dot{\gamma}_i Q \left(d_i \otimes n_i \right) Q^T +\f} + +where now \f$ \dot{\gamma}_i \f$, the slip rate on each system, is the constitutive chioce. NEML provides a variety of options for defining these slip rates in terms of internal hardening variables and the results shear stress + +\f{align*} + \tau_i = \sigma : Q \operatorname{sym}\left(d_i \otimes n_i \right) Q^T +\f} + +Ancillary classes automatically generate lists of slip and twin systems from the crystal sytem, so the user does not need to manually provide these themselves. + +NEML2 uses *modified* Rodrigues parameters to define orientations internally. These can be converted to Euler angles, quaternions, etc. for output. \ No newline at end of file diff --git a/doc/content/system/data.md b/doc/content/system/data.md new file mode 100644 index 0000000000..a4186c7e25 --- /dev/null +++ b/doc/content/system/data.md @@ -0,0 +1,23 @@ +# Data {#system-data} + +[TOC] + +Refer to [Syntax Documentation](@ref syntax-data) for the list of available objects. + +## Buffer + +The term "buffer" is inherited from PyTorch. In a `torch.nn.Module` (which can be thought of the PyTorch equivalent of NEML2's `Model`), a set of _parameters_ and _buffer_ can be declared. Both parameters and buffer are tensors registered to the model. When the model is sent to a different device, i.e., from CPU to GPU, the parameters and buffer registered with the model are sent to the target device. + +The only notable difference between buffer and parameter is that buffer tensors are _NOT_ meant to part of the function graph (while calling the model's forward operator), while parameters are expected to be differentiated. Some examples of buffer, in the context of crystal plasticity, include crystal class, lattice vectors, slip planes, and slip directions of a crystal. + +## Data + +NEML2 provides the Data system to predefine commonly used buffer tensors that are oftentimes shared among material models. Data objects shall inherit from the base class `Data` and can register as many buffer tensors as necessary. + +Objects that are defined as part of the Data system are different from models in several ways: +1. Data objects _do not_ contain parameters. +2. Data objects _do not_ define variables. +3. Data objects _do not_ define forward operator. +4. Data objects _do not_ participate in dependency resolution. + +A model can register a Data object using the `register_data` method which returns a reference to the registered `Data`. diff --git a/doc/content/system/driver.md b/doc/content/system/driver.md new file mode 100644 index 0000000000..c18db5ef78 --- /dev/null +++ b/doc/content/system/driver.md @@ -0,0 +1,9 @@ +# Driver {#system-drivers} + +[TOC] + +Refer to [Syntax Documentation](@ref syntax-drivers) for the list of available objects. + +Drivers are objects that "drive" the update and evolution of one or more material models and their internal data. Especially for non-autonomous material models, a driver is mandatory to evolve the mateiral model. + +Drivers must derive from the base class `Driver` and override the pure virtual method `run` which returns a boolean indicating whether the model execution was successful. diff --git a/doc/content/system/model.md b/doc/content/system/model.md new file mode 100644 index 0000000000..c290a48a97 --- /dev/null +++ b/doc/content/system/model.md @@ -0,0 +1,114 @@ +# Model {#system-models} + +[TOC] + +Refer to [Syntax Documentation](@ref syntax-models) for the list of available objects. + +## Model definition {#model-definition} + +A NEML2 model is a function (in the context of mathematics) +\f[ + f: \mathbb{R}^m \to \mathbb{R}^n +\f] +mapping from the input space \f$\mathbb{R}^m\f$ of dimension \f$m\f$ to the output space \f$\mathbb{R}^n\f$ of dimension \f$n\f$. \f$\left[ \cdot \right]\f$ be the flatten-concatenation operator, the input vector is the concatenation of \f$p\f$ flattened variables, i.e., +\f[ + x = \left[ x_i \right]_{i=1}^p \in \mathbb{R}^m, \quad \sum_{i=1}^p \lvert x_i \rvert = m, +\f] +where \f$\lvert x \rvert\f$ denotes the modulus of flattened variable \f$x\f$. Similarly, the output vector is the concatenation of \f$q\f$ flattened variables, i.e., +\f[ + y = \left[ y_i \right]_{i=1}^q \in \mathbb{R}^n, \quad \sum_{i=1}^q \lvert y_i \rvert = n. +\f] + +Translating the above mathematical definition into NEML2 is straightforward. +- A model following this definition derives from [Model](@ref neml2::Model). +- [declare_input_variable](@ref neml2::Model::declare_input_variable) declares an input variable \f$x_i\f$ in the input space \f$\mathbb{R}^m\f$. +- [declare_output_variable](@ref neml2::Model::declare_output_variable) declares an output variable \f$y_i\f$ in the output space \f$\mathbb{R}^n\f$. +- [set_value](@ref neml2::Model::set_value) is a method defining the forward operator \f$f\f$ itself. + +Both `declare_input_variable` and `declare_output_variable` are templated on the variable type -- recall that only a variable of the NEML2 primitive tensor type can be registered. Furthermore, both methods return a `Variable &` used for retrieving and setting the variable value inside the forward operator, i.e. `set_value`. Note that the reference returned by `declare_input_variable` is writable, while the reference returned by `declare_output_variable` is read-only. + +## Model composition {#model-composition} + +Quoting [Wikipedia](https://en.wikipedia.org/wiki/Function_composition): +> In mathematics, function composition is an operation \f$\circ\f$ that takes two functions \f$f\f$ and \f$g\f$, and produces a function \f$h = g \circ f\f$ such that \f$h(x) = g(f(x))\f$. + +Since NEML2 `Model` is a function (in the context of mathematics) at its core, it should be possible, in theory, to compose different NEML2 `Model`s into a new NEML2 `Model`. The [ComposedModel](@ref neml2::ComposedModel) is precisely for that purpose. + +Similar to the statement "a composed function is a function" in the context of mathematics, in NEML2, the equivalent statement "a `ComposedModel` is a `Model`" also holds. In addition, the `ComposedModel` provides four key features to help simplify the composition and reduces computational cost: +- Automatic dependency registration +- Automatic input/output identification +- Automatic dependency resolution +- Automatic chain rule + +### A symbolic example {#a-symbolic-example} + +To demonstrate the utility of the four key features of `ComposedModel`, let us consider the composition of three functions \f$f\f$, \f$g\f$, and \f$h\f$: +\f{align*} + y_1 &= f(x_1, x_2), \\ + y_2 &= g(y_1, x_3), \\ + y &= h(y_1, y_2, x_4). +\f} + +### Automatic dependency registration {#automatic-dependency-registration} + +It is obvious to us that the function \f$h\f$ _depends_ on functions \f$f\f$ and \f$g\f$ because the input of \f$h\f$ depends on the outputs of \f$f\f$ and \f$g\f$. Such dependency is automatically identified and registered while composing a `ComposedModel` in NEML2. This procedure is called "automatic dependency registration". + +In order to identify dependencies among different `Model`s, we keep track of the set of _consumed_ variables, \f$\mathcal{I}_i\f$, and a set of _provided_ variables, \f$\mathcal{O}_i\f$, for each `Model` \f$f_i\f$. When a set of models (functions) are composed together, `Model` \f$f_i\f$ is said to _depend_ on \f$f_j\f$ if and only if \f$\exists x\f$ such that +\f[ + x \in \mathcal{I}_i \wedge x \in \mathcal{O}_j. +\f] + +### Automatic input/output identification {#automatic-input-output-identification} + +The only possible composition \f$r\f$ of these three functions is +\f[ + y = r(x_1, x_2, x_3, x_4) := h(f(x_1, x_2), g(f(x_1, x_2), x_3), x_4). +\f] +The input variables of the composed function \f$r\f$ are \f$[x_1, x_2, x_3, x_4]\f$ (or their flattened concatenation), and the output variable of the composed function is simply \f$y\f$. The input/output variables are automatically identified while composing a `ComposedModel` in NEML2. This procedure is referred to as "automatic input/output identification". + +In a `ComposedModel`, a _leaf_ model is a model which does not depend on any other model, and a _root_ model is a model which is not depent upon by any other model. A `ComposedModel` may have multiple leaf models and multiple root models. An input variable is said to be a _root_ input variable if it is not consumed by any other model, i.e. \f$x \in \mathcal{I}_i\f$ is a root input variable if and only if +\f[ + x \notin \mathcal{O}_j, \quad \forall i \neq j. +\f] +Similarly, an output variable is said to be a _leaf_ output variable if it is not provided by any other model, i.e. \f$x \in \mathcal{O}_i\f$ is a leaf output variable if an only if +\f[ + x \notin \mathcal{I}_j, \quad \forall i \neq j. +\f] +The input variables of a `ComposedModel` is the union of the set of all the root input variables, and the output variables of a `ComposedModel` is the set of all the leaf output variables. + +### Automatic dependency resolution {#automatic-dependency-resolution} + +To evaluate the forward operator of the composed model \f$r\f$, one has to first evaluate model \f$f\f$, then model \f$g\f$, and finally model \f$h\f$. The process of sorting out such evaluation order is called "dependency resolution". + +While it is possible to sort the evaluation order "by hand" for this simple example composition, it is generally not a trivial task for practical compositions with more involved dependencies. To that end, NEML2 uses [topological sort](https://en.wikipedia.org/wiki/Topological_sorting) to sort the model evaluation order, such that by the time each model is evaluated, all of its dependent models have already been evaluated. + +### Automatic chain rule {#automatic-chain-rule} + +Chain rule can be applied to evaluate the derivative of the forward operator with respect to the input variables, i.e., +\f{align*} + \frac{\partial y}{\partial x_1} &= \left( \frac{\partial y}{\partial y_1} + \frac{\partial y}{\partial y_2} \frac{\partial y_2}{\partial y_1} \right) \frac{\partial y_1}{\partial x_1}, \\ + \frac{\partial y}{\partial x_2} &= \left( \frac{\partial y}{\partial y_1} + \frac{\partial y}{\partial y_2} \frac{\partial y_2}{\partial y_1} \right) \frac{\partial y_1}{\partial x_2}, \\ + \frac{\partial y}{\partial x_3} &= \frac{\partial y}{\partial y_2} \frac{\partial y_2}{\partial x_3}, \\ + \frac{\partial y}{\partial x_4} &= \frac{\partial y}{\partial x_4}. +\f} +Spelling out this chain rule can be cumbersome and error-prone, especially for more complicated model compositions. The evaluation of the chain rule is automated in NEML2, and the user is only responsible for implementing the partial derivatives of each model. For example, in the implementation of `Model` \f$f\f$, the user only need to define the partial derivatives +\f[ + \frac{\partial y_1}{\partial x_1}, \quad \frac{\partial y_1}{\partial x_2}; +\f] +similarly, `Model` \f$g\f$ only defines +\f[ + \frac{\partial y_2}{\partial y_1}, \quad \frac{\partial y_2}{\partial x_3} +\f] +and `Model` \f$h\f$ only defines +\f[ + \frac{\partial y}{\partial y_1}, \quad \frac{\partial y}{\partial y_2}, \quad \frac{\partial y}{\partial x_4}. +\f] +The assembly of the partial derivatives into the total derivative \f$\partial y / \partial \boldsymbol{x}\f$ using the chain rule is handled by NEML2. This design serves as the fundation for a modular model implementation: +- Each model _does not_ need to know its composition with others. +- The same model partial derivatives can be reused in _any_ composition. + +## Automatic differentiation {#automatic-differentiation} + +Deriving and implementing derivatives of the forward operator can be cubersome from times to times. NEML2 offers the option to use automatic differentiation to obtain derivatives. To enable automatic differentiation, simply set the `_use_AD_first_derivative` and/or the `_use_AD_second_derivative` options to `true`. + +Since a composed model uses chain rule to efficiently evaluate the total derivatives, automatic differentiation is disabled for `ComposedModel`. However, each of the child model can still use AD to calculate the derivatives of its own forward operator. Moreover, AD and non-AD models can be composed together. diff --git a/doc/content/system/solver.md b/doc/content/system/solver.md new file mode 100644 index 0000000000..7030fdfc5b --- /dev/null +++ b/doc/content/system/solver.md @@ -0,0 +1,49 @@ +# Solver {#system-solvers} + +[TOC] + +Refer to [Syntax Documentation](@ref syntax-solvers) for the list of available objects. + +Many material models are _implicit_, meaning that the update of the material model is the solution to one or more nonlinear systems of equations. While a model or a composition of models can define such nonlinear system, a solver is required to actually _solve_ the system. + +## Nonlinear solver + +All nonlinear solvers derive from the common base class `NonlinearSolver`. The base class defines 3 public members: `atol` for the absolute tolerance, `rtol` for the relative tolerance, and `miters` for the maximum number of iterations. + +Derived classes must override the method +```cpp +std::tuple solve(NonlinearSystem & system, BatchTensor & sol) +``` +The first argument is the nonlinear system of equations to be solved, and the second argument is the initial guess which will be iteratively updated during the solve. The second argument will hold the solution to the system upon convergence. The first tuple element in the return value is a boolean indicating whether the solve has succeeeded, and the second tuple element is the number of iterations taken before convergence. + +While the convergence criteria are defined by the specific solvers derived from the base class, it is generally recommended to use both `atol` and `rtol` in the convergence check. Below is an example convergence criteria +```cpp +bool +MySolver::converged(const torch::Tensor & nR, const torch::Tensor & nR0) const +{ + return torch::all(torch::logical_or(nR < atol, nR / nR0 < rtol)).item(); +} +``` +where `nR` is the vector norm of the current residual, and `nR0` is the vector norm of the initial residual (evaluated at the initial guess). The above statement makes sure the current residual is either below the absolute tolerance or has been sufficiently reduced, and the condition is applied to _all_ batches of the residual norm. + +## Nonlinear system + +The first argument passed to the `NonlinearSolver::solve` method is of type `NonlinearSystem &`. Since `Model` derives from `NonlinearSystem`, no special action needs to be taken to cast a `Model` into a `NonlinearSystem`. However, the input and output axes of the `Model` must conform with the following requirements in order to be properly recognized as a nonlinear system: +1. The input axis must have a sub-axis named "state". +2. The output axis must have a sub-axis named "residual". +3. The input "state" sub-axis and the output "residual" sub-axis must be conformal, i.e., the variable names on the two axes must have one-to-one correspondence. + +With these requirements, the following rules are implied during the evaluation of a nonlinear system: +1. The input "state" sub-axis is used as the initial guess. The driver is responsible for setting the appropriate initial guess. +2. The output "residual" sub-axis is the residual of the nonlinear system. +3. The derivative sub-block "(residual, state)" is the Jacobian of the nonlinear system. + +Since the input "state" sub-axis and the output "residual" sub-axis are required to be conformal, the Jacobian of the nonlinear system must be square (while not necessarily symmetric). + +## Automatic scaling + +In addition to the default direct (LU) solver, iterative solvers, e.g. CG, GMRES, etc., can be used to solve the linearized system associated with each nonlinear update. It is well known that the effectiveness of iterative solvers is affected by the conditioning of the linear system. + +Since NEML2 supports general Multiphysics material models, conditioning of the system cannot be guaranteed directly by the model's forward operator. Therefore, NEML2 provides a mechanism to (attempt to) scale the nonlinear system and reduce the condition number. + +The automatic scaling algorithm used by NEML2 is algebraic and is based on the initial Jacobian (i.e., the Jacobian of the nonlinear system evaluated at its initial condition and the initial guess). The implementation closely follows section 2.3 in this [report](https://cs.stanford.edu/people/paulliu/files/cs517-project.pdf). Note that the resulting scaling matrices are _not_ necessarily a global minimizer, nor a local minimizer, of the condition number. In fact, the condition number isn't guaranteed to decrease. However, in most of the material models that have been tested, this algorithm can substantially improve the conditioning of the system. diff --git a/doc/content/system/tensor.md b/doc/content/system/tensor.md new file mode 100644 index 0000000000..c8dfd835f8 --- /dev/null +++ b/doc/content/system/tensor.md @@ -0,0 +1,197 @@ +# Tensor {#system-tensors} + +[TOC] + +Refer to [Syntax Documentation](@ref syntax-tensors) for the list of available objects. + +## Tensor types {#tensor-types} + +Currently, PyTorch is the only supported tensor backend in NEML2. Therefore, all tensor types in NEML2 directly inherit from `torch::Tensor`. In the future, support for other tensor backends may be added, but the public-facing interfaces will remain largely the same. + +### Dynamically shaped tensor {#dynamically-shaped-tensor} + +[BatchTensor](@ref neml2::BatchTensor) is a general-purpose *dynamically shaped* tensor type for batched tensors. With a view towards vectorization, the same set of operations can be "simultaneously" applied to a "batch" of (logically the same) tensors. To provide a unified user interface for dealing with such batched operation, NEML2 assumes that the *first* \f$N\f$ dimensions of a tensor are batched dimensions, and the following dimensions are the base (logical) dimensions. + +> Unlike PyTorch, NEML2 explicitly distinguishes between batch dimensions and base (logical) dimensions. + +A `BatchTensor` can be created using +```cpp +BatchTensor A(torch::rand({1, 1, 5, 2}), 2); +``` +where `A` is a tensor with 2 batch dimensions. The batch sizes of `A` is `(1, 1)`: +```cpp +auto batch_sz = A.batch_sizes(); +neml2_assert(batch_sz == {1, 1}); +``` +and the base (logical) sizes of `A` is `(5, 2)`: +```cpp +auto base_sz = A.base_sizes(); +neml2_assert(batch_sz == {5, 2}); +``` + +### Statically shaped tensors {#statically-shaped-tensor} + +[FixedDimTensor](@ref neml2::FixedDimTensor) is the parent class for all the tensor types with a *fixed* base shape. It is templated on the base shape of the tensor. NEML2 offers a rich collection of primitive tensor types inherited from `FixedDimTensor`. Currently implemented primitive tensor types are summarized below + +| Tensor type | Base shape | Description | +| :------------------------------------- | :---------------- | :--------------------------------------------------------------- | +| [Scalar](@ref neml2::Scalar) | \f$()\f$ | Rank-0 tensor, i.e. scalar | +| [Vec](@ref neml2::Vec) | \f$(3)\f$ | Rank-1 tensor, i.e. vector | +| [R2](@ref neml2::R2) | \f$(3,3)\f$ | Rank-2 tensor | +| [SR2](@ref neml2::SR2) | \f$(6)\f$ | Symmetric rank-2 tensor | +| [WR2](@ref neml2::WR2) | \f$(3)\f$ | Skew-symmetric rank-2 tensor | +| [R3](@ref neml2::R3) | \f$(3,3,3)\f$ | Rank-3 tensor | +| [SFR3](@ref neml2::SFR3) | \f$(6,3)\f$ | Rank-3 tensor with symmetry on base dimensions 0 and 1 | +| [R4](@ref neml2::R4) | \f$(3,3,3,3)\f$ | Rank-4 tensor | +| [SSR4](@ref neml2::SSR4) | \f$(6,6)\f$ | Rank-4 tensor with minor symmetry | +| [R5](@ref neml2::R5) | \f$(3,3,3,3,3)\f$ | Rank-5 tensor | +| [SSFR5](@ref neml2::SSFR5) | \f$(6,6,3)\f$ | Rank-5 tensor with minor symmetry on base dimensions 0-3 | +| [Rot](@ref neml2::Rot) | \f$(3)\f$ | Rotation tensor represented in the Rodrigues form | +| [Quaternion](@ref neml2::Quaternion) | \f$(4)\f$ | Quaternion | +| [MillerIndex](@ref neml2::MillerIndex) | \f$(3)\f$ | Crystal direction or lattice plane represented as Miller indices | + +Furthermore, all primitive tensor types can be "registered" as variables on a `LabeledAxis`, which will be discussed in the following section on [labeled view](@ref tensor-labeling). + +## Working with tensors {#working-with-tensors} + +### Tensor creation {#tensor-creation} + +A factory tensor creation function produces a new tensor. All factory functions adhere to the same schema: +```cpp +::(, const torch::TensorOptions & options); +``` +where `` is the class name of the primitive tensor type listed above, and `` is the name of the factory function which produces the new tensor. `` are any required or optional arguments a particular factory function accepts. Refer to each tensor type's class documentation for the concrete signature. The last argument `const torch::TensorOptions & options` configures the data type, device, layout and other "meta" properties of the produced tensor. The commonly used meta properties are +- `dtype`: the data type of the elements stored in the tensor. Available options are `kUInt8`, `kInt8`, `kInt16`, `kInt32`, `kInt64`, `kFloat32`, and `kFloat64`. +- `layout`: the striding of the tensor. Available options are `kStrided` (dense) and `kSparse`. +- `device`: the compute device where the tensor will be allocated. Available options are `kCPU` and `kCUDA`. +- `requires_grad`: whether the tensor is part of a function graph used by automatic differentiation to track functional relationship. Available options are `true` and `false`. + +For example, the following code +```cpp +auto a = SR2::zeros({5, 3}, + torch::TensorOptions() + .device(torch::kCPU) + .layout(torch::kStrided) + .dtype(torch::kFloat32)); +``` +creates a statically (base) shaped, dense, single precision tensor of type `SR2` filled with zeros, with batch shape \f$(5, 3)\f$, allocated on the CPU. + +### Tensor broadcasting {#tensor-broadcasting} + +Quoting Numpy's definition of broadcasting: + +> The term broadcasting describes how NumPy treats arrays with different shapes during arithmetic operations. Subject to certain constraints, the smaller array is “broadcast” across the larger array so that they have compatible shapes. + +NEML2's broadcasting semantics is largely the same as those of Numpy and PyTorch. However, since NEML2 explicitly distinguishes between batch and base dimensions, the broadcasting semantics must also be extended. Two NEML2 tensors are said to be _batch-broadcastable_ if iterating backward from the last batch dimension, one of the following is satisfied: +1. Both tensors have the same size on the dimension; +2. One tensor has size 1 on the dimension; +3. The dimension does not exist in one tensor. + +_Base-broadcastable_ follows a similar definition. Most binary operators on dynamically shaped tensors, i.e., those of type `BatchTensor`, require the operands to be both batch- _and_ base-broadcastable. On the other hand, most binary operators on statically base shaped tensors, i.e., those of pritimitive tensor types, only require the operands to be batch-broadcastable. + +### Tensor indexing {#tensor-indexing} + +In defining the forward operator of a material model, many logically different tensors representing inputs, outputs, residuals, and Jacobians have to be created, copied, and destroyed on the fly. These operations occupy a significant amount of computing time, especially on GPUs. + +To address this challenge, NEML2 creates *views*, instead of copies, of tensors whenever possible. As its name suggests, the view of a tensor is a possibly different interpretation of the underlying data. Quoting the PyTorch documentation: + +> For a tensor to be viewed, the new view size must be compatible with its original size and stride, i.e., each new view dimension must either be a subspace of an original dimension, or only span across original dimensions \f$d, d+1, ..., d+k\f$ that satisfy the following contiguity-like condition that \f$\forall i = d,...,d+k-1\f$, +> \f[ +> \text{stride}[i] = \text{stride}[i+1] \times \text{size}[i+1] +> \f] +> Otherwise, it will not be possible to view self tensor as shape without copying it. + +In NEML2, use [base_index](@ref neml2::BatchTensorBase::base_index) for indexing the base dimensions and [batch_index](@ref neml2::BatchTensorBase::batch_index) for indexing the batch dimensions: +```cpp +using namespace torch::indexing; +BatchTensor A(torch::tensor({{2, 3, 4}, {-1, -2, 3}, {6, 9, 7}}), 1); +// A = [[ 2 3 4] +// [ -1 -2 3] +// [ 6 9 7]] +BatchTensor B = A.batch_index({Slice(0, 2)}); +// B = [[ 2 3 4] +// [ -1 -2 3]] +BatchTensor C = A.base_index({Slice(1, 3)}); +// C = [[ 3 4] +// [ -2 3] +// [ 9 7]] +``` +To modify the content of a tensor, use [base_index_put](@ref neml2::BatchTensorBase::base_index_put) or [batch_index_put](@ref neml2::BatchTensorBase::batch_index_put): +```cpp +A.base_index_put({Slice(1, 3)}, torch::ones({3, 2})); +// A = [[ 2 1 1] +// [ -1 1 1] +// [ 6 1 1]] +A.batch_index_put({Slice(0, 2)}, torch::zeros({2, 3})); +// A = [[ 0 0 0] +// [ 0 0 0] +// [ 6 1 1]] +``` +A detailed explanation on tensor indexing APIs is available as part of the official [PyTorch documentation](https://pytorch.org/cppdocs/notes/tensor_indexing.html). + +### Tensor labeling {#tensor-labeling} + +In the context of material modeling, oftentimes views of tensors have practical/physical meanings. For example, given a logically 1D tensor with base size 9, its underlying data in an arbitrary batch may look like +``` +equivalent plastic strain 2.1 + cauchy stress -2.1 + 0 + 1.3 + -1.1 + 2.5 + 2.5 + temperature 102.9 + time 3.6 +``` +where component 0 stores the scalar-valued equivalent plastic strain, components 1-6 store the tensor-valued cauchy stress (we use the Mandel notation for symmetric second order tensors), component 7 stores the scalar-valued temperature, and component 8 stores the scalar-valued time. + +The string indicating the physical meaning of the view, e.g., "cauchy stress", is called a "label", and the view of the tensor indexed by a label is called a "labeled view", i.e., +``` + cauchy stress -2.1 + 0 + 1.3 + -1.1 + 2.5 + 2.5 +``` + +NEML2 provides a data structure named [LabeledAxis](@ref neml2::LabeledAxis) to facilitate the creation and modification of labels, and a data structure named [LabeledTensor](@ref neml2::LabeledTensor) to facilitate the creation and modification of labeled views. + +The [LabeledAxis](@ref neml2::LabeledAxis) contains all information regarding how an axis of a `LabeledTensor` is labeled. The following naming convention is used: +- Item: A labelable slice of data +- Variable: An item that is also of a [NEML2 primitive tensor type](@ref tensor-types) +- Sub-axis: An item of type `LabeledAxis` + +So yes, an axis can be labeled recursively, e.g., + +``` + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 +/// |-----------| |-----| | | | | | | +/// a b | | | | | | +/// |-------------------| |--------------| |--| |--| +/// sub a b c +``` +The above example represents an axis of size 15. This axis has 4 items: `a`, `b`, `c`, and `sub`. +- "a" is a variable of storage size 6 (possibly of type `SR2`). +- "b" is a variable of type `Scalar`. +- "c" is a variable of type `Scalar`. +- "sub" is a sub-axis of type `LabeledAxis`. "sub" by itself represents an axis of size 7, containing 2 items: + - "a" is a variable of storage size 6. + - "b" is a variable of type `Scalar`. + +Duplicate labels are *not* allowed on the same level of the axis, e.g. "a", "b", "c", and "sub" share the same level and so must be different. However, items on different levels of an axis can share the same label, e.g., "a" on the sub-axis "sub" has the same label as "a" on the main axis. In NEML2 convention, item names are always fully qualified, and a sub-axis is prefixed with a left slash, e.g. item "b" on the sub-axis "sub" can be denoted as "sub/b" on the main axis. + +> A label cannot contain: white spaces, quotes, left slash (`/`), or new line. +> +> Due to performance considerations, a `LabeledAxis` can only be modified, e.g., adding/removing variables and sub-axis, at the time a model is constructed. After the model construction phase, the `LabeledAxis` associated with that model can no longer be modified over the entire course of the simulation. + +Refer to the documentation for a complete list of APIs for creating and modifying a [LabeledAxis](@ref neml2::LabeledAxis). + +[LabeledTensor](@ref neml2::LabeledTensor) is the primary data structure in NEML2 for working with labeled tensor views. Each `LabeledTensor` consists of one `BatchTensor` and one or more `LabeledAxis`s. The `LabeledTensor` is templated on the base dimension \f$D\f$. [LabeledVector](@ref neml2::LabeledVector) and [LabeledMatrix](@ref neml2::LabeledMatrix) are the two most widely used data structures in NEML2. + +`LabeledTensor` handles the creation, modification, and accessing of labeled tensors. Recall that all primitive data types in a labeled tensor are flattened, e.g., a symmetric fourth order tensor of type `SSR4` with batch size `(5)` and base size `(6, 6)` are flattened to have base size `(36)` in the labeled tensor. The documentation provides a complete list of APIs. The commonly used methods are +- [operator()](@ref neml2::LabeledTensor::operator()()) for retrieving a labeled view into the raw (flattened) data without reshaping +- [get](@ref neml2::LabeledTensor::get) for retrieving a labeled view and reshaping it to the correct shape +- [set](@ref neml2::LabeledTensor::set) for setting values for a labeled view +- [slice](@ref neml2::LabeledTensor::slice) for slicing a sub-axis along a specific base dimension +- [block](@ref neml2::LabeledTensor::block) for sub-indexing the `LabeledTensor` with \f$D\f$ sub-axis names diff --git a/doc/requirements.txt b/doc/requirements.txt index 5500f007d0..df8a651c87 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1 +1,2 @@ PyYAML +pybind11-stubgen diff --git a/tests/profiling/main.cxx b/doc/syntax.cxx similarity index 77% rename from tests/profiling/main.cxx rename to doc/syntax.cxx index 668cd39ece..e3675e92d2 100644 --- a/tests/profiling/main.cxx +++ b/doc/syntax.cxx @@ -22,24 +22,22 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include "utils.h" -#include "neml2/misc/error.h" -#include "neml2/drivers/Driver.h" - -using namespace neml2; +#include "neml2/base/Registry.h" +#include int -main(int argc, char * argv[]) +main() { - if (argc != 3) + std::ofstream syntax_file; + syntax_file.open("syntax.yml"); + + for (auto && [type, options] : neml2::Registry::expected_options()) { - std::cout << "Usage: profiling_tests /path/to/input.i driver_name" << std::endl; - return 1; + options.set("type") = type; + syntax_file << neml2::Registry::syntax_type(type) << ":\n"; + syntax_file << options << "\n"; } - load_model(std::string(argv[1])); - auto & driver = Factory::get_object("Drivers", std::string(argv[2])); - driver.run(); - + syntax_file.close(); return 0; } diff --git a/extern/Catch2 b/extern/Catch2 deleted file mode 160000 index 20ace55034..0000000000 --- a/extern/Catch2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 20ace5503422a8511036aa9d486435041127e0cf diff --git a/extern/doxygen-awesome-css b/extern/doxygen-awesome-css deleted file mode 160000 index a3c119b479..0000000000 --- a/extern/doxygen-awesome-css +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a3c119b4797be2039761ec1fa0731f038e3026f6 diff --git a/extern/gperftools b/extern/gperftools deleted file mode 160000 index dffb4a2f28..0000000000 --- a/extern/gperftools +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dffb4a2f284700b814e2c3aa1f9263fd14e836f8 diff --git a/extern/hit/CMakeLists.txt b/extern/hit/CMakeLists.txt deleted file mode 100644 index 8d4bc05eb4..0000000000 --- a/extern/hit/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -add_library(hit SHARED - hit/parse.cc - hit/lex.cc - hit/braceexpr.cc -) - -if(Torch_CXX11_ABI) - target_compile_definitions(hit PUBLIC _GLIBCXX_USE_CXX11_ABI=1) -else() - target_compile_definitions(hit PUBLIC _GLIBCXX_USE_CXX11_ABI=0) -endif() - -set_target_properties(hit PROPERTIES UNITY_BUILD OFF) -target_include_directories(hit PUBLIC .) -target_sources(hit - PUBLIC - FILE_SET HEADERS - FILES - hit/braceexpr.h - hit/hit.h - hit/lex.h - hit/parse.h -) -install( - TARGETS hit - FILE_SET HEADERS - COMPONENT Development -) diff --git a/extern/hit/hit b/extern/hit/hit deleted file mode 160000 index 1a05e6d78e..0000000000 --- a/extern/hit/hit +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1a05e6d78ed5080e8d9f8bebfd85630d3bd7bf10 diff --git a/include/neml2/base/CrossRef.h b/include/neml2/base/CrossRef.h index 589ae53dce..e22231b333 100644 --- a/include/neml2/base/CrossRef.h +++ b/include/neml2/base/CrossRef.h @@ -55,15 +55,16 @@ class CrossRef /** * @brief Assignment operator + * + * This simply assigns the string without parsing and resolving the cross-reference */ CrossRef & operator=(const std::string & other); /** * @brief Implicit conversion operator. * - * It is assumed that the cross-referenced object has already been manufactured at this point. - * - * @return T The resolved value. + * The underlying string is parsed and used to resolve the cross-reference. It is assumed that the + * cross-referenced object has already been manufactured at this point. */ operator T() const; diff --git a/include/neml2/base/DependencyResolver.h b/include/neml2/base/DependencyResolver.h index bc2788e183..63cbb952ab 100644 --- a/include/neml2/base/DependencyResolver.h +++ b/include/neml2/base/DependencyResolver.h @@ -44,6 +44,10 @@ template class DependencyResolver { public: + /** + * Similar to @tparam ItemType but additionally contains information about its @tparam Node + * parent, i.e. the node which defines this consumed/provided item. + */ struct Item { Item(Node * const node, const ItemType & item) @@ -52,19 +56,25 @@ class DependencyResolver { } + /// Node which defines this item Node * const parent; + + /// The consumed/provided item const ItemType value; + /// Test for equality between two items bool operator==(const Item & other) const { return parent == other.parent && value == other.value; } + /// Test for inequality between two items bool operator!=(const Item & other) const { return parent != other.parent || value != other.value; } + /// An arbitrary comparator so that items can be sorted (for consistency) bool operator<(const Item & other) const { return parent != other.parent ? (parent < other.parent) : (value < other.value); @@ -73,30 +83,61 @@ class DependencyResolver DependencyResolver() = default; + /// Add a node (defining consumed/provided items) in the dependency graph void add_node(DependencyDefinition *); + /// Add an additional outbound item that the dependency graph _provides_ void add_additional_outbound_item(const ItemType & item); - /// Set a node's priority + /// Set a node's priority, useful for resolving cyclic dependency void set_priority(DependencyDefinition *, size_t); /// Resolve nodal dependency and find an evaluation order void resolve(); + /// The resolved (nodal) evaluation order following which all consumed items of the current node const std::vector & resolution() const { return _resolution; } + /** + * The item-item provider dictionary: key of the dictionary is the item of interest, and the value + * of the dictionary is the set of items that _provide_ the item of interest. + */ const std::map> & item_providers() const { return _item_provider_graph; } + + /** + * The item-item consumer dictionary: key of the dictionary is the item of interest, and the value + * of the dictionary is the set of items that _consume_ the item of interest. + */ const std::map> & item_consumers() const { return _item_consumer_graph; } + + /** + * The node-node provider dictionary: key of the dictionary is the node of interest, and the value + * of the dictionary is the set of nodes that _provide_ the item of interest. + */ const std::map> & node_providers() const { return _node_provider_graph; } + + /** + * The node-node consumer dictionary: key of the dictionary is the node of interest, and the value + * of the dictionary is the set of nodes that _consume_ the item of interest. + */ const std::map> & node_consumers() const { return _node_consumer_graph; } + /// End nodes which are not consumed by anyone else const std::set & end_nodes() const { return _end_nodes; } + + /// The items provided by the overall dependency graph, i.e., the items that are not consumed by _any_ node. const std::set & outbound_items() const { return _out_items; } + /// Start nodes which do not consume anyone else const std::set & start_nodes() const { return _start_nodes; } + + /// The items consumed by the overall dependency graph, i.e., the items that are not provided by _any_ node. const std::set & inbound_items() const { return _in_items; } + /// @returns a boolean flag controlling whether item provider should be unique bool & unique_item_provider() { return _unique_item_provider; } + + /// @returns a boolean flag controlling whether item consumer should be unique bool & unique_item_consumer() { return _unique_item_consumer; } private: diff --git a/include/neml2/base/Factory.h b/include/neml2/base/Factory.h index 7583590c40..dc2e006a2c 100644 --- a/include/neml2/base/Factory.h +++ b/include/neml2/base/Factory.h @@ -32,8 +32,8 @@ namespace neml2 { /** * The factory is responsible for: - * 1. retriving a `NEML2Object` given the object name as a `std::string` - * 2. creating a `NEML2Object` given the type of the `NEML2Object` as a `std::string`. + * 1. retriving a NEML2Object given the object name as a std::string + * 2. creating a NEML2Object given the type of the NEML2Object as a std::string. */ class Factory { @@ -41,7 +41,7 @@ class Factory /// The sequence which we use to manufacture objects. static std::vector pipeline; - /// Get the global `Factory` singleton. + /// Get the global Factory singleton. static Factory & get(); /** @@ -52,7 +52,7 @@ class Factory * - the object with the given name exists but does not have the correct type (e.g., dynamic case * fails). * - * @tparam T The type of the `NEML2Object` + * @tparam T The type of the NEML2Object * @param section The section name under which the search happens. * @param name The name of the object to retrieve. * @param additional_options Additional input options to pass to the object constructor @@ -74,7 +74,7 @@ class Factory * - the object with the given name exists but does not have the correct type (e.g., dynamic case * fails). * - * @tparam T The type of the `NEML2Object` + * @tparam T The type of the NEML2Object * @param section The section name under which the search happens. * @param name The name of the object to retrieve. * @param additional_options Additional input options to pass to the object constructor @@ -111,7 +111,7 @@ class Factory protected: /** - * @brief Manufacture a single `NEML2Object`. + * @brief Manufacture a single NEML2Object. * * @param section The section which the object to be manufactured belongs to. * @param options The options of the object. diff --git a/include/neml2/base/NEML2Object.h b/include/neml2/base/NEML2Object.h index 9286014791..c61e570835 100644 --- a/include/neml2/base/NEML2Object.h +++ b/include/neml2/base/NEML2Object.h @@ -71,9 +71,11 @@ class NEML2Object /// A readonly reference to the object's docstring const std::string & doc() const { return _options.doc(); } + /// Get a readonly pointer to the host template const T * host() const; + /// Get a writable pointer to the host template T * host(); diff --git a/include/neml2/base/OptionSet.h b/include/neml2/base/OptionSet.h index 69ca03a591..9b6e6c7aea 100644 --- a/include/neml2/base/OptionSet.h +++ b/include/neml2/base/OptionSet.h @@ -37,18 +37,18 @@ namespace neml2 { +///@{ /** - * Helper functions for printing scalar, vector, vector. Called from + * Helper functions for printing scalar, vector, vector of vector. Called from * OptionSet::Option::print(...). */ template void print_helper(std::ostream & os, const P *); - template void print_helper(std::ostream & os, const std::vector

*); - template void print_helper(std::ostream & os, const std::vector> *); +///@} /** * @brief A custom map-like data structure. The keys are strings, and the values can be @@ -91,6 +91,10 @@ class OptionSet const std::string & doc() const { return _metadata.doc; } /// A writable reference to the option set's docstring std::string & doc() { return _metadata.doc; } + /// A readonly reference to the option set's section + const std::string & section() const { return _metadata.section; } + /// A writable reference to the option set's section + std::string & section() { return _metadata.section; } /** * \returns \p true if an option of type \p T @@ -200,7 +204,7 @@ class OptionSet * object to suppress certain option. A suppressed option cannot be modified by the user. It * is up to the specific Parser to decide what happens when a user attempts to set a * suppressed option, e.g., the parser can choose to throw an exception, print a warning and - * accepts it, or print a warning and ignores it. + * accept it, or print a warning and ignores it. */ bool suppressed = false; } _metadata; @@ -345,6 +349,14 @@ class OptionSet * https://www.doxygen.nl/manual/markdown.html */ std::string doc = ""; + /** + * @brief Which NEML2 input file section this object belongs to + * + * NEML2 supports first class systems such as [Tensors], [Models], [Drivers], [Solvers], etc. + * This field denotes which section, i.e. which of the first class system, this object belongs + * to. + */ + std::string section = ""; } _metadata; /// Data structure to map names with values diff --git a/include/neml2/base/Parser.h b/include/neml2/base/Parser.h index 94de84f69b..a4e23339e7 100644 --- a/include/neml2/base/Parser.h +++ b/include/neml2/base/Parser.h @@ -29,9 +29,28 @@ namespace neml2 { +enum ParserType +{ + HIT, + XML, + YAML, + AUTO +}; + +/** + * @brief A convenient function to parse all options from an input file + * + * @param path Path to the input file to be parsed + * @param additional_input Additional cliargs to pass to the parser + * @param ptype Input file format + */ +void load_model(const std::string & path, + const std::string & additional_input = "", + ParserType ptype = ParserType::AUTO); + /** * @brief A parser is responsible for parsing an input file into a collection of options which - * can be used by the `Factory` to manufacture corresponding objects. + * can be used by the Factory to manufacture corresponding objects. * */ class Parser diff --git a/include/neml2/base/Registry.h b/include/neml2/base/Registry.h index 601d7a20f3..e41991189b 100644 --- a/include/neml2/base/Registry.h +++ b/include/neml2/base/Registry.h @@ -45,8 +45,8 @@ using BuildPtr = std::shared_ptr (*)(const OptionSet & options); * The Registry is used as a global singleton to collect information on all available NEML2Object * that can manufactured from the input file. * - * To register a concrete class to the registry, use the macro `register_NEML2_object` or - * `register_NEML2_object_alias`. Each object/class should only be registered once. + * To register a concrete class to the registry, use the macro register_NEML2_object or + * register_NEML2_object_alias. Each object/class should only be registered once. */ class Registry { @@ -62,15 +62,18 @@ class Registry return 0; } + /// Return the expected options of all registered classs + static std::map expected_options(); + /// Return the expected options of a specific registered class static OptionSet expected_options(const std::string & name); + /// Return the syntax type (what appears in the input file) given a registered object's type + static std::string syntax_type(const std::string & type); + /// Return the build method pointer of a specific registered class static BuildPtr builder(const std::string & name); - /// List all registered objects - static void print(std::ostream & os = std::cout); - private: Registry() {} diff --git a/include/neml2/drivers/solid_mechanics/LargeDeformationIncrementalSolidMechanicsDriver.h b/include/neml2/drivers/solid_mechanics/LargeDeformationIncrementalSolidMechanicsDriver.h index 0e1bef9294..86d1dba98b 100644 --- a/include/neml2/drivers/solid_mechanics/LargeDeformationIncrementalSolidMechanicsDriver.h +++ b/include/neml2/drivers/solid_mechanics/LargeDeformationIncrementalSolidMechanicsDriver.h @@ -71,13 +71,16 @@ class LargeDeformationIncrementalSolidMechanicsDriver : public TransientDriver VariableName _driving_force_name; /** - * The value of the (total) vorticity + * The name of the total vorticity */ - WR2 _vorticity; + VariableName _vorticity_name; + + /// Whether vorticity is prescribed + const bool _vorticity_prescribed; /** - * The name of the total vorticity + * The value of the (total) vorticity */ - VariableName _vorticity_name; + WR2 _vorticity; }; } diff --git a/include/neml2/misc/math.h b/include/neml2/misc/math.h index 267f7431b9..9eab1f31b5 100644 --- a/include/neml2/misc/math.h +++ b/include/neml2/misc/math.h @@ -207,7 +207,7 @@ BatchTensor skew_to_full(const BatchTensor & skew, TorchSize dim = 0); * @param p The parameter to take derivatives with respect to * @return BatchTensor \f$\partial y/\partial p\f$ */ -BatchTensor jacrev(const BatchTensor & out, const BatchTensor & p); +BatchTensor jacrev(const BatchTensor & y, const BatchTensor & p); BatchTensor base_diag_embed(const BatchTensor & a, TorchSize offset = 0, TorchSize d1 = -2, TorchSize d2 = -1); diff --git a/include/neml2/models/BufferStore.h b/include/neml2/models/BufferStore.h index 5228b6d73a..e89880dabe 100644 --- a/include/neml2/models/BufferStore.h +++ b/include/neml2/models/BufferStore.h @@ -38,13 +38,13 @@ class BufferStore BufferStore(const OptionSet & options, NEML2Object * object); /// @returns the buffer storage - /// @{ + ///@{ const Storage & named_buffers() const { return const_cast(this)->named_buffers(); } Storage & named_buffers(); - /// }@ + ///}@ /// Get a writable reference of a buffer template OptionSet Interpolation::expected_options() { + // This is the only way of getting tensor type in a static method like this... + // Trim 6 chars to remove 'neml2::' + auto tensor_type = utils::demangle(typeid(T).name()).substr(7); + OptionSet options = NonlinearParameter::expected_options(); + options.doc() = "Interpolate a " + tensor_type + + " as a function of the given argument. See neml2::Interpolation for rules on " + "shapes of the interpolant and the argument."; + options.set("argument"); + options.set("argument").doc() = "Argument used to query the interpolant"; + options.set>("abscissa"); + options.set("abscissa").doc() = "Scalar defining the abscissa values of the interpolant"; + options.set>("ordinate"); + options.set("ordinate").doc() = tensor_type + " defining the ordinate values of the interpolant"; + return options; } diff --git a/include/neml2/models/Model.h b/include/neml2/models/Model.h index 69e98347d5..b3527b1771 100644 --- a/include/neml2/models/Model.h +++ b/include/neml2/models/Model.h @@ -242,7 +242,7 @@ class Model : public std::enable_shared_from_this, * If \p merge_input is set to true, this model will also *consume* the consumed variables of \p * model, which will affect dependency resolution inside a ComposedModel. * - * @param model The model to register + * @param name The model to register * @param extra_deriv_order The additional derivative order required for the registered-submodel * @param nonlinear Set to true if the registered model defines a nonlinear system to be solved * @param merge_input Whether to merge the input of the registered model into *this* model's diff --git a/include/neml2/models/ParameterStore.h b/include/neml2/models/ParameterStore.h index 2d0b2ba928..953dbbc954 100644 --- a/include/neml2/models/ParameterStore.h +++ b/include/neml2/models/ParameterStore.h @@ -40,13 +40,13 @@ class ParameterStore ParameterStore(const OptionSet & options, NEML2Object * object); /// @returns the buffer storage - /// @{ + ///@{ const Storage & named_parameters() const { return const_cast(this)->named_parameters(); } Storage & named_parameters(); - /// }@ + ///}@ /// Get a writable reference of a parameter template Variable & get_input_variable(const VariableName & name) { @@ -68,7 +68,7 @@ class VariableStore /// @} /// Get an output variable - /// @{ + ///@{ template const Variable & get_output_variable(const VariableName & name) { @@ -87,49 +87,49 @@ class VariableStore /// @} /// Definition of the input variables - /// @{ + ///@{ LabeledAxis & input_axis() { return _input_axis; } const LabeledAxis & input_axis() const { return _input_axis; } /// @} /// Which variables this object defines as output - /// @{ + ///@{ LabeledAxis & output_axis() { return _output_axis; } const LabeledAxis & output_axis() const { return _output_axis; } /// @} /// Input variable views - /// @{ + ///@{ Storage & input_views() { return _input_views; } const Storage & input_views() const { return _input_views; } /// @} /// Output variable views - /// @{ + ///@{ Storage & output_views() { return _output_views; } const Storage & output_views() const { return _output_views; } /// @} /// Input storage - /// @{ + ///@{ LabeledVector & input_storage() { return _in; } const LabeledVector & input_storage() const { return _in; } /// @} /// Output storage - /// @{ + ///@{ LabeledVector & output_storage() { return _out; } const LabeledVector & output_storage() const { return _out; } /// @} /// Derivative storage - /// @{ + ///@{ LabeledMatrix & derivative_storage() { return _dout_din; } const LabeledMatrix & derivative_storage() const { return _dout_din; } /// @} /// Second derivative storage - /// @{ + ///@{ LabeledTensor3D & second_derivative_storage() { return _d2out_din2; } const LabeledTensor3D & second_derivative_storage() const { return _d2out_din2; } /// @} diff --git a/include/neml2/models/crystallography/user_tensors/FillMillerIndex.h b/include/neml2/models/crystallography/user_tensors/FillMillerIndex.h index c1bde07609..c6b93ae495 100644 --- a/include/neml2/models/crystallography/user_tensors/FillMillerIndex.h +++ b/include/neml2/models/crystallography/user_tensors/FillMillerIndex.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/models/crystallography/MillerIndex.h" @@ -36,7 +35,7 @@ namespace crystallography /** * @brief Create a single-batched "list" of Miller indices */ -class FillMillerIndex : public MillerIndex, public NEML2Object +class FillMillerIndex : public MillerIndex, public UserTensor { public: static OptionSet expected_options(); diff --git a/include/neml2/models/crystallography/user_tensors/SymmetryFromOrbifold.h b/include/neml2/models/crystallography/user_tensors/SymmetryFromOrbifold.h index a0a0639505..dd961211cf 100644 --- a/include/neml2/models/crystallography/user_tensors/SymmetryFromOrbifold.h +++ b/include/neml2/models/crystallography/user_tensors/SymmetryFromOrbifold.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/R2.h" @@ -36,7 +35,7 @@ namespace crystallography /** * @brief Provide the correct symmetry operators for a given crystal class */ -class SymmetryFromOrbifold : public R2, public NEML2Object +class SymmetryFromOrbifold : public R2, public UserTensor { public: static OptionSet expected_options(); @@ -49,4 +48,4 @@ class SymmetryFromOrbifold : public R2, public NEML2Object SymmetryFromOrbifold(const OptionSet & options); }; } // namespace crystallography -} // namespace neml2 \ No newline at end of file +} // namespace neml2 diff --git a/include/neml2/models/solid_mechanics/ChabochePlasticHardening.h b/include/neml2/models/solid_mechanics/ChabochePlasticHardening.h index f68aeb479a..12f6d205ae 100644 --- a/include/neml2/models/solid_mechanics/ChabochePlasticHardening.h +++ b/include/neml2/models/solid_mechanics/ChabochePlasticHardening.h @@ -38,13 +38,13 @@ class ChabochePlasticHardening : public FlowRule protected: void set_value(bool out, bool dout_din, bool d2out_din2) override; - /// Backstress + /// Back stress const Variable & _X; /// Flow direction const Variable & _NM; - /// Rate of backstress + /// Rate of back stress Variable & _X_dot; const Scalar & _C; diff --git a/include/neml2/models/solid_mechanics/KinematicHardening.h b/include/neml2/models/solid_mechanics/KinematicHardening.h index 24d45e766c..ecc337bb51 100644 --- a/include/neml2/models/solid_mechanics/KinematicHardening.h +++ b/include/neml2/models/solid_mechanics/KinematicHardening.h @@ -39,7 +39,7 @@ class KinematicHardening : public Model /// Kinematic plastic strain const Variable & _Kp; - /// Backstress + /// Back stress Variable & _X; }; } // namespace neml2 diff --git a/include/neml2/models/solid_mechanics/OverStress.h b/include/neml2/models/solid_mechanics/OverStress.h index c8b0849e61..344308667d 100644 --- a/include/neml2/models/solid_mechanics/OverStress.h +++ b/include/neml2/models/solid_mechanics/OverStress.h @@ -41,7 +41,7 @@ class OverStress : public Model /// Mandel stress const Variable & _M; - /// Backstress + /// Back stress const Variable & _X; /// Overstress diff --git a/include/neml2/models/solid_mechanics/crystal_plasticity/FixOrientation.h b/include/neml2/models/solid_mechanics/crystal_plasticity/FixOrientation.h index 5e9aeb4756..0f3713581f 100644 --- a/include/neml2/models/solid_mechanics/crystal_plasticity/FixOrientation.h +++ b/include/neml2/models/solid_mechanics/crystal_plasticity/FixOrientation.h @@ -29,7 +29,7 @@ namespace neml2 { /** - * @brief Swap orientation plane when the singularity at \f[2 \pi\] is met with the modified + * @brief Swap orientation plane when the singularity at \f$ 2 \pi \f$ is met with the modified * Rodrigues vector. * * See the following reference for details diff --git a/include/neml2/solvers/NonlinearSystem.h b/include/neml2/solvers/NonlinearSystem.h index a4cd9d0dea..81ff2730a1 100644 --- a/include/neml2/solvers/NonlinearSystem.h +++ b/include/neml2/solvers/NonlinearSystem.h @@ -38,6 +38,10 @@ class NonlinearSystem public: static OptionSet expected_options(); + static void disable_automatic_scaling(OptionSet & options); + + static void enable_automatic_scaling(OptionSet & options); + NonlinearSystem(const OptionSet & options); /** @@ -89,7 +93,7 @@ class NonlinearSystem * @param residual Whether residual is requested * @param Jacobian Whether Jacobian is requested */ - virtual void assemble(bool, bool) = 0; + virtual void assemble(bool residual, bool Jacobian) = 0; /// Number of degrees of freedom TorchSize _ndof; diff --git a/include/neml2/tensors/LabeledAxis.h b/include/neml2/tensors/LabeledAxis.h index 71c73e25f6..926405fbce 100644 --- a/include/neml2/tensors/LabeledAxis.h +++ b/include/neml2/tensors/LabeledAxis.h @@ -39,7 +39,7 @@ namespace neml2 * @brief A *labeled* axis used to associate layout of a tensor with human-interpretable names. * * A logically one-dimensional tensor requires one LabeledAxis, two-dimensional tensor requires two - * LabeledAxis, and so on. See @ref labeledview for a detailed explanation of tensor labeling. + * LabeledAxis, and so on. See @ref tensor-labeling for a detailed explanation of tensor labeling. * * All the LabeledAxis modifiers can only be used during the setup stage. Calling any modifiers * after the setup stage is forbidden and will result in a runtime error in Debug mode. diff --git a/include/neml2/tensors/LabeledTensor.h b/include/neml2/tensors/LabeledTensor.h index 1fa223bc03..e9a4be7e7f 100644 --- a/include/neml2/tensors/LabeledTensor.h +++ b/include/neml2/tensors/LabeledTensor.h @@ -103,7 +103,7 @@ class LabeledTensor void zero_(); /// Get the underlying tensor - /// @{ + ///@{ const BatchTensor & tensor() const { return _tensor; } BatchTensor & tensor() { return _tensor; } /// @} diff --git a/include/neml2/tensors/Variable.h b/include/neml2/tensors/Variable.h index 39c359e5c5..73d41a0ca4 100644 --- a/include/neml2/tensors/Variable.h +++ b/include/neml2/tensors/Variable.h @@ -82,7 +82,7 @@ class VariableBase /// Create a wrapper representing the second derivative d2y/dx2 Derivative d(const VariableBase & x1, const VariableBase & x2); - /// @{ Accessors for storage + ///@{ Accessors for storage const LabeledVector & value_storage() const; const LabeledMatrix & derivative_storage() const; const LabeledTensor3D & second_derivative_storage() const; diff --git a/include/neml2/tensors/user_tensors/EmptyBatchTensor.h b/include/neml2/tensors/user_tensors/EmptyBatchTensor.h index 379fa36e72..699ec1501d 100644 --- a/include/neml2/tensors/user_tensors/EmptyBatchTensor.h +++ b/include/neml2/tensors/user_tensors/EmptyBatchTensor.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/BatchTensor.h" @@ -34,7 +33,7 @@ namespace neml2 /** * @brief Create an empty BatchTensor from the input file. */ -class EmptyBatchTensor : public BatchTensor, public NEML2Object +class EmptyBatchTensor : public BatchTensor, public UserTensor { public: static OptionSet expected_options(); diff --git a/include/neml2/tensors/user_tensors/EmptyFixedDimTensor.h b/include/neml2/tensors/user_tensors/EmptyFixedDimTensor.h index 11ce394e57..d84504ed57 100644 --- a/include/neml2/tensors/user_tensors/EmptyFixedDimTensor.h +++ b/include/neml2/tensors/user_tensors/EmptyFixedDimTensor.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/macros.h" #include "neml2/tensors/tensors.h" @@ -38,7 +37,7 @@ namespace neml2 * @tparam T The concrete tensor derived from FixedDimTensor */ template -class EmptyFixedDimTensor : public T, public NEML2Object +class EmptyFixedDimTensor : public T, public UserTensor { public: static OptionSet expected_options(); diff --git a/include/neml2/tensors/user_tensors/Fill3DVec.h b/include/neml2/tensors/user_tensors/Fill3DVec.h deleted file mode 100644 index baf5cb2e08..0000000000 --- a/include/neml2/tensors/user_tensors/Fill3DVec.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2023, UChicago Argonne, LLC -// All Rights Reserved -// Software Name: NEML2 -- the New Engineering material Model Library, version 2 -// By: Argonne National Laboratory -// OPEN SOURCE LICENSE (MIT) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#pragma once - -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" - -#include "neml2/tensors/Vec.h" - -namespace neml2 -{ -/** - * @brief Create a single-batched "list" of 3D vectors - */ -class Fill3DVec : public Vec, public NEML2Object -{ -public: - static OptionSet expected_options(); - - /** - * @brief Construct a new Fill3DVec object - * - * @param options The options extracted from the input file. - */ - Fill3DVec(const OptionSet & options); - -private: - /// A helper method to dispatch to the correct fill method based on the number of values. - Vec fill(const std::vector & values) const; -}; -} // namespace neml2 diff --git a/include/neml2/tensors/user_tensors/FillR2.h b/include/neml2/tensors/user_tensors/FillR2.h index bcd141b761..d1ab63992a 100644 --- a/include/neml2/tensors/user_tensors/FillR2.h +++ b/include/neml2/tensors/user_tensors/FillR2.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/R2.h" @@ -34,7 +33,7 @@ namespace neml2 /** * @brief Create a filled R2 from the input file. */ -class FillR2 : public R2, public NEML2Object +class FillR2 : public R2, public UserTensor { public: static OptionSet expected_options(); diff --git a/include/neml2/tensors/user_tensors/FillRot.h b/include/neml2/tensors/user_tensors/FillRot.h index 82616bc887..f05dbd84f2 100644 --- a/include/neml2/tensors/user_tensors/FillRot.h +++ b/include/neml2/tensors/user_tensors/FillRot.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/Rot.h" @@ -34,7 +33,7 @@ namespace neml2 /** * @brief Create a filled R2 from the input file. */ -class FillRot : public Rot, public NEML2Object +class FillRot : public Rot, public UserTensor { public: static OptionSet expected_options(); diff --git a/include/neml2/tensors/user_tensors/FillSR2.h b/include/neml2/tensors/user_tensors/FillSR2.h index d8bb9cdff4..48fd6b94b1 100644 --- a/include/neml2/tensors/user_tensors/FillSR2.h +++ b/include/neml2/tensors/user_tensors/FillSR2.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/SR2.h" @@ -34,7 +33,7 @@ namespace neml2 /** * @brief Create a filled SR2 from the input file. */ -class FillSR2 : public SR2, public NEML2Object +class FillSR2 : public SR2, public UserTensor { public: static OptionSet expected_options(); diff --git a/include/neml2/tensors/user_tensors/FillWR2.h b/include/neml2/tensors/user_tensors/FillWR2.h index 10164763b9..8486fef68d 100644 --- a/include/neml2/tensors/user_tensors/FillWR2.h +++ b/include/neml2/tensors/user_tensors/FillWR2.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/WR2.h" @@ -34,7 +33,7 @@ namespace neml2 /** * @brief Create a filled WR2 from the input file. */ -class FillWR2 : public WR2, public NEML2Object +class FillWR2 : public WR2, public UserTensor { public: static OptionSet expected_options(); diff --git a/include/neml2/tensors/user_tensors/FullBatchTensor.h b/include/neml2/tensors/user_tensors/FullBatchTensor.h index 5e71a11b5a..9c0e446382 100644 --- a/include/neml2/tensors/user_tensors/FullBatchTensor.h +++ b/include/neml2/tensors/user_tensors/FullBatchTensor.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/BatchTensor.h" @@ -34,7 +33,7 @@ namespace neml2 /** * @brief Create a full BatchTensor from the input file. */ -class FullBatchTensor : public BatchTensor, public NEML2Object +class FullBatchTensor : public BatchTensor, public UserTensor { public: static OptionSet expected_options(); diff --git a/include/neml2/tensors/user_tensors/FullFixedDimTensor.h b/include/neml2/tensors/user_tensors/FullFixedDimTensor.h index 7506a2f757..1520bc786f 100644 --- a/include/neml2/tensors/user_tensors/FullFixedDimTensor.h +++ b/include/neml2/tensors/user_tensors/FullFixedDimTensor.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/macros.h" #include "neml2/tensors/tensors.h" @@ -38,7 +37,7 @@ namespace neml2 * @tparam T The concrete tensor derived from FixedDimTensor */ template -class FullFixedDimTensor : public T, public NEML2Object +class FullFixedDimTensor : public T, public UserTensor { public: static OptionSet expected_options(); diff --git a/include/neml2/tensors/user_tensors/IdentityBatchTensor.h b/include/neml2/tensors/user_tensors/IdentityBatchTensor.h index d089b96c2a..1b47b2b9b9 100644 --- a/include/neml2/tensors/user_tensors/IdentityBatchTensor.h +++ b/include/neml2/tensors/user_tensors/IdentityBatchTensor.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/BatchTensor.h" @@ -34,7 +33,7 @@ namespace neml2 /** * @brief Create an identity BatchTensor from the input file. */ -class IdentityBatchTensor : public BatchTensor, public NEML2Object +class IdentityBatchTensor : public BatchTensor, public UserTensor { public: static OptionSet expected_options(); diff --git a/include/neml2/tensors/user_tensors/LinspaceBatchTensor.h b/include/neml2/tensors/user_tensors/LinspaceBatchTensor.h index 0e7d85c86b..5e1cdd8d7e 100644 --- a/include/neml2/tensors/user_tensors/LinspaceBatchTensor.h +++ b/include/neml2/tensors/user_tensors/LinspaceBatchTensor.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/BatchTensor.h" @@ -34,7 +33,7 @@ namespace neml2 /** * @brief Create a linspace BatchTensor from the input file. */ -class LinspaceBatchTensor : public BatchTensor, public NEML2Object +class LinspaceBatchTensor : public BatchTensor, public UserTensor { public: static OptionSet expected_options(); diff --git a/include/neml2/tensors/user_tensors/LinspaceFixedDimTensor.h b/include/neml2/tensors/user_tensors/LinspaceFixedDimTensor.h index 2ea8c3b701..0bd980ff5c 100644 --- a/include/neml2/tensors/user_tensors/LinspaceFixedDimTensor.h +++ b/include/neml2/tensors/user_tensors/LinspaceFixedDimTensor.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/macros.h" #include "neml2/tensors/tensors.h" @@ -38,7 +37,7 @@ namespace neml2 * @tparam T The concrete tensor derived from FixedDimTensor */ template -class LinspaceFixedDimTensor : public T, public NEML2Object +class LinspaceFixedDimTensor : public T, public UserTensor { public: static OptionSet expected_options(); diff --git a/include/neml2/tensors/user_tensors/LogspaceBatchTensor.h b/include/neml2/tensors/user_tensors/LogspaceBatchTensor.h index 39f86c06fd..04bec52a94 100644 --- a/include/neml2/tensors/user_tensors/LogspaceBatchTensor.h +++ b/include/neml2/tensors/user_tensors/LogspaceBatchTensor.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/BatchTensor.h" @@ -34,7 +33,7 @@ namespace neml2 /** * @brief Create a logspace BatchTensor from the input file. */ -class LogspaceBatchTensor : public BatchTensor, public NEML2Object +class LogspaceBatchTensor : public BatchTensor, public UserTensor { public: static OptionSet expected_options(); diff --git a/include/neml2/tensors/user_tensors/LogspaceFixedDimTensor.h b/include/neml2/tensors/user_tensors/LogspaceFixedDimTensor.h index 4c17bc7572..0f7a16795f 100644 --- a/include/neml2/tensors/user_tensors/LogspaceFixedDimTensor.h +++ b/include/neml2/tensors/user_tensors/LogspaceFixedDimTensor.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/macros.h" #include "neml2/tensors/tensors.h" @@ -38,7 +37,7 @@ namespace neml2 * @tparam T The concrete tensor derived from FixedDimTensor */ template -class LogspaceFixedDimTensor : public T, public NEML2Object +class LogspaceFixedDimTensor : public T, public UserTensor { public: static OptionSet expected_options(); diff --git a/include/neml2/tensors/user_tensors/OnesBatchTensor.h b/include/neml2/tensors/user_tensors/OnesBatchTensor.h index 905d8f2bad..2e43c49220 100644 --- a/include/neml2/tensors/user_tensors/OnesBatchTensor.h +++ b/include/neml2/tensors/user_tensors/OnesBatchTensor.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/BatchTensor.h" @@ -34,7 +33,7 @@ namespace neml2 /** * @brief Create a ones BatchTensor from the input file. */ -class OnesBatchTensor : public BatchTensor, public NEML2Object +class OnesBatchTensor : public BatchTensor, public UserTensor { public: static OptionSet expected_options(); diff --git a/include/neml2/tensors/user_tensors/OnesFixedDimTensor.h b/include/neml2/tensors/user_tensors/OnesFixedDimTensor.h index 026a898afe..3c15f719f5 100644 --- a/include/neml2/tensors/user_tensors/OnesFixedDimTensor.h +++ b/include/neml2/tensors/user_tensors/OnesFixedDimTensor.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/macros.h" #include "neml2/tensors/tensors.h" @@ -38,7 +37,7 @@ namespace neml2 * @tparam T The concrete tensor derived from FixedDimTensor */ template -class OnesFixedDimTensor : public T, public NEML2Object +class OnesFixedDimTensor : public T, public UserTensor { public: static OptionSet expected_options(); diff --git a/include/neml2/tensors/user_tensors/Orientation.h b/include/neml2/tensors/user_tensors/Orientation.h index 5da65f77df..5c053da6a0 100644 --- a/include/neml2/tensors/user_tensors/Orientation.h +++ b/include/neml2/tensors/user_tensors/Orientation.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/Rot.h" @@ -34,7 +33,7 @@ namespace neml2 /** * @brief Create batch of rotations, with various methods */ -class Orientation : public Rot, public NEML2Object +class Orientation : public Rot, public UserTensor { public: static OptionSet expected_options(); @@ -42,14 +41,6 @@ class Orientation : public Rot, public NEML2Object /** * @brief Construct a new Orientation object * - * @param "input_type" -- the method used to define the angles, "euler_angles" or "random" - * @param "angle_convention" -- Euler angle convention, "Kocks", "Roe", or "Bunge" - * @param "angle_type" -- type of angles, either "degrees" or "radians" - * @param "values" -- input Euler angles, as a flattened nx3 matrix - * @param "normalize" -- if true do a "shadow parameter" replacement of the underlying MRP - * representation to move the inputs farther away from the singularity - * @param "random_seed" -- random seed for random angle generation - * @param "quantity" -- number (batch size) of random orientations */ Orientation(const OptionSet & options); diff --git a/include/neml2/tensors/user_tensors/UserBatchTensor.h b/include/neml2/tensors/user_tensors/UserBatchTensor.h index d386835777..99ce7782ce 100644 --- a/include/neml2/tensors/user_tensors/UserBatchTensor.h +++ b/include/neml2/tensors/user_tensors/UserBatchTensor.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/BatchTensor.h" @@ -34,7 +33,7 @@ namespace neml2 /** * @brief Create raw BatchTensor from the input file. */ -class UserBatchTensor : public BatchTensor, public NEML2Object +class UserBatchTensor : public BatchTensor, public UserTensor { public: static OptionSet expected_options(); diff --git a/include/neml2/tensors/user_tensors/UserFixedDimTensor.h b/include/neml2/tensors/user_tensors/UserFixedDimTensor.h index b4c5ed4441..7a57f66109 100644 --- a/include/neml2/tensors/user_tensors/UserFixedDimTensor.h +++ b/include/neml2/tensors/user_tensors/UserFixedDimTensor.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/macros.h" #include "neml2/tensors/tensors.h" @@ -38,7 +37,7 @@ namespace neml2 * @tparam T The concrete tensor derived from BatchTensorBase */ template -class UserFixedDimTensor : public T, public NEML2Object +class UserFixedDimTensor : public T, public UserTensor { public: static OptionSet expected_options(); diff --git a/tests/unit/base/test_Registry.cxx b/include/neml2/tensors/user_tensors/UserTensor.h similarity index 85% rename from tests/unit/base/test_Registry.cxx rename to include/neml2/tensors/user_tensors/UserTensor.h index 866d85d290..64053fa76c 100644 --- a/tests/unit/base/test_Registry.cxx +++ b/include/neml2/tensors/user_tensors/UserTensor.h @@ -21,18 +21,19 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include -#include -#include "neml2/base/Registry.h" +#pragma once -using namespace neml2; +#include "neml2/base/Registry.h" +#include "neml2/base/NEML2Object.h" -TEST_CASE("Registry", "[base]") +namespace neml2 { - SECTION("syntax") - { - std::ofstream out("syntax.yml"); - Registry::print(out); - } -} +class UserTensor : public NEML2Object +{ +public: + static OptionSet expected_options(); + + UserTensor(const OptionSet & options); +}; +} // namespace neml2 diff --git a/include/neml2/tensors/user_tensors/ZerosBatchTensor.h b/include/neml2/tensors/user_tensors/ZerosBatchTensor.h index c513404ccb..2a7797b2a9 100644 --- a/include/neml2/tensors/user_tensors/ZerosBatchTensor.h +++ b/include/neml2/tensors/user_tensors/ZerosBatchTensor.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/BatchTensor.h" @@ -34,7 +33,7 @@ namespace neml2 /** * @brief Create a zeros BatchTensor from the input file. */ -class ZerosBatchTensor : public BatchTensor, public NEML2Object +class ZerosBatchTensor : public BatchTensor, public UserTensor { public: static OptionSet expected_options(); diff --git a/include/neml2/tensors/user_tensors/ZerosFixedDimTensor.h b/include/neml2/tensors/user_tensors/ZerosFixedDimTensor.h index 52115f9156..a86ad9f066 100644 --- a/include/neml2/tensors/user_tensors/ZerosFixedDimTensor.h +++ b/include/neml2/tensors/user_tensors/ZerosFixedDimTensor.h @@ -24,8 +24,7 @@ #pragma once -#include "neml2/base/Registry.h" -#include "neml2/base/NEML2Object.h" +#include "neml2/tensors/user_tensors/UserTensor.h" #include "neml2/tensors/macros.h" #include "neml2/tensors/tensors.h" @@ -38,7 +37,7 @@ namespace neml2 * @tparam T The concrete tensor derived from FixedDimTensor */ template -class ZerosFixedDimTensor : public T, public NEML2Object +class ZerosFixedDimTensor : public T, public UserTensor { public: static OptionSet expected_options(); diff --git a/pyproject.toml b/pyproject.toml index f3f1a6e72a..163998fbcd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] -requires = ["scikit-build-core>=0.3.3", "pybind11"] -build-backend = "scikit_build_core.build" +requires = ["setuptools>=42", "cmake>=3.23"] +build-backend = "setuptools.build_meta" [project] name = "neml2" @@ -12,39 +12,3 @@ authors = [ description = "GPU-enabled vectorized material modeling library" readme = "README.md" requires-python = ">=3.8" -dependencies = ["torch==2.2.2"] - -[tool.scikit-build] -cmake.version = ">=3.23" -cmake.build-type = "RelWithDebInfo" -wheel.expand-macos-universal-tags = true -wheel.packages = [] -wheel.install-dir = "neml2" - -[tool.scikit-build.cmake.define] -CMAKE_INSTALL_PREFIX = "install" -CMAKE_JOB_POOL_COMPILE = "CI" -CMAKE_JOB_POOL_LINK = "CI" -CMAKE_UNITY_BUILD = "ON" -NEML2_PYBIND = "ON" -NEML2_WHEELS = "ON" -NEML2_TESTS = "OFF" -NEML2_DOC = "OFF" - -[tool.cibuildwheel] -build = "cp38-* cp39-* cp310-* cp311-*" -skip = "*-win32 *-manylinux_i686 *-musllinux*" -before-build = "pip install numpy torch==2.2.2" -build-verbosity = 1 -test-requires = "pytest numpy torch==2.2.2" -test-command = "pytest {project}/tests" - -[tool.cibuildwheel.macos.environment] -# MacOS 10.15 is the lowest version that supports _full_ C++17 -MACOSX_DEPLOYMENT_TARGET = "10.15" - -[tool.cibuildwheel.linux] -repair-wheel-command = "mv {wheel} {dest_dir}/" - -[tool.cibuildwheel.macos] -repair-wheel-command = "mv {wheel} {dest_dir}/" diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 009e5fff70..6c0d7bde5f 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -1,21 +1,15 @@ -find_package(Python COMPONENTS Interpreter Development.Module) -find_package(pybind11 CONFIG REQUIRED HINTS ${Python_SITEARCH}) +message(STATUS "Configuring pybind11") +FetchContent_MakeAvailable(pybind11) if(NOT Torch_PYTHON_BINDING) message(FATAL_ERROR "Could not find the libTorch Python binding") endif() -if(NOT ${NEML2_BINARY_DIR} STREQUAL ${NEML2_SOURCE_DIR}) - add_custom_target(pyneml2 ALL - COMMAND ${CMAKE_COMMAND} -E copy ${NEML2_SOURCE_DIR}/python/neml2/__init__.py ${NEML2_BINARY_DIR}/python/neml2/__init__.py - COMMENT "Copying __init__.py" - ) -endif() - -# macro for defining a submodule with given source files +# Macro for defining a submodule with given source files macro(add_submodule mname msrcs) - python_add_library(${mname} MODULE ${msrcs} WITH_SOABI) + pybind11_add_module(${mname} MODULE ${msrcs}) set_target_properties(${mname} PROPERTIES LIBRARY_OUTPUT_DIRECTORY neml2) + set_target_properties(${mname} PROPERTIES INSTALL_RPATH "${EXEC_DIR}/lib;${Torch_LINK_DIRECTORIES}") target_include_directories(${mname} PUBLIC ${NEML2_SOURCE_DIR}) target_link_libraries(${mname} PRIVATE pybind11::headers) target_link_libraries(${mname} PUBLIC neml2 ${Torch_PYTHON_BINDING}) @@ -34,10 +28,11 @@ add_submodule_dir(tensors) add_submodule_dir(models) add_submodule(math neml2/misc/math.cxx) -# Install python artifacts -install(DIRECTORY neml2/ - DESTINATION . - FILES_MATCHING - PATTERN "*.py" - PATTERN "*.pyi" -) +# Artifacts +configure_file(neml2/__init__.py ${NEML2_BINARY_DIR}/python/neml2/__init__.py COPYONLY) +install(FILES neml2/__init__.py DESTINATION .) + +# Tests +if(NEML2_TESTS) + add_subdirectory(tests) +endif() diff --git a/python/neml2/base/Factory.cxx b/python/neml2/base/Factory.cxx index c7951022ae..af13af0987 100644 --- a/python/neml2/base/Factory.cxx +++ b/python/neml2/base/Factory.cxx @@ -35,8 +35,5 @@ def_Factory(py::module_ & m) { py::class_(m, "Factory") .def_static("load", &Factory::load) - .def_static("clear", &Factory::clear) - .def_static("get_model", - [](const std::string & name) - { return Factory::get_object_ptr("Models", name); }); + .def_static("clear", &Factory::clear); } diff --git a/python/neml2/models/Model.cxx b/python/neml2/models/Model.cxx index 3d99db18ce..7d30fb0f06 100644 --- a/python/neml2/models/Model.cxx +++ b/python/neml2/models/Model.cxx @@ -26,6 +26,7 @@ #include "python/neml2/misc/types.h" #include "neml2/models/Model.h" +#include "neml2/base/Factory.h" namespace py = pybind11; using namespace neml2; @@ -55,4 +56,10 @@ def_Model(py::module_ & m) "named_parameters", [](Model & self) { return &self.named_parameters(); }, py::return_value_policy::reference); + + // Make Model manufacturable + auto factory = (py::class_)py::module_::import("neml2.base").attr("Factory"); + factory.def_static("get_model", + [](const std::string & name) + { return Factory::get_object_ptr("Models", name); }); } diff --git a/python/neml2/tensors/TensorValue.cxx b/python/neml2/tensors/TensorValue.cxx index be48a53049..b4ef4c3f4f 100644 --- a/python/neml2/tensors/TensorValue.cxx +++ b/python/neml2/tensors/TensorValue.cxx @@ -42,8 +42,8 @@ def_TensorValueBase(py::module_ & m) .def("dim", [](const TensorValueBase & self) { return BatchTensor(self).dim(); }) .def_property_readonly("shape", [](const TensorValueBase & self) { return BatchTensor(self).sizes(); }) - .def_property_readonly("dtype", - [](const TensorValueBase & self) { return BatchTensor(self).dtype(); }) + .def_property_readonly( + "dtype", [](const TensorValueBase & self) { return BatchTensor(self).scalar_type(); }) .def_property_readonly( "device", [](const TensorValueBase & self) { return BatchTensor(self).device(); }) .def_property_readonly("requires_grad", @@ -54,5 +54,4 @@ def_TensorValueBase(py::module_ & m) { return BatchTensor(self).requires_grad_(req); }) .def_property_readonly("grad", [](const TensorValueBase & self) { return BatchTensor(self).grad(); }); - ; } diff --git a/python/requirements.txt b/python/requirements.txt deleted file mode 100644 index d9cd539b30..0000000000 --- a/python/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -# -##### pip requirements ###### -# -pybind11 -pybind11-stubgen diff --git a/python/tests/CMakeLists.txt b/python/tests/CMakeLists.txt new file mode 100644 index 0000000000..d219e8c380 --- /dev/null +++ b/python/tests/CMakeLists.txt @@ -0,0 +1,18 @@ +# ---------------------------------------------------------------------------- +# Dependencies and 3rd party packages +# ---------------------------------------------------------------------------- +find_package(Python COMPONENTS Interpreter) +execute_process( + COMMAND_ERROR_IS_FATAL ANY + COMMAND ${Python_EXECUTABLE} ${NEML2_SOURCE_DIR}/scripts/check_python_dep.py ${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt +) + +# ---------------------------------------------------------------------------- +# Install test resources +# ---------------------------------------------------------------------------- +install(DIRECTORY . + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/python + COMPONENT Development + FILES_MATCHING + PATTERN "*.py" +) diff --git a/tests/python/base/test_HITParser.i b/python/tests/base/test_HITParser.i similarity index 100% rename from tests/python/base/test_HITParser.i rename to python/tests/base/test_HITParser.i diff --git a/tests/python/base/test_HITParser.py b/python/tests/base/test_HITParser.py similarity index 100% rename from tests/python/base/test_HITParser.py rename to python/tests/base/test_HITParser.py diff --git a/tests/python/models/test_Model.i b/python/tests/models/test_Model.i similarity index 100% rename from tests/python/models/test_Model.i rename to python/tests/models/test_Model.i diff --git a/tests/python/models/test_Model.py b/python/tests/models/test_Model.py similarity index 100% rename from tests/python/models/test_Model.py rename to python/tests/models/test_Model.py diff --git a/tests/python/models/test_ParameterStore.i b/python/tests/models/test_ParameterStore.i similarity index 100% rename from tests/python/models/test_ParameterStore.i rename to python/tests/models/test_ParameterStore.i diff --git a/tests/python/models/test_ParameterStore.py b/python/tests/models/test_ParameterStore.py similarity index 100% rename from tests/python/models/test_ParameterStore.py rename to python/tests/models/test_ParameterStore.py diff --git a/tests/requirements.txt b/python/tests/requirements.txt similarity index 100% rename from tests/requirements.txt rename to python/tests/requirements.txt diff --git a/tests/python/tensors/common.py b/python/tests/tensors/common.py similarity index 100% rename from tests/python/tensors/common.py rename to python/tests/tensors/common.py diff --git a/tests/python/tensors/test_BatchTensor.py b/python/tests/tensors/test_BatchTensor.py similarity index 100% rename from tests/python/tensors/test_BatchTensor.py rename to python/tests/tensors/test_BatchTensor.py diff --git a/tests/python/tensors/test_BatchTensorBase.py b/python/tests/tensors/test_BatchTensorBase.py similarity index 100% rename from tests/python/tensors/test_BatchTensorBase.py rename to python/tests/tensors/test_BatchTensorBase.py diff --git a/tests/python/tensors/test_FixedDimTensor.py b/python/tests/tensors/test_FixedDimTensor.py similarity index 100% rename from tests/python/tensors/test_FixedDimTensor.py rename to python/tests/tensors/test_FixedDimTensor.py diff --git a/tests/python/tensors/test_LabeledAxisAccessor.py b/python/tests/tensors/test_LabeledAxisAccessor.py similarity index 100% rename from tests/python/tensors/test_LabeledAxisAccessor.py rename to python/tests/tensors/test_LabeledAxisAccessor.py diff --git a/tests/python/tensors/test_Scalar.py b/python/tests/tensors/test_Scalar.py similarity index 100% rename from tests/python/tensors/test_Scalar.py rename to python/tests/tensors/test_Scalar.py diff --git a/requirements.txt b/requirements.txt index 0e6322036e..442b6d9853 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,5 +3,4 @@ # -r doc/requirements.txt -r scripts/requirements.txt --r python/requirements.txt --r tests/requirements.txt +-r python/tests/requirements.txt diff --git a/runner/CMakeLists.txt b/runner/CMakeLists.txt new file mode 100644 index 0000000000..dd1236044d --- /dev/null +++ b/runner/CMakeLists.txt @@ -0,0 +1,29 @@ +include(NEML2UnityGroup) + +message(STATUS "Configuring argparse") +FetchContent_MakeAvailable(argparse) + +file(GLOB_RECURSE srcs src/*.cxx) +add_executable(runner ${srcs}) +set_target_properties(runner PROPERTIES INSTALL_RPATH "${EXEC_DIR}/../lib;${Torch_LINK_DIRECTORIES}") +register_unity_group(runner .) +target_compile_options(runner PRIVATE -Wall -Wextra -pedantic -Werror) +target_link_libraries(runner PRIVATE neml2 argparse) + +option(NEML2_RUNNER_AS_PROFILER "Additionally link the runner against gperftools profiler" OFF) + +if(NEML2_RUNNER_AS_PROFILER) + FetchContent_Populate(gperftools) + add_subdirectory(${gperftools_SOURCE_DIR} ${gperftools_BINARY_DIR} EXCLUDE_FROM_ALL) + target_link_libraries(runner PRIVATE profiler) +endif() + +install(TARGETS runner) +install(DIRECTORY benchmark + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/benchmark + FILES_MATCHING + PATTERN "*.i" + PATTERN "*.pt" + PATTERN "*.vtest" + PATTERN "*.xml" +) diff --git a/tests/benchmark/chaboche/model.i b/runner/benchmark/chaboche/model.i similarity index 100% rename from tests/benchmark/chaboche/model.i rename to runner/benchmark/chaboche/model.i diff --git a/tests/benchmark/taylor_rolling_fcc/model.i b/runner/benchmark/taylor_rolling_fcc/model.i similarity index 99% rename from tests/benchmark/taylor_rolling_fcc/model.i rename to runner/benchmark/taylor_rolling_fcc/model.i index 1f2552ae68..573fc78a1e 100644 --- a/tests/benchmark/taylor_rolling_fcc/model.i +++ b/runner/benchmark/taylor_rolling_fcc/model.i @@ -89,7 +89,6 @@ times = 'times' prescribed_deformation_rate = 'deformation_rate' prescribed_vorticity = 'vorticity' - provide_vorticity = true ic_rot_names = 'state/orientation' ic_rot_values = 'initial_orientation' predictor = 'CP_PREVIOUS_STATE' diff --git a/tests/benchmark/taylor_single_orientation/model.i b/runner/benchmark/taylor_single_orientation/model.i similarity index 99% rename from tests/benchmark/taylor_single_orientation/model.i rename to runner/benchmark/taylor_single_orientation/model.i index a843abd829..a29fffd0ad 100644 --- a/tests/benchmark/taylor_single_orientation/model.i +++ b/runner/benchmark/taylor_single_orientation/model.i @@ -88,7 +88,6 @@ times = 'times' prescribed_deformation_rate = 'deformation_rate' prescribed_vorticity = 'vorticity' - provide_vorticity = true ic_rot_names = 'state/orientation' ic_rot_values = 'initial_orientation' predictor = 'CP_PREVIOUS_STATE' diff --git a/runner/src/main.cxx b/runner/src/main.cxx new file mode 100644 index 0000000000..95c227cd83 --- /dev/null +++ b/runner/src/main.cxx @@ -0,0 +1,101 @@ +// Copyright 2023, UChicago Argonne, LLC +// All Rights Reserved +// Software Name: NEML2 -- the New Engineering material Model Library, version 2 +// By: Argonne National Laboratory +// OPEN SOURCE LICENSE (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include "neml2/base/Parser.h" +#include "neml2/base/Factory.h" +#include "neml2/drivers/Driver.h" + +#include +#include + +int +main(int argc, char * argv[]) +{ + argparse::ArgumentParser program("runner"); + + // Positional arguments + program.add_argument("input").help("path to the input file"); + program.add_argument("driver").help("name of the driver in the input file"); + program.add_argument("additional_args") + .remaining() + .help("additional command-line arguments to pass to the input file parser"); + + // Optional arguments + program.add_argument("-t", "--time") + .help("output the elapsed wall time during model evaluation") + .flag(); + + try + { + // Parse cliargs + program.parse_args(argc, argv); + const auto input = program.get("input"); + const auto drivername = program.get("driver"); + + // Remaining args + std::vector args; + try + { + args = program.get>("additional_args"); + } + catch (std::logic_error & e) + { + } + std::ostringstream args_stream; + std::copy(args.begin(), args.end(), std::ostream_iterator(args_stream, " ")); + const auto additional_cliargs = args_stream.str(); + + // Run the model + try + { + neml2::load_model(input, additional_cliargs); + auto & driver = neml2::Factory::get_object("Drivers", drivername); + + if (program["--time"] == true) + { + auto t1 = std::chrono::high_resolution_clock::now(); + driver.run(); + auto t2 = std::chrono::high_resolution_clock::now(); + auto dt = std::chrono::duration_cast(t2 - t1).count(); + std::cout << "Elapsed wall time: " << dt << " ms" << std::endl; + } + else + driver.run(); + } + catch (const std::exception & err) + { + std::cerr << "An exception was raised while running the model:\n"; + std::cerr << err.what() << std::endl; + std::exit(1); + } + } + catch (const std::exception & err) + { + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); + } + + return 0; +} diff --git a/scripts/benchmark.sh b/scripts/benchmark.sh index 90a9c161e0..6d076fccd3 100755 --- a/scripts/benchmark.sh +++ b/scripts/benchmark.sh @@ -26,6 +26,6 @@ cd tests mkdir -p $3 -./benchmark_tests $1 --benchmark-samples $2 --use-colour no -o $3/log.txt +./benchmark $1 --benchmark-samples $2 --use-colour no -o $3/log.txt python ../scripts/extract_timings.py $3/log.txt $3/timings.csv python ../scripts/analyze_timings.py $3/timings.csv $3/timings.png diff --git a/scripts/check_copyright.py b/scripts/check_copyright.py index a0430d8b83..7a9473a61e 100755 --- a/scripts/check_copyright.py +++ b/scripts/check_copyright.py @@ -131,7 +131,7 @@ def update_heading_ondemand(path, copyright, prefix, modify): additional_files = {} exclude_dirs = ["extern"] - exclude_files = ["__init__.py"] + exclude_files = ["__init__.py", "setup.py"] rootdir = Path(".") diff --git a/scripts/check_python_dep.py b/scripts/check_python_dep.py new file mode 100755 index 0000000000..9a55983825 --- /dev/null +++ b/scripts/check_python_dep.py @@ -0,0 +1,105 @@ +#! /usr/bin/env python + +# Copyright 2023, UChicago Argonne, LLC +# All Rights Reserved +# Software Name: NEML2 -- the New Engineering material Model Library, version 2 +# By: Argonne National Laboratory +# OPEN SOURCE LICENSE (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import sys +import importlib.metadata +from pathlib import Path + + +def show_missing_reqs(missing_reqs): + print("-" * 79) + print("There are missing Python package dependencies:") + for missing_req in missing_reqs: + print(" {}".format(missing_req)) + print("They can be installed using `pip install -r requirements.txt`.") + print("-" * 79) + + +try: + from packaging.requirements import Requirement +except: + show_missing_reqs(["packaging"]) + + +def get_reqs(path): + """ + Recursively parse the requirements.txt to gather all dependencies + """ + reqs = [] + with open(path, "r") as f: + for req in f: + req = req.strip() + if req.startswith("#"): + continue + if req.startswith("-r"): + reqfile = req.split(" ")[1] + reqs += get_reqs(path.parent / reqfile) + else: + reqs.append(Requirement(req)) + return reqs + + +def _yield_missing_reqs(req: Requirement, current_extra: str = ""): + if req.marker and not req.marker.evaluate({"extra": current_extra}): + return + + try: + version = importlib.metadata.distribution(req.name).version + except importlib.metadata.PackageNotFoundError: # req not installed + yield req + else: + if req.specifier.contains(version): + for child_req in ( + importlib.metadata.metadata(req.name).get_all("Requires-Dist") or [] + ): + child_req_obj = Requirement(child_req) + + need_check, ext = False, None + for extra in req.extras: + if child_req_obj.marker and child_req_obj.marker.evaluate( + {"extra": extra} + ): + need_check = True + ext = extra + break + + if need_check: # check for extra reqs + yield from _yield_missing_reqs(child_req_obj, ext) + + else: # main version not match + yield req + + +if __name__ == "__main__": + reqfile = Path(sys.argv[1]) + + missing_reqs = [] + for req in get_reqs(reqfile): + missing_reqs += [missing_req for missing_req in _yield_missing_reqs(req)] + + if missing_reqs: + show_missing_reqs(missing_reqs) + exit(1) diff --git a/scripts/coverage.sh b/scripts/coverage.sh index fc5034288b..4cebca0739 100755 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -33,7 +33,7 @@ export SRC_DIR=$ROOT/src export COVERAGE_DIR=$ROOT/coverage lcov --gcov-tool gcov --capture --initial --directory $SRC_DIR --output-file $COVERAGE_DIR/initialize.info cd tests -./unit_tests || true +./unit/unit_tests || true cd .. lcov --gcov-tool gcov --capture --ignore-errors gcov,source --directory $SRC_DIR --output-file $COVERAGE_DIR/covered.info lcov --gcov-tool gcov --add-tracefile $COVERAGE_DIR/initialize.info --add-tracefile $COVERAGE_DIR/covered.info --output-file $COVERAGE_DIR/final.info diff --git a/scripts/fixup_pystub.py b/scripts/fixup_pystub.py new file mode 100755 index 0000000000..a0c7a405f6 --- /dev/null +++ b/scripts/fixup_pystub.py @@ -0,0 +1,37 @@ +#! /usr/bin/env python + +# Copyright 2023, UChicago Argonne, LLC +# All Rights Reserved +# Software Name: NEML2 -- the New Engineering material Model Library, version 2 +# By: Argonne National Laboratory +# OPEN SOURCE LICENSE (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +from pathlib import Path +import sys + +if __name__ == "__main__": + root = Path(sys.argv[1]) + for stub in root.glob("*.pyi"): + with open(stub) as f: + fixed = f.read().replace("neml2.", "") + + with open(stub, "w") as f: + f.write(fixed) diff --git a/scripts/syntax_to_md.py b/scripts/syntax_to_md.py index e9450f4f28..bd475b1f99 100755 --- a/scripts/syntax_to_md.py +++ b/scripts/syntax_to_md.py @@ -30,13 +30,6 @@ from pathlib import Path -def get_type(params): - for param in params: - for param_name, info in param.items(): - if param_name == "type": - return demangle(info["value"]) - - def demangle(type): type = type.replace( "std::__cxx11::basic_string, std::allocator >", @@ -47,43 +40,104 @@ def demangle(type): type = type.replace("std::", "") type = type.replace("at::", "") type = re.sub("CrossRef<(.+)>", r"\1", type) + type = type.replace("LabeledAxisAccessor", "VariableName") + return type def postprocess(value, type): if type == "bool": - value = str(bool(value)) + value = "true" if value else "false" return value -if __name__ == "__main__": - outfile = Path(sys.argv[2]) - outfile.parent.mkdir(parents=True, exist_ok=True) +def get_sections(syntax): + sections = [params["section"] for type, params in syntax.items()] + return list(dict.fromkeys(sections)) + +if __name__ == "__main__": with open(sys.argv[1], "r") as stream: syntax = yaml.safe_load(stream) - with open(sys.argv[2], "w") as stream: - stream.write("# Syntax Documentation {#syntax}\n\n") - stream.write("[TOC]\n\n") - for s in syntax: - for type, params in s.items(): - input_type = get_type(params) - stream.write("## {}\n\n".format(input_type)) - names = [] - types = [] - values = [] - for param in params: - for param_name, info in param.items(): + outdir = Path(sys.argv[2]) + outdir.mkdir(parents=True, exist_ok=True) + + logfile = Path(sys.argv[3]) + logfile.parent.mkdir(parents=True, exist_ok=True) + + with open(logfile, "w") as log: + missing = 0 + log.write("## Missing syntax\n") + sections = get_sections(syntax) + for section in sections: + with open((outdir / section.lower()).with_suffix(".md"), "w") as stream: + stream.write( + "# [{}] {{#{}}}\n\n".format(section, "syntax-" + section.lower()) + ) + stream.write("[TOC]\n\n") + stream.write("## Available objects and their input file syntax\n\n") + stream.write( + "Refer to [System Documentation](@ref system-{}) for detailed explanation about this system.\n\n".format( + section.lower() + ) + ) + for type, params in syntax.items(): + if params["section"] != section: + continue + input_type = demangle(params["type"]["value"]) + stream.write( + "### {} {{#{}}}\n\n".format(input_type, input_type.lower()) + ) + if params["doc"]: + stream.write("{}\n".format(params["doc"])) + else: + missing += 1 + + log.write( + " * '{}' is missing object description\n".format( + input_type + ) + ) + for param_name, info in params.items(): + if param_name == "section": + continue + if param_name == "doc": + continue if param_name == "name": continue if param_name == "type": continue + if info["suppressed"]: + continue + param_type = demangle(info["type"]) param_value = postprocess(info["value"], param_type) - stream.write("- {}\n".format(param_name)) - stream.write(" - **Type**: {}\n".format(param_type)) - if param_value != None: - stream.write(" - **Default**: {}\n".format(param_value)) - stream.write("\n") - stream.write("Details: [{}](@ref {})\n\n".format(input_type, type)) + stream.write("

\n") + if not info["doc"]: + stream.write( + " `{}`\n\n".format(param_name) + ) + missing += 1 + log.write( + " * '{}'.'{}' is missing option description\n".format( + input_type, param_name + ) + ) + else: + stream.write( + " `{}` {}\n\n".format( + param_name, info["doc"] + ) + ) + stream.write(" - Type: {}\n".format(param_type)) + if param_value: + stream.write(" - Default: {}\n".format(param_value)) + stream.write("
\n") + stream.write("\n") + stream.write( + "Detailed documentation [link](@ref {})\n\n".format(type) + ) + + if missing == 0: + log.write("Nothing, good job! :purple_heart:") diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000..668026d6d6 --- /dev/null +++ b/setup.py @@ -0,0 +1,71 @@ +import os +import re +import subprocess +import sys +from pathlib import Path + +from setuptools import Extension, setup +from setuptools.command.build_ext import build_ext + + +# A CMakeExtension needs a sourcedir instead of a file list. +# The name must be the _single_ output extension from the CMake build. +# If you need multiple extensions, see scikit-build. +class CMakeExtension(Extension): + def __init__(self, name: str, sourcedir: str = "") -> None: + super().__init__(name, sources=[]) + self.sourcedir = os.fspath(Path(sourcedir).resolve()) + + +class CMakeBuild(build_ext): + def build_extension(self, ext: CMakeExtension) -> None: + # Give up on windows + if self.compiler.compiler_type == "msvc": + raise RuntimeError("MSVC not supported") + + # Must be in this form due to bug in .resolve() only fixed in Python 3.10+ + ext_fullpath = Path.cwd() / self.get_ext_fullpath(ext.name) + extdir = ext_fullpath.parent.resolve() / "neml2" + + # Configure arguments + cmake_args = [ + "-DCMAKE_INSTALL_PREFIX={}".format(extdir), + "-DCMAKE_BUILD_TYPE=RelWithDebInfo", + "-DCMAKE_UNITY_BUILD=ON", + "-DNEML2_PYBIND=ON", + "-DNEML2_TESTS=OFF", + "-DNEML2_RUNNER=OFF", + "-DNEML2_DOC=OFF", + ] + + # Build arguments + build_args = ["-j{}".format(os.environ.get("BUILD_JOBS", "1"))] + + # Install arguments + install_args = [] + + if sys.platform.startswith("darwin"): + # Cross-compile support for macOS - respect ARCHFLAGS if set + archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", "")) + if archs: + cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))] + + build_temp = Path(self.build_temp) + if not build_temp.exists(): + build_temp.mkdir(parents=True) + + subprocess.run( + ["cmake", ext.sourcedir, *cmake_args], cwd=build_temp, check=True + ) + subprocess.run( + ["cmake", "--build", ".", *build_args], cwd=build_temp, check=True + ) + subprocess.run( + ["cmake", "--install", ".", *install_args], cwd=build_temp, check=True + ) + + +setup( + ext_modules=[CMakeExtension("neml2")], + cmdclass={"build_ext": CMakeBuild}, +) diff --git a/src/neml2/CMakeLists.txt b/src/neml2/CMakeLists.txt index 73f199dfc8..c7a73edd94 100644 --- a/src/neml2/CMakeLists.txt +++ b/src/neml2/CMakeLists.txt @@ -3,12 +3,12 @@ include(NEML2UnityGroup) # Add all the source files file(GLOB_RECURSE SRCS *.cxx) add_library(neml2 SHARED ${SRCS}) +set_target_properties(neml2 PROPERTIES INSTALL_RPATH "${EXEC_DIR};${Torch_LINK_DIRECTORIES}") # Make scalar type configurable: if(NOT NEML2_DTYPE) set(NEML2_DTYPE "Float64" CACHE STRING "Default NEML2 scalar type." FORCE) - set_property(CACHE NEML2_DTYPE PROPERTY STRINGS - "UInt8" "Int8" "Int16" "Int32" "Int64" "Float16" "Float32" "Float64") + set_property(CACHE NEML2_DTYPE PROPERTY STRINGS "Float16" "Float32" "Float64") endif() message(STATUS "Configuring with default scalar type: ${NEML2_DTYPE}") @@ -16,8 +16,7 @@ message(STATUS "Configuring with default scalar type: ${NEML2_DTYPE}") # Also want to configure an int type for specialized int tensors if(NOT NEML2_INT_DTYPE) set(NEML2_INT_DTYPE "Int64" CACHE STRING "Default NEML2 integer scalar type." FORCE) - set_property(CACHE NEML2_INT_DTYPE PROPERTY STRINGS - "Int8" "Int16" "Int32" "Int64") + set_property(CACHE NEML2_INT_DTYPE PROPERTY STRINGS "Int8" "Int16" "Int32" "Int64") endif() message(STATUS "Configuring with default integer scalar type: ${NEML2_INT_DTYPE}") @@ -31,7 +30,7 @@ configure_file( target_compile_options(neml2 PRIVATE -Wall -Wextra -pedantic -Werror) # Group source files together if UNITY build is requested -register_unity_group(neml2 "NEML2" .) +register_unity_group(neml2 .) # NEML2 headers file(GLOB_RECURSE _NEML2_HEADERS ${NEML2_SOURCE_DIR}/include/*.h) @@ -43,24 +42,34 @@ target_sources(neml2 FILES ${_NEML2_HEADERS} ) +install(TARGETS neml2 COMPONENT Development FILE_SET HEADERS) # torch target_include_directories(neml2 SYSTEM PUBLIC ${Torch_INCLUDE_DIRECTORIES}) target_link_directories(neml2 PUBLIC ${Torch_LINK_DIRECTORIES}) target_link_libraries(neml2 PUBLIC ${Torch_LIBRARIES}) -if(Torch_CXX11_ABI) - target_compile_definitions(neml2 PUBLIC _GLIBCXX_USE_CXX11_ABI=1) -else() - target_compile_definitions(neml2 PUBLIC _GLIBCXX_USE_CXX11_ABI=0) -endif() - # hit for parsing -add_subdirectory("${NEML2_SOURCE_DIR}/extern/hit" "${NEML2_BINARY_DIR}/extern/hit") -target_link_libraries(neml2 PUBLIC hit) +message(STATUS "Configuring hit") +FetchContent_MakeAvailable(hit) + +add_library(hit SHARED + ${hit_SOURCE_DIR}/parse.cc + ${hit_SOURCE_DIR}/lex.cc + ${hit_SOURCE_DIR}/braceexpr.cc +) -install( - TARGETS neml2 +set_target_properties(hit PROPERTIES UNITY_BUILD OFF) +target_include_directories(hit PUBLIC ${hit_SOURCE_DIR}/..) +target_sources(hit + PUBLIC FILE_SET HEADERS - COMPONENT Development + BASE_DIRS ${hit_SOURCE_DIR}/.. + FILES + ${hit_SOURCE_DIR}/braceexpr.h + ${hit_SOURCE_DIR}/hit.h + ${hit_SOURCE_DIR}/lex.h + ${hit_SOURCE_DIR}/parse.h ) +install(TARGETS hit COMPONENT Development FILE_SET HEADERS) +target_link_libraries(neml2 PUBLIC hit) diff --git a/src/neml2/base/NEML2Object.cxx b/src/neml2/base/NEML2Object.cxx index e4a134c62a..b40fc95a78 100644 --- a/src/neml2/base/NEML2Object.cxx +++ b/src/neml2/base/NEML2Object.cxx @@ -30,6 +30,7 @@ NEML2Object::expected_options() { auto options = OptionSet(); options.set("_host") = nullptr; + options.set("_host").suppressed() = true; return options; } diff --git a/src/neml2/base/OptionSet.cxx b/src/neml2/base/OptionSet.cxx index f54e5213d9..6b8572d1de 100644 --- a/src/neml2/base/OptionSet.cxx +++ b/src/neml2/base/OptionSet.cxx @@ -89,11 +89,28 @@ OptionSet::print(std::ostream & os) const { OptionSet::const_iterator it = _values.begin(); + os << " section: " << section() << '\n'; + if (doc().empty()) + os << " doc:\n"; + else + { + os << " doc: |-\n"; + os << " " << doc() << '\n'; + } + while (it != _values.end()) { - os << " - " << it->first << ":\n"; - os << " type: " << it->second->type() << '\n'; - os << " value: "; + os << " " << it->first << ":\n"; + os << " type: " << it->second->type() << '\n'; + if (it->second->doc().empty()) + os << " doc:\n"; + else + { + os << " doc: |-\n"; + os << " " << it->second->doc() << '\n'; + } + os << " suppressed: " << it->second->suppressed() << '\n'; + os << " value: "; it->second->print(os); if (++it != _values.end()) os << '\n'; diff --git a/src/neml2/base/Parser.cxx b/src/neml2/base/Parser.cxx index e1decf2cf6..cb4d1c28f7 100644 --- a/src/neml2/base/Parser.cxx +++ b/src/neml2/base/Parser.cxx @@ -21,8 +21,36 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. + #include "neml2/base/Parser.h" +#include "neml2/base/Factory.h" +#include "neml2/base/HITParser.h" namespace neml2 { +void +load_model(const std::string & path, const std::string & additional_input, ParserType ptype) +{ + // We are being forward looking here + if (ptype == ParserType::AUTO) + { + if (utils::end_with(path, ".i")) + ptype = ParserType::HIT; + else if (utils::end_with(path, ".xml")) + ptype = ParserType::XML; + else if (utils::end_with(path, ".yml")) + ptype = ParserType::YAML; + } + + // but for now we only support HIT + if (ptype == ParserType::HIT) + { + HITParser parser; + + Factory::clear(); + Factory::load(parser.parse(path, additional_input)); + } + else + neml_assert(false, "Unsupported parser type"); +} } // namespace neml2 diff --git a/src/neml2/base/Registry.cxx b/src/neml2/base/Registry.cxx index 46d7d5f0dd..fcd3dbc609 100644 --- a/src/neml2/base/Registry.cxx +++ b/src/neml2/base/Registry.cxx @@ -34,6 +34,13 @@ Registry::get() return registry_singleton; } +std::map +Registry::expected_options() +{ + auto & reg = get(); + return reg._expected_options; +} + OptionSet Registry::expected_options(const std::string & name) { @@ -45,6 +52,13 @@ Registry::expected_options(const std::string & name) return reg._expected_options.at(name); } +std::string +Registry::syntax_type(const std::string & type) +{ + auto & reg = get(); + return reg._syntax_type[type]; +} + BuildPtr Registry::builder(const std::string & name) { @@ -56,20 +70,6 @@ Registry::builder(const std::string & name) return reg._objects.at(name); } -// LCOV_EXCL_START -void -Registry::print(std::ostream & os) -{ - auto & reg = get(); - for (auto [type, options] : reg._expected_options) - { - options.set("type") = type; - os << "- " << reg._syntax_type[type] << ":\n"; - os << options << "\n"; - } -} -// LCOV_EXCL_STOP - void Registry::add_inner(const std::string & name, const std::string & type, diff --git a/src/neml2/drivers/Driver.cxx b/src/neml2/drivers/Driver.cxx index 861e00cf0f..3a1c6b0964 100644 --- a/src/neml2/drivers/Driver.cxx +++ b/src/neml2/drivers/Driver.cxx @@ -30,7 +30,11 @@ OptionSet Driver::expected_options() { OptionSet options = NEML2Object::expected_options(); + options.section() = "Drivers"; + options.set("verbose") = false; + options.set("verbose").doc() = "Whether to output additional logging information"; + return options; } diff --git a/src/neml2/drivers/TransientDriver.cxx b/src/neml2/drivers/TransientDriver.cxx index 050738f8cc..22ee9f5a38 100644 --- a/src/neml2/drivers/TransientDriver.cxx +++ b/src/neml2/drivers/TransientDriver.cxx @@ -34,23 +34,66 @@ OptionSet TransientDriver::expected_options() { OptionSet options = Driver::expected_options(); + options.set("model"); + options.set("model").doc() = "The material model to be updated by the driver"; + options.set>("times"); + options.set("times").doc() = + "Time steps to perform the material update. The times tensor must have exactly 2 dimensions. " + "The first dimension represents time steps, and the second dimension represents batches " + "(i.e., how many material models to update simultaneously)."; + options.set("time") = VariableName("forces", "t"); + options.set("time").doc() = "Time"; + options.set("predictor") = "PREVIOUS_STATE"; + options.set("predictor").doc() = + "Predictor used to set the initial guess for each time step. Options are PREVIOUS_STATE, " + "LINEAR_EXTRAPOLATION, CP_PREVIOUS_STATE, and CP_LINEAR_EXTRAPOLATION. The options prefixed " + "with 'CP_' are specifically designed for crystal plasticity models."; + options.set("cp_elastic_scale") = 1.0; + options.set("cp_elastic_scale").doc() = "Elastic step scale factor used in the 'CP_' predictors"; + options.set("save_as"); + options.set("save_as").doc() = + "File path (absolute or relative to the working directory) to store the results"; + options.set("show_parameters") = false; + options.set("show_parameters").doc() = "Whether to show model parameters at the beginning"; + options.set("show_input_axis") = false; + options.set("show_input_axis").doc() = "Whether to show model input axis at the beginning"; + options.set("show_output_axis") = false; + options.set("show_output_axis").doc() = "Whether to show model output axis at the beginning"; + options.set("device") = "cpu"; + options.set("device").doc() = + "Device on which to evaluate the material model. The string supplied must follow the " + "following schema: (cpu|cuda)[:] where cpu or cuda specifies the device type, " + "and : optionally specifies a device index. For example, device='cpu' sets the " + "target compute device to be CPU, and device='cuda:1' sets the target compute device to be " + "CUDA with device ID 1."; options.set>("ic_scalar_names"); + options.set("ic_scalar_names").doc() = "Apply initial conditions to these Scalar variables"; + options.set>>("ic_scalar_values"); + options.set("ic_scalar_values").doc() = "Initial condition values for the Scalar variables"; + options.set>("ic_rot_names"); + options.set("ic_rot_names").doc() = "Apply initial conditions to these Rot variables"; + options.set>>("ic_rot_values"); + options.set("ic_rot_values").doc() = "Initial condition values for the Rot variables"; + options.set>("ic_sr2_names"); + options.set("ic_sr2_names").doc() = "Apply initial conditions to these SR2 variables"; + options.set>>("ic_sr2_values"); + options.set("ic_sr2_values").doc() = "Initial condition values for the SR2 variables"; return options; } diff --git a/src/neml2/drivers/solid_mechanics/LargeDeformationIncrementalSolidMechanicsDriver.cxx b/src/neml2/drivers/solid_mechanics/LargeDeformationIncrementalSolidMechanicsDriver.cxx index b2282b4529..0506094d9d 100644 --- a/src/neml2/drivers/solid_mechanics/LargeDeformationIncrementalSolidMechanicsDriver.cxx +++ b/src/neml2/drivers/solid_mechanics/LargeDeformationIncrementalSolidMechanicsDriver.cxx @@ -32,15 +32,32 @@ OptionSet LargeDeformationIncrementalSolidMechanicsDriver::expected_options() { OptionSet options = TransientDriver::expected_options(); + options.doc() = "Driver for large deformation solid mechanics material model. The material model " + "is updated *incrementally*."; + options.set("control") = "STRAIN"; + options.set("control").doc() = "External control of the material update. Options are STRAIN and " + "STRESS, for strain control and stress control, respectively."; + options.set("deformation_rate") = VariableName("forces", "deformation_rate"); + options.set("deformation_rate").doc() = "Deformation rate"; + options.set("cauchy_stress_rate") = VariableName("forces", "cauchy_stress_rate"); + options.set("cauchy_stress_rate").doc() = "Cauchy stress rate"; + + options.set("vorticity") = VariableName("forces", "vorticity"); + options.set("vorticity").doc() = "Vorticity"; + options.set>("prescribed_deformation_rate"); + options.set("prescribed_deformation_rate").doc() = + "Prescribed deformation rate (when control = STRAIN)"; + options.set>("prescribed_cauchy_stress_rate"); + options.set("prescribed_cauchy_stress_rate").doc() = + "Prescribed cauchy stress rate (when control = STRESS)"; - options.set("vorticity") = VariableName("forces", "vorticity"); - options.set("provide_vorticity") = false; - options.set>("prescribed_vorticity") = "vorticity"; + options.set>("prescribed_vorticity"); + options.set("prescribed_vorticity").doc() = "Prescribed vorticity"; return options; } @@ -49,7 +66,12 @@ LargeDeformationIncrementalSolidMechanicsDriver::LargeDeformationIncrementalSoli const OptionSet & options) : TransientDriver(options), _control(options.get("control")), - _vorticity_name(options.get("vorticity")) + _vorticity_name(options.get("vorticity")), + _vorticity_prescribed( + !options.get>("prescribed_vorticity").raw().empty()), + _vorticity(_vorticity_prescribed + ? WR2(options.get>("prescribed_vorticity")) + : WR2::zeros(_driving_force.batch_sizes())) { if (_control == "STRAIN") { @@ -66,11 +88,6 @@ LargeDeformationIncrementalSolidMechanicsDriver::LargeDeformationIncrementalSoli throw NEMLException("Unsupported control type."); // LCOV_EXCL_STOP - if (options.get("provide_vorticity")) - _vorticity = WR2(options.get>("prescribed_vorticity")); - else - _vorticity = WR2::zeros(_driving_force.batch_sizes()); - _driving_force = _driving_force.to(_device); _vorticity = _vorticity.to(_device); diff --git a/src/neml2/drivers/solid_mechanics/SolidMechanicsDriver.cxx b/src/neml2/drivers/solid_mechanics/SolidMechanicsDriver.cxx index 8fce1a8c01..4ee9e98c16 100644 --- a/src/neml2/drivers/solid_mechanics/SolidMechanicsDriver.cxx +++ b/src/neml2/drivers/solid_mechanics/SolidMechanicsDriver.cxx @@ -32,13 +32,31 @@ OptionSet SolidMechanicsDriver::expected_options() { OptionSet options = TransientDriver::expected_options(); + options.doc() = + "Driver for small deformation solid mechanics material model with optional thermal coupling."; + options.set("control") = "STRAIN"; + options.set("control").doc() = "External control of the material update. Options are STRAIN and " + "STRESS, for strain control and stress control, respectively."; + options.set("total_strain") = VariableName("forces", "E"); + options.set("total_strain").doc() = "Total strain"; + options.set("cauchy_stress") = VariableName("forces", "S"); + options.set("cauchy_stress").doc() = "Cauchy stress"; + options.set("temperature") = VariableName("forces", "T"); + options.set("temperature").doc() = "Temperature"; + options.set>("prescribed_strains"); + options.set("prescribed_strains").doc() = "Prescribed strain (when control = STRAIN)"; + options.set>("prescribed_stresses"); + options.set("prescribed_stresses").doc() = "Prescribed stress (when control = STRESS)"; + options.set>("prescribed_temperatures"); + options.set("prescribed_temperatures").doc() = "Prescribed temperature"; + return options; } diff --git a/src/neml2/models/ArrheniusParameter.cxx b/src/neml2/models/ArrheniusParameter.cxx index 40075828ef..3a6af7aed5 100644 --- a/src/neml2/models/ArrheniusParameter.cxx +++ b/src/neml2/models/ArrheniusParameter.cxx @@ -32,10 +32,23 @@ OptionSet ArrheniusParameter::expected_options() { OptionSet options = NonlinearParameter::expected_options(); + options.doc() = "Define the nonlinear parameter as a function of temperature according to the " + "Arrhenius law \\f$ p = p_0 \\exp \\left( -\\frac{Q}{RT} \\right) \\f$, where " + "\\f$ p_0 \\f$ is the reference value, \\f$ Q \\f$ is the activation energy, " + "\\f$ R \\f$ is the ideal gas constant, and \\f$ T \\f$ is the temperature."; + options.set>("reference_value"); + options.set("reference_value").doc() = "Reference value"; + options.set>("activation_energy"); + options.set("activation_energy").doc() = "Activation energy"; + options.set("ideal_gas_constant"); + options.set("ideal_gas_constant").doc() = "The ideal gas constant"; + options.set("temperature") = VariableName("forces", "T"); + options.set("temperature").doc() = "Temperature"; + return options; } diff --git a/src/neml2/models/BackwardEulerTimeIntegration.cxx b/src/neml2/models/BackwardEulerTimeIntegration.cxx index bb55b3c730..cd4521bc09 100644 --- a/src/neml2/models/BackwardEulerTimeIntegration.cxx +++ b/src/neml2/models/BackwardEulerTimeIntegration.cxx @@ -35,9 +35,23 @@ OptionSet BackwardEulerTimeIntegration::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = + "Define the backward Euler time integration residual \\f$ r = s - s_n - (t - t_n) \\dot{s} " + "\\f$, where \\f$s\\f$ is the variable being integrated, \\f$\\dot{s}\\f$ is the variable " + "rate, and \\f$t\\f$ is time. Subscripts \\f$n\\f$ denote quantities from the previous time " + "step."; + + NonlinearSystem::enable_automatic_scaling(options); + options.set("variable"); + options.set("variable").doc() = "Variable being integrated"; + options.set("variable_rate"); + options.set("variable_rate").doc() = "Variable rate"; + options.set("time") = VariableName("t"); + options.set("time").doc() = "Time"; + return options; } diff --git a/src/neml2/models/ComposedModel.cxx b/src/neml2/models/ComposedModel.cxx index a7d83d3027..b73f3e4bf4 100644 --- a/src/neml2/models/ComposedModel.cxx +++ b/src/neml2/models/ComposedModel.cxx @@ -32,9 +32,26 @@ OptionSet ComposedModel::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = + "Compose multiple models together to form a single model. The composed model can then be " + "treated as a new model and composed with others. The [system documentation](@ref " + "model-composition) provides in-depth explanation on how the models are composed together."; + + NonlinearSystem::enable_automatic_scaling(options); + options.set>("models"); + options.set("models").doc() = "Models being composed together"; + options.set>("additional_outputs"); + options.set("additional_outputs").doc() = + "Extra output variables to be extracted from the composed model in addition to the ones " + "identified through dependency resolution."; + options.set>("priority"); + options.set("priority").doc() = + "Priorities of models in decreasing order. A model with higher priority will be evaluated " + "first. This is useful for breaking cyclic dependency."; + return options; } @@ -98,8 +115,8 @@ ComposedModel::check_AD_limitation() const { if (_AD_1st_deriv || _AD_2nd_deriv) throw NEMLException( - "ComposedModel does not use automatic differentiation. use_AD_first_derivative and " - "use_AD_second_derivative should be set to false."); + "ComposedModel does not use automatic differentiation. _use_AD_first_derivative and " + "_use_AD_second_derivative should be set to false."); } void diff --git a/src/neml2/models/CopyVariable.cxx b/src/neml2/models/CopyVariable.cxx index 45579a2dba..d2d9a91ccc 100644 --- a/src/neml2/models/CopyVariable.cxx +++ b/src/neml2/models/CopyVariable.cxx @@ -34,8 +34,14 @@ OptionSet CopyVariable::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Copy the value from one variable to another."; + options.set("from"); + options.set("from").doc() = "Variable to copy value from"; + options.set("to"); + options.set("to").doc() = "Variable to copy value to"; + return options; } diff --git a/src/neml2/models/Data.cxx b/src/neml2/models/Data.cxx index b15d58c30c..7adafeba66 100644 --- a/src/neml2/models/Data.cxx +++ b/src/neml2/models/Data.cxx @@ -30,6 +30,7 @@ OptionSet Data::expected_options() { auto options = NEML2Object::expected_options(); + options.section() = "Data"; return options; } diff --git a/src/neml2/models/ForceRate.cxx b/src/neml2/models/ForceRate.cxx index 5af7a64fe1..75f6ce2cfc 100644 --- a/src/neml2/models/ForceRate.cxx +++ b/src/neml2/models/ForceRate.cxx @@ -35,8 +35,16 @@ OptionSet ForceRate::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Calculate the first order discrete time derivative of a force variable as \\f$ " + "\\dot{f} = \\frac{f-f_n}{t-t_n} \\f$, where \\f$ f \\f$ is the force variable, " + "and \\f$ t \\f$ is time."; + options.set("force"); + options.set("force").doc() = "The force variable to take time derivative with"; + options.set("time") = {"t"}; + options.set("time").doc() = "Time"; + return options; } diff --git a/src/neml2/models/ForwardEulerTimeIntegration.cxx b/src/neml2/models/ForwardEulerTimeIntegration.cxx index 60f5db73f5..d40fe4cabd 100644 --- a/src/neml2/models/ForwardEulerTimeIntegration.cxx +++ b/src/neml2/models/ForwardEulerTimeIntegration.cxx @@ -35,8 +35,18 @@ OptionSet ForwardEulerTimeIntegration::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = + "Perform forward Euler time integration defined as \\f$ s = s_n + (t - t_n) \\dot{s} " + "\\f$, where \\f$s\\f$ is the variable being integrated, \\f$\\dot{s}\\f$ is the variable " + "rate, and \\f$t\\f$ is time. Subscripts \\f$n\\f$ denote quantities from the previous time " + "step."; + options.set("variable"); + options.set("variable").doc() = "Variable being integrated"; + options.set("time") = VariableName("t"); + options.set("time").doc() = "Time"; + return options; } diff --git a/src/neml2/models/ImplicitUpdate.cxx b/src/neml2/models/ImplicitUpdate.cxx index 3d2414bdaf..7f6f382e77 100644 --- a/src/neml2/models/ImplicitUpdate.cxx +++ b/src/neml2/models/ImplicitUpdate.cxx @@ -33,8 +33,16 @@ OptionSet ImplicitUpdate::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = + "Update an implicit model by solving the underlying implicit system of equations."; + options.set("implicit_model"); + options.set("implicit_model").doc() = + "The implicit model defining the implicit system of equations to be solved"; + options.set("solver"); + options.set("solver").doc() = "Solver used to solve the implicit system"; + return options; } diff --git a/src/neml2/models/LinearInterpolation.cxx b/src/neml2/models/LinearInterpolation.cxx index f77827cda6..dfaec5b879 100644 --- a/src/neml2/models/LinearInterpolation.cxx +++ b/src/neml2/models/LinearInterpolation.cxx @@ -37,6 +37,7 @@ OptionSet LinearInterpolation::expected_options() { OptionSet options = Interpolation::expected_options(); + options.doc() += " This object performs a _linear interpolation_."; return options; } diff --git a/src/neml2/models/Model.cxx b/src/neml2/models/Model.cxx index 818277a5ea..f9a132e4f1 100644 --- a/src/neml2/models/Model.cxx +++ b/src/neml2/models/Model.cxx @@ -33,10 +33,20 @@ Model::expected_options() { OptionSet options = Data::expected_options(); options += NonlinearSystem::expected_options(); - options.set("use_AD_first_derivative") = false; - options.set("use_AD_second_derivative") = false; + NonlinearSystem::disable_automatic_scaling(options); + + options.section() = "Models"; + + options.set("_use_AD_first_derivative") = false; + options.set("_use_AD_second_derivative") = false; options.set("_extra_derivative_order") = 0; options.set("_nonlinear_system") = false; + + options.set("_extra_derivative_order").suppressed() = true; + options.set("_nonlinear_system").suppressed() = true; + options.set("_use_AD_first_derivative").suppressed() = true; + options.set("_use_AD_second_derivative").suppressed() = true; + return options; } @@ -45,8 +55,8 @@ Model::Model(const OptionSet & options) ParameterStore(options, this), VariableStore(options, this), NonlinearSystem(options), - _AD_1st_deriv(options.get("use_AD_first_derivative")), - _AD_2nd_deriv(options.get("use_AD_second_derivative")), + _AD_1st_deriv(options.get("_use_AD_first_derivative")), + _AD_2nd_deriv(options.get("_use_AD_second_derivative")), _options(default_tensor_options()), _deriv_order(-1), _extra_deriv_order(options.get("_extra_derivative_order")), diff --git a/src/neml2/models/RotationMatrix.cxx b/src/neml2/models/RotationMatrix.cxx index 46ed019a9f..c54bc9ab13 100644 --- a/src/neml2/models/RotationMatrix.cxx +++ b/src/neml2/models/RotationMatrix.cxx @@ -32,8 +32,15 @@ OptionSet RotationMatrix::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = + "Convert a Rot (rotation represented in Rodrigues format) to R2 (a full rotation matrix)."; + options.set("from"); + options.set("from").doc() = "Rot to convert"; + options.set("to"); + options.set("to").doc() = "R2 to store the resulting rotation matrix"; + return options; } diff --git a/src/neml2/models/SR2Invariant.cxx b/src/neml2/models/SR2Invariant.cxx index 38cf72cbb5..82a2e8df01 100644 --- a/src/neml2/models/SR2Invariant.cxx +++ b/src/neml2/models/SR2Invariant.cxx @@ -33,9 +33,17 @@ OptionSet SR2Invariant::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Calculate the invariant of a symmetric second order tensor (of type SR2)."; + options.set("tensor"); + options.set("tensor").doc() = "SR2 which is used to calculate the invariant of"; + options.set("invariant"); + options.set("invariant").doc() = "Invariant"; + options.set("invariant_type"); + options.set("invariant_type").doc() = "Type of invariant. Options are I1, I2, and VONMISES."; + return options; } diff --git a/src/neml2/models/StateRate.cxx b/src/neml2/models/StateRate.cxx index 7efdb018dd..5e92cc7162 100644 --- a/src/neml2/models/StateRate.cxx +++ b/src/neml2/models/StateRate.cxx @@ -35,8 +35,16 @@ OptionSet StateRate::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Calculate the first order discrete time derivative of a state variable as \\f$ " + "\\dot{s} = \\frac{s-s_n}{t-t_n} \\f$, where \\f$ s \\f$ is the state variable, " + "and \\f$ t \\f$ is time."; + options.set("state"); + options.set("state").doc() = "The state variable to take time derivative with"; + options.set("time") = VariableName("t"); + options.set("time").doc() = "Time"; + return options; } diff --git a/src/neml2/models/SumModel.cxx b/src/neml2/models/SumModel.cxx index 7a69b9dbae..a039f6c3d3 100644 --- a/src/neml2/models/SumModel.cxx +++ b/src/neml2/models/SumModel.cxx @@ -34,10 +34,24 @@ template OptionSet SumModel::expected_options() { + // This is the only way of getting tensor type in a static method like this... + // Trim 6 chars to remove 'neml2::' + auto tensor_type = utils::demangle(typeid(T).name()).substr(7); + OptionSet options = Model::expected_options(); + options.doc() = "Calculate linear combination of multiple " + tensor_type + + " tensors as \\f$ u = c_i v_i \\f$ (Einstein summation assumed), where \\f$ c_i " + "\\f$ are the coefficients, and \\f$ v_i \\f$ are the variables to be summed."; + options.set>("from_var"); + options.set("from_var").doc() = tensor_type + " tensors to be summed"; + options.set("to_var"); + options.set("to_var").doc() = "The sum"; + options.set>>("coefficients") = {}; + options.set("coefficients").doc() = "Weights associated with each variable"; + return options; } diff --git a/src/neml2/models/WR2ExplicitExponentialTimeIntegration.cxx b/src/neml2/models/WR2ExplicitExponentialTimeIntegration.cxx index be456d1909..90697a7e92 100644 --- a/src/neml2/models/WR2ExplicitExponentialTimeIntegration.cxx +++ b/src/neml2/models/WR2ExplicitExponentialTimeIntegration.cxx @@ -33,8 +33,16 @@ OptionSet WR2ExplicitExponentialTimeIntegration::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Perform explicit discrete exponential time integration of a rotation. The " + "update can be written as \\f$ s = \\exp\\left[ (t-t_n)\\dot{s}\\right] \\circ " + "s_n \\f$, where \\f$ \\circ \\f$ denotes the rotation operator."; + options.set("variable"); + options.set("variable").doc() = "Variable being integrated"; + options.set("time") = VariableName("t"); + options.set("time").doc() = "Time"; + return options; } diff --git a/src/neml2/models/WR2ImplicitExponentialTimeIntegration.cxx b/src/neml2/models/WR2ImplicitExponentialTimeIntegration.cxx index 8ae1cc86c5..7d3cb811fd 100644 --- a/src/neml2/models/WR2ImplicitExponentialTimeIntegration.cxx +++ b/src/neml2/models/WR2ImplicitExponentialTimeIntegration.cxx @@ -37,8 +37,19 @@ OptionSet WR2ImplicitExponentialTimeIntegration::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Define the implicit discrete exponential time integration residual of a " + "rotation variable. The residual can be written as \\f$ r = s - \\exp\\left[ " + "(t-t_n)\\dot{s}\\right] \\circ s_n \\f$, where \\f$ \\circ \\f$ denotes the " + "rotation operator."; + + NonlinearSystem::enable_automatic_scaling(options); + options.set("variable"); + options.set("variable").doc() = "Variable being integrated"; + options.set("time") = VariableName("t"); + options.set("time").doc() = "Time"; + return options; } diff --git a/src/neml2/models/crystallography/CrystalGeometry.cxx b/src/neml2/models/crystallography/CrystalGeometry.cxx index 573b1a9d89..cb0e9bf13c 100644 --- a/src/neml2/models/crystallography/CrystalGeometry.cxx +++ b/src/neml2/models/crystallography/CrystalGeometry.cxx @@ -40,10 +40,22 @@ OptionSet CrystalGeometry::expected_options() { OptionSet options = Data::expected_options(); + + options.doc() = + "A Data object storing basic crystallographic information for a given crystal system."; + options.set>("crystal_class"); + options.set("crystal_class").doc() = "The set of symmetry operations defining the crystal class."; + options.set>("lattice_vectors"); + options.set("lattice_vectors").doc() = + "The three lattice vectors defining the crystal translational symmetry"; + options.set>("slip_directions"); + options.set("slip_directions").doc() = "A list of Miller indices defining the slip directions"; + options.set>("slip_planes"); + options.set("slip_planes").doc() = "A list of Miller indices defining the slip planes"; return options; } diff --git a/src/neml2/models/crystallography/CubicCrystal.cxx b/src/neml2/models/crystallography/CubicCrystal.cxx index 0a6261502a..16d55da49c 100644 --- a/src/neml2/models/crystallography/CubicCrystal.cxx +++ b/src/neml2/models/crystallography/CubicCrystal.cxx @@ -38,11 +38,14 @@ OptionSet CubicCrystal::expected_options() { OptionSet options = CrystalGeometry::expected_options(); + options.doc() = + "A specialization of the general CrystalGeometry class defining a cubic crystal system."; options.set("crystal_class").suppressed() = true; options.set("lattice_vectors").suppressed() = true; options.set>("lattice_parameter"); + options.set("lattice_parameter").doc() = "The lattice parameter"; return options; } @@ -56,4 +59,4 @@ CubicCrystal::CubicCrystal(const OptionSet & options) } } // namespace crystallography -} // namespace neml2 \ No newline at end of file +} // namespace neml2 diff --git a/src/neml2/models/crystallography/user_tensors/FillMillerIndex.cxx b/src/neml2/models/crystallography/user_tensors/FillMillerIndex.cxx index eb020148e9..051736bed1 100644 --- a/src/neml2/models/crystallography/user_tensors/FillMillerIndex.cxx +++ b/src/neml2/models/crystallography/user_tensors/FillMillerIndex.cxx @@ -34,14 +34,18 @@ register_NEML2_object(FillMillerIndex); OptionSet FillMillerIndex::expected_options() { - OptionSet options = NEML2Object::expected_options(); + OptionSet options = UserTensor::expected_options(); + options.doc() = "Fills a tensor of Miller indices from a list of integers. Use -1 instead of " + "\\f$ \\bar{1} \\f$."; + options.set>("values"); + options.set("values").doc() = "List of integers defining a Miller index"; return options; } FillMillerIndex::FillMillerIndex(const OptionSet & options) : MillerIndex(fill(options.get>("values"))), - NEML2Object(options) + UserTensor(options) { } diff --git a/src/neml2/models/crystallography/user_tensors/SymmetryFromOrbifold.cxx b/src/neml2/models/crystallography/user_tensors/SymmetryFromOrbifold.cxx index d94316bc60..70750d0dc8 100644 --- a/src/neml2/models/crystallography/user_tensors/SymmetryFromOrbifold.cxx +++ b/src/neml2/models/crystallography/user_tensors/SymmetryFromOrbifold.cxx @@ -35,15 +35,21 @@ register_NEML2_object(SymmetryFromOrbifold); OptionSet SymmetryFromOrbifold::expected_options() { - OptionSet options = NEML2Object::expected_options(); + OptionSet options = UserTensor::expected_options(); + options.doc() = "Returns a tensor of symmetry operations for a given symmetr group represented " + "in orbifold notation."; + options.set("orbifold"); + options.set("orbifold").doc() = + "A string giving the orbifold representation of the group, for example 432 for the typical " + "cubic crystal system defined by chiral octahedral symmetry"; return options; } SymmetryFromOrbifold::SymmetryFromOrbifold(const OptionSet & options) : R2(symmetry_operations_from_orbifold(options.get("orbifold"))), - NEML2Object(options) + UserTensor(options) { } } -} // namespace neml2 \ No newline at end of file +} // namespace neml2 diff --git a/src/neml2/models/solid_mechanics/AssociativeIsotropicPlasticHardening.cxx b/src/neml2/models/solid_mechanics/AssociativeIsotropicPlasticHardening.cxx index 4b10c2db0c..860776c638 100644 --- a/src/neml2/models/solid_mechanics/AssociativeIsotropicPlasticHardening.cxx +++ b/src/neml2/models/solid_mechanics/AssociativeIsotropicPlasticHardening.cxx @@ -32,10 +32,21 @@ OptionSet AssociativeIsotropicPlasticHardening::expected_options() { OptionSet options = FlowRule::expected_options(); + options.doc() += " This object calculates the rate of equivalent plastic strain following " + "associative flow rule, i.e. \\f$ \\dot{\\varepsilon}_p = - \\dot{\\gamma} " + "\\frac{\\partial f}{\\partial k} \\f$, where \\f$ \\dot{\\varepsilon}_p \\f$ " + "is the equivalent plastic strain, \\f$ \\dot{\\gamma} \\f$ is the flow rate, " + "\\f$ f \\f$ is the yield function, and \\f$ k \\f$ is the isotropic hardening."; + options.set("isotropic_hardening_direction") = VariableName("state", "internal", "Nk"); + options.set("isotropic_hardening_direction").doc() = + "Direction of associative isotropic hardening which can be calculated using Normality."; + options.set("equivalent_plastic_strain_rate") = VariableName("state", "internal", "ep_rate"); + options.set("equivalent_plastic_strain_rate").doc() = "Rate of equivalent plastic strain"; + return options; } diff --git a/src/neml2/models/solid_mechanics/AssociativeKinematicPlasticHardening.cxx b/src/neml2/models/solid_mechanics/AssociativeKinematicPlasticHardening.cxx index d0835f6d79..92f93fafce 100644 --- a/src/neml2/models/solid_mechanics/AssociativeKinematicPlasticHardening.cxx +++ b/src/neml2/models/solid_mechanics/AssociativeKinematicPlasticHardening.cxx @@ -33,10 +33,22 @@ OptionSet AssociativeKinematicPlasticHardening::expected_options() { OptionSet options = FlowRule::expected_options(); + options.doc() += + " This object calculates the rate of kinematic plastic strain following associative flow " + "rule, i.e. \\f$ \\dot{\\boldsymbol{K}}_p = - \\dot{\\gamma} \\frac{\\partial f}{\\partial " + "\\boldsymbol{X}} \\f$, where \\f$ \\dot{\\boldsymbol{K}}_p \\f$ is the kinematic plastic " + "strain, \\f$ \\dot{\\gamma} \\f$ is the flow rate, \\f$ f \\f$ is the yield function, and " + "\\f$ \\boldsymbol{X} \\f$ is the kinematic hardening."; + options.set("kinematic_hardening_direction") = VariableName("state", "internal", "NX"); + options.set("kinematic_hardening_direction").doc() = + "Direction of associative kinematic hardening which can be calculated using Normality."; + options.set("kinematic_plastic_strain_rate") = VariableName("state", "internal", "Kp_rate"); + options.set("kinematic_plastic_strain_rate").doc() = "Rate of kinematic plastic strain"; + return options; } diff --git a/src/neml2/models/solid_mechanics/AssociativePlasticFlow.cxx b/src/neml2/models/solid_mechanics/AssociativePlasticFlow.cxx index 06c323001c..6b67a5addf 100644 --- a/src/neml2/models/solid_mechanics/AssociativePlasticFlow.cxx +++ b/src/neml2/models/solid_mechanics/AssociativePlasticFlow.cxx @@ -33,8 +33,19 @@ OptionSet AssociativePlasticFlow::expected_options() { OptionSet options = FlowRule::expected_options(); + options.doc() += + " This object calculates the rate of plastic strain following associative flow rule, i.e. " + "\\f$ \\dot{\\boldsymbol{\\varepsilon}}_p = - \\dot{\\gamma} \\frac{\\partial f}{\\partial " + "\\boldsymbol{M}} \\f$, where \\f$ \\dot{\\boldsymbol{\\varepsilon}}_p \\f$ is the plastic " + "strain, \\f$ \\dot{\\gamma} \\f$ is the flow rate, \\f$ f \\f$ is the yield function, and " + "\\f$ \\boldsymbol{M} \\f$ is the Mandel stress."; + options.set("flow_direction") = VariableName("state", "internal", "NM"); + options.set("flow_direction").doc() = "Flow direction which can be calculated using Normality"; + options.set("plastic_strain_rate") = VariableName("state", "internal", "Ep_rate"); + options.set("plastic_strain_rate").doc() = "Rate of plastic strain"; + return options; } diff --git a/src/neml2/models/solid_mechanics/ChabochePlasticHardening.cxx b/src/neml2/models/solid_mechanics/ChabochePlasticHardening.cxx index 5ffe8b0201..17809ce7f8 100644 --- a/src/neml2/models/solid_mechanics/ChabochePlasticHardening.cxx +++ b/src/neml2/models/solid_mechanics/ChabochePlasticHardening.cxx @@ -33,12 +33,34 @@ OptionSet ChabochePlasticHardening::expected_options() { OptionSet options = FlowRule::expected_options(); + options.doc() += + " This object defines the non-associative Chaboche kinematic hardening. In the Chaboche " + "model, back stress is directly treated as an internal variable. Rate of back stress is " + "given as \\f$ \\dot{\\boldsymbol{X}} = \\left( \\frac{2}{3} C \\frac{\\partial f}{\\partial " + "\\boldsymbol{M}} - g \\boldsymbol{X} \\right) \\dot{\\gamma} - A \\lVert \\boldsymbol{X} " + "\\rVert^{a - 1} \\boldsymbol{X} \\f$, including kinematic hardening, dynamic recovery, and " + "static recovery. \\f$ \\frac{\\partial f}{\\partial \\boldsymbol{M}} \\f$ is the flow " + "direction, \\f$ \\dot{\\gamma} \\f$ is the flow rate, and \\f$ C \\f$, \\f$ g \\f$, \\f$ A " + "\\f$, and \\f$ a \\f$ are material parameters."; + options.set("back_stress") = VariableName("state", "internal", "X"); + options.set("back_stress").doc() = "Back stress"; + options.set("flow_direction") = VariableName("state", "internal", "NM"); + options.set("flow_direction").doc() = "Flow direction"; + options.set>("C"); + options.set("C").doc() = "Kinematic hardening coefficient"; + options.set>("g"); + options.set("g").doc() = "Dynamic recovery coefficient"; + options.set>("A"); + options.set("A").doc() = "Static recovery prefactor"; + options.set>("a"); + options.set("a").doc() = "Static recovery exponent"; + return options; } diff --git a/src/neml2/models/solid_mechanics/Eigenstrain.cxx b/src/neml2/models/solid_mechanics/Eigenstrain.cxx index 85a3f4ddc5..c2133faf27 100644 --- a/src/neml2/models/solid_mechanics/Eigenstrain.cxx +++ b/src/neml2/models/solid_mechanics/Eigenstrain.cxx @@ -30,7 +30,10 @@ OptionSet Eigenstrain::expected_options() { OptionSet options = Model::expected_options(); + options.set("eigenstrain") = VariableName("forces", "Eg"); + options.set("eigenstrain").doc() = "Eigenstrain"; + return options; } diff --git a/src/neml2/models/solid_mechanics/ElasticStrain.cxx b/src/neml2/models/solid_mechanics/ElasticStrain.cxx index ac2ae95778..34ca23942a 100644 --- a/src/neml2/models/solid_mechanics/ElasticStrain.cxx +++ b/src/neml2/models/solid_mechanics/ElasticStrain.cxx @@ -33,10 +33,22 @@ OptionSet ElasticStrain::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Calculate elastic strain given total and plastic strain (assuming additive " + "decomposition), i.e. \\f$ \\boldsymbol{\\varepsilon}_e = " + "\\boldsymbol{\\varepsilon} - \\boldsymbol{\\varepsilon}_p \\f$."; + options.set("total_strain") = VariableName("forces", "E"); + options.set("total_strain").doc() = "Total strain"; + options.set("plastic_strain") = VariableName("state", "internal", "Ep"); + options.set("plastic_strain").doc() = "Plastic strain"; + options.set("elastic_strain") = VariableName("state", "internal", "Ee"); + options.set("elastic_strain").doc() = "Elastic strain"; + options.set("rate_form") = false; + options.set("rate_form").doc() = "Whether the model defines the relationship in rate form"; + return options; } diff --git a/src/neml2/models/solid_mechanics/Elasticity.cxx b/src/neml2/models/solid_mechanics/Elasticity.cxx index 79ff8d2059..13e1e89f45 100644 --- a/src/neml2/models/solid_mechanics/Elasticity.cxx +++ b/src/neml2/models/solid_mechanics/Elasticity.cxx @@ -30,10 +30,24 @@ OptionSet Elasticity::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Relate elastic strain to stress"; + options.set("strain") = VariableName("state", "internal", "Ee"); + options.set("strain").doc() = "Elastic strain"; + options.set("stress") = VariableName("state", "S"); + options.set("stress").doc() = "Stress"; + options.set("compliance") = false; + options.set("compliance").doc() = + "Whether the model defines the compliance relationship, i.e., mapping from stress to elastic " + "strain. When set to false (default), the model maps elastic strain to stress."; + options.set("rate_form") = false; + options.set("rate_form").doc() = + "Whether the model defines the stress-strain relationship in rate form. When set to true, " + "the model maps elastic strain *rate* to stress *rate*."; + return options; } diff --git a/src/neml2/models/solid_mechanics/FlowRule.cxx b/src/neml2/models/solid_mechanics/FlowRule.cxx index e428a62af5..ba11ae3a70 100644 --- a/src/neml2/models/solid_mechanics/FlowRule.cxx +++ b/src/neml2/models/solid_mechanics/FlowRule.cxx @@ -30,7 +30,12 @@ OptionSet FlowRule::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Map the flow rate (i.e., the consistency parameter in the KKT conditions) to " + "the rate of internal variables."; + options.set("flow_rate") = VariableName("state", "internal", "gamma_rate"); + options.set("flow_rate").doc() = "Flow rate"; + return options; } diff --git a/src/neml2/models/solid_mechanics/GTNYieldFunction.cxx b/src/neml2/models/solid_mechanics/GTNYieldFunction.cxx index 51fa8d0f78..a3cadebc62 100644 --- a/src/neml2/models/solid_mechanics/GTNYieldFunction.cxx +++ b/src/neml2/models/solid_mechanics/GTNYieldFunction.cxx @@ -32,15 +32,44 @@ OptionSet GTNYieldFunction::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = + "Gurson-Tvergaard-Needleman yield function for poroplasticity. The yield function is defined " + "as \\f$ f = \\left( \\frac{\\bar{\\sigma}}{\\sigma_y + k} \\right)^2 + 2 q_1 \\phi \\cosh " + "\\left( \\frac{1}{2} q_2 \\frac{3\\sigma_h-\\sigma_s}{\\sigma_y + k} \\right) - \\left( q_3 " + "\\phi^2 + 1 \\right) \\f$, where \\f$ \\bar{\\sigma} \\f$ is the von Mises stress, \\f$ " + "\\sigma_y \\f$ is the yield stress, \\f$ k \\f$ is isotropic hardening, \\f$ \\phi \\f$ is " + "the porosity, \\f$ \\sigma_h \\f$ is the hydrostatic stress, and \\f$ \\sigma_s \\f$ is the " + "void growth back stress (sintering stress). \\f$ q_1 \\f$, \\f$ q_2 \\f$, and \\f$ q_3 \\f$ " + "are parameters controlling the yield mechanisms."; + options.set>("yield_stress"); + options.set("yield_stress").doc() = "Yield stress"; + options.set>("q1"); + options.set("q1").doc() = + "Parameter controlling the balance/competition between plastic flow and void evolution."; + options.set>("q2"); + options.set("q2").doc() = "Void evolution rate"; + options.set>("q3"); + options.set("q3").doc() = "Pore pressure"; + options.set("flow_invariant") = VariableName("state", "internal", "se"); + options.set("flow_invariant").doc() = "Effective stress driving plastic flow"; + options.set("poro_invariant") = VariableName("state", "internal", "sp"); + options.set("poro_invariant").doc() = "Effective stress driving porous flow"; + options.set("isotropic_hardening"); + options.set("isotropic_hardening").doc() = "Isotropic hardening"; + options.set("void_fraction") = VariableName("state", "internal", "f"); + options.set("void_fraction").doc() = "Void fraction (porosity)"; + options.set("yield_function") = VariableName("state", "internal", "fp"); + options.set("yield_function").doc() = "Yield function"; + return options; } diff --git a/src/neml2/models/solid_mechanics/GursonCavitation.cxx b/src/neml2/models/solid_mechanics/GursonCavitation.cxx index 7f43498d31..41eee9edf8 100644 --- a/src/neml2/models/solid_mechanics/GursonCavitation.cxx +++ b/src/neml2/models/solid_mechanics/GursonCavitation.cxx @@ -33,9 +33,18 @@ OptionSet GursonCavitation::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Local mass balance used in conjunction with the GTNYieldFunction, \\f$ " + "\\dot{\\phi} = (1-\\phi) \\dot{\\varepsilon}_p \\f$."; + options.set("plastic_strain_rate") = VariableName("state", "internal", "Ep_rate"); + options.set("plastic_strain_rate").doc() = "Plastic strain rate"; + options.set("void_fraction") = VariableName("state", "internal", "f"); + options.set("void_fraction").doc() = "Void fraction (porosity)"; + options.set("void_fraction_rate") = VariableName("state", "internal", "f_rate"); + options.set("void_fraction_rate").doc() = "Rate of void evolution"; + return options; } diff --git a/src/neml2/models/solid_mechanics/IsotropicHardening.cxx b/src/neml2/models/solid_mechanics/IsotropicHardening.cxx index a4420e8b9d..7ec4a11d7d 100644 --- a/src/neml2/models/solid_mechanics/IsotropicHardening.cxx +++ b/src/neml2/models/solid_mechanics/IsotropicHardening.cxx @@ -30,8 +30,14 @@ OptionSet IsotropicHardening::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Map equivalent plastic strain to isotropic hardening"; + options.set("equivalent_plastic_strain") = VariableName("state", "internal", "ep"); + options.set("equivalent_plastic_strain").doc() = "Equivalent plastic strain"; + options.set("isotropic_hardening") = VariableName("state", "internal", "k"); + options.set("isotropic_hardening").doc() = "Isotropic hardening"; + return options; } diff --git a/src/neml2/models/solid_mechanics/IsotropicMandelStress.cxx b/src/neml2/models/solid_mechanics/IsotropicMandelStress.cxx index f3af34a02a..57882cfdb8 100644 --- a/src/neml2/models/solid_mechanics/IsotropicMandelStress.cxx +++ b/src/neml2/models/solid_mechanics/IsotropicMandelStress.cxx @@ -33,6 +33,9 @@ OptionSet IsotropicMandelStress::expected_options() { OptionSet options = MandelStress::expected_options(); + options.doc() += " For isotropic material under small deformation, the Mandel stress and the " + "Cauchy stress coincide."; + return options; } diff --git a/src/neml2/models/solid_mechanics/KinematicHardening.cxx b/src/neml2/models/solid_mechanics/KinematicHardening.cxx index 7a638a4040..7aabb7d79c 100644 --- a/src/neml2/models/solid_mechanics/KinematicHardening.cxx +++ b/src/neml2/models/solid_mechanics/KinematicHardening.cxx @@ -30,8 +30,14 @@ OptionSet KinematicHardening::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Map kinematic plastic strain to back stress"; + options.set("kinematic_plastic_strain") = VariableName("state", "internal", "Kp"); + options.set("kinematic_plastic_strain").doc() = "Kinematic plastic strain"; + options.set("back_stress") = VariableName("state", "internal", "X"); + options.set("back_stress").doc() = "Back stress"; + return options; } diff --git a/src/neml2/models/solid_mechanics/LinearIsotropicElasticity.cxx b/src/neml2/models/solid_mechanics/LinearIsotropicElasticity.cxx index 23970803a0..cf0390ab80 100644 --- a/src/neml2/models/solid_mechanics/LinearIsotropicElasticity.cxx +++ b/src/neml2/models/solid_mechanics/LinearIsotropicElasticity.cxx @@ -33,8 +33,18 @@ OptionSet LinearIsotropicElasticity::expected_options() { OptionSet options = Elasticity::expected_options(); + options.doc() += " for linear isotropic material. \\f$ \\boldsymbol{\\sigma} = K \\tr " + "\\boldsymbol{\\varepsilon}_e + 2 G \\text{dev} \\boldsymbol{\\varepsilon}_e " + "\\f$, where \\f$ K \\f$ and \\f$ G \\f$ are bulk and shear moduli, " + "respectively. For convenience, this object only requests Young's modulus and " + "Poisson's ratio, and handles the Lame parameter conversion behind the scenes."; + options.set>("youngs_modulus"); + options.set("youngs_modulus").doc() = "Young's modulus"; + options.set>("poisson_ratio"); + options.set("poisson_ratio").doc() = "Poisson's ratio"; + return options; } diff --git a/src/neml2/models/solid_mechanics/LinearIsotropicHardening.cxx b/src/neml2/models/solid_mechanics/LinearIsotropicHardening.cxx index 0bd475f7de..7bfe7c6dd7 100644 --- a/src/neml2/models/solid_mechanics/LinearIsotropicHardening.cxx +++ b/src/neml2/models/solid_mechanics/LinearIsotropicHardening.cxx @@ -32,7 +32,12 @@ OptionSet LinearIsotropicHardening::expected_options() { OptionSet options = IsotropicHardening::expected_options(); + options.doc() += " following a linear relationship, i.e., \\f$ k = H \\varepsilon_p \\f$ where " + "\\f$ H \\f$ is the hardening modulus."; + options.set>("hardening_modulus"); + options.set("hardening_modulus").doc() = "Hardening modulus"; + return options; } diff --git a/src/neml2/models/solid_mechanics/LinearKinematicHardening.cxx b/src/neml2/models/solid_mechanics/LinearKinematicHardening.cxx index 640aa3d9a8..ece9cddaaa 100644 --- a/src/neml2/models/solid_mechanics/LinearKinematicHardening.cxx +++ b/src/neml2/models/solid_mechanics/LinearKinematicHardening.cxx @@ -33,7 +33,12 @@ OptionSet LinearKinematicHardening::expected_options() { OptionSet options = KinematicHardening::expected_options(); + options.doc() += " following a linear relationship, i.e., \\f$ \\boldsymbol{X} = H " + "\\boldsymbol{K}_p \\f$ where \\f$ H \\f$ is the hardening modulus."; + options.set>("hardening_modulus"); + options.set("hardening_modulus").doc() = "Hardening modulus"; + return options; } diff --git a/src/neml2/models/solid_mechanics/MandelStress.cxx b/src/neml2/models/solid_mechanics/MandelStress.cxx index 3d04f7d1f1..2af8167e7e 100644 --- a/src/neml2/models/solid_mechanics/MandelStress.cxx +++ b/src/neml2/models/solid_mechanics/MandelStress.cxx @@ -30,8 +30,14 @@ OptionSet MandelStress::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Map Cauchy stress to Mandel stress"; + options.set("cauchy_stress") = VariableName("state", "S"); + options.set("cauchy_stress").doc() = "Cauchy stress"; + options.set("mandel_stress") = VariableName("state", "internal", "M"); + options.set("mandel_stress").doc() = "Mandel stress"; + return options; } diff --git a/src/neml2/models/solid_mechanics/Normality.cxx b/src/neml2/models/solid_mechanics/Normality.cxx index 44ad2db8a7..c8922f7025 100644 --- a/src/neml2/models/solid_mechanics/Normality.cxx +++ b/src/neml2/models/solid_mechanics/Normality.cxx @@ -32,10 +32,21 @@ OptionSet Normality::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Store the first derivatives of a scalar-valued function in given variables, " + "i.e. \\f$ u_i = \\dfrac{f(\\boldsymbol{v})}{v_i} \\f$."; + options.set("model"); + options.set("model").doc() = "The model which evaluates the scalar-valued function"; + options.set("function"); + options.set("function").doc() = "Function to take derivative"; + options.set>("from"); + options.set("from").doc() = "Function arguments to take derivatives w.r.t."; + options.set>("to"); + options.set("to").doc() = "Variables to store the first derivatives"; + return options; } diff --git a/src/neml2/models/solid_mechanics/OlevskySinteringStress.cxx b/src/neml2/models/solid_mechanics/OlevskySinteringStress.cxx index 368265a5d1..6ddea100e6 100644 --- a/src/neml2/models/solid_mechanics/OlevskySinteringStress.cxx +++ b/src/neml2/models/solid_mechanics/OlevskySinteringStress.cxx @@ -32,10 +32,24 @@ OptionSet OlevskySinteringStress::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Define the Olevsky-Skorohod sintering stress to be used in conjunction with " + "poroplasticity yield functions such as the GTNYieldFunction. The sintering " + "stress is defined as \\f$ \\sigma_s = 3 \\dfrac{\\gamma}{r} \\phi^2 \\f$, where " + "\\f$ \\gamma \\f$ is the surface tension, \\f$ r \\f$ is the size of the " + "particles/powders, and \\f$ \\phi \\f$ is the void fraction."; + options.set("sintering_stress") = VariableName("state", "internal", "ss"); + options.set("sintering_stress").doc() = "Sintering stress"; + options.set("void_fraction") = VariableName("state", "internal", "f"); + options.set("void_fraction").doc() = "Void fraction"; + options.set>("surface_tension"); + options.set("surface_tension").doc() = "Surface tension"; + options.set>("particle_radius"); + options.set("particle_radius").doc() = "Particle radius"; + return options; } diff --git a/src/neml2/models/solid_mechanics/OverStress.cxx b/src/neml2/models/solid_mechanics/OverStress.cxx index 6b94d37d0a..672b5b190d 100644 --- a/src/neml2/models/solid_mechanics/OverStress.cxx +++ b/src/neml2/models/solid_mechanics/OverStress.cxx @@ -33,9 +33,19 @@ OptionSet OverStress::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Calculate the over stress \\f$ \\boldsymbol{O} = \\boldsymbol{M} - " + "\\boldsymbol{X} \\f$, where \\f$ \\boldsymbol{M} \\f$ is the Mandel stress and " + "\\f$ \\boldsymbol{X} \\f$ is the back stress."; + options.set("mandel_stress") = VariableName("state", "internal", "M"); + options.set("mandel_stress").doc() = "Mandel stress"; + options.set("back_stress") = VariableName("state", "internal", "X"); + options.set("back_stress").doc() = "Back stress"; + options.set("over_stress") = VariableName("state", "internal", "O"); + options.set("over_stress").doc() = "Over stress"; + return options; } diff --git a/src/neml2/models/solid_mechanics/PerzynaPlasticFlowRate.cxx b/src/neml2/models/solid_mechanics/PerzynaPlasticFlowRate.cxx index 44ee98690e..90ca2642ad 100644 --- a/src/neml2/models/solid_mechanics/PerzynaPlasticFlowRate.cxx +++ b/src/neml2/models/solid_mechanics/PerzynaPlasticFlowRate.cxx @@ -32,8 +32,18 @@ OptionSet PerzynaPlasticFlowRate::expected_options() { OptionSet options = PlasticFlowRate::expected_options(); + options.doc() = + "Perzyna's viscous approximation of the consistent yield envelope (with a power " + "law), i.e. \\f$ \\dot{\\gamma} = \\left( \\frac{\\left< f \\right>}{\\eta} \\right)^n \\f$, " + "where \\f$ f \\f$ is the yield function, \\f$ \\eta \\f$ is the reference stress, and \\f$ " + "n \\f$ is the power-law exponent."; + options.set>("reference_stress"); + options.set("reference_stress").doc() = "Reference stress"; + options.set>("exponent"); + options.set("exponent").doc() = "Power-law exponent"; + return options; } diff --git a/src/neml2/models/solid_mechanics/PlasticFlowRate.cxx b/src/neml2/models/solid_mechanics/PlasticFlowRate.cxx index 587304797e..dffb3be995 100644 --- a/src/neml2/models/solid_mechanics/PlasticFlowRate.cxx +++ b/src/neml2/models/solid_mechanics/PlasticFlowRate.cxx @@ -30,8 +30,13 @@ OptionSet PlasticFlowRate::expected_options() { OptionSet options = Model::expected_options(); + options.set("yield_function") = VariableName("state", "internal", "fp"); + options.set("yield_function").doc() = "Yield function"; + options.set("flow_rate") = VariableName("state", "internal", "gamma_rate"); + options.set("flow_rate").doc() = "Flow rate"; + return options; } diff --git a/src/neml2/models/solid_mechanics/RateIndependentPlasticFlowConstraint.cxx b/src/neml2/models/solid_mechanics/RateIndependentPlasticFlowConstraint.cxx index b78ceba82f..29c38c298e 100644 --- a/src/neml2/models/solid_mechanics/RateIndependentPlasticFlowConstraint.cxx +++ b/src/neml2/models/solid_mechanics/RateIndependentPlasticFlowConstraint.cxx @@ -32,9 +32,21 @@ OptionSet RateIndependentPlasticFlowConstraint::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Solve the consistent plasticity yield envelope by solving the equivalent " + "complementarity condition \\f[ r = \\begin{cases} \\dot{\\gamma}, & f < 0 " + "\\\\ f, & f \\geq 0. \\end{cases} \\f]"; + options.set("yield_function") = VariableName("state", "internal", "fp"); + options.set("yield_function").doc() = "Yield function"; + options.set("flow_rate") = VariableName("state", "internal", "gamma_rate"); + options.set("flow_rate").doc() = "Flow rate"; + options.set("yielding_tolerance") = 1e-8; + options.set("yielding_tolerance").doc() = + "Tolerance for determining whether the current material state is inside or outside the yield " + "envelope"; + return options; } diff --git a/src/neml2/models/solid_mechanics/ThermalEigenstrain.cxx b/src/neml2/models/solid_mechanics/ThermalEigenstrain.cxx index 9bc005809b..82985dd136 100644 --- a/src/neml2/models/solid_mechanics/ThermalEigenstrain.cxx +++ b/src/neml2/models/solid_mechanics/ThermalEigenstrain.cxx @@ -32,9 +32,21 @@ OptionSet ThermalEigenstrain::expected_options() { OptionSet options = Eigenstrain::expected_options(); + options.doc() = + "Define the (cummulative, as opposed to instantaneous) linear isotropic thermal eigenstrain, " + "i.e. \\f$ \\boldsymbol{\\varepsilon}_T = \\alpha (T - T_0) \\boldsymbol{I} \\f$, where \\f$ " + "\\alpha \\f$ is the coefficient of thermal expansion (CTE), \\f$ T \\f$ is the temperature, " + "and \\f$ T_0 \\f$ is the reference (stress-free) temperature."; + options.set("temperature") = VariableName("forces", "T"); + options.set("temperature").doc() = "Temperature"; + options.set>("reference_temperature"); + options.set("reference_temperature").doc() = "Reference (stress-free) temperature"; + options.set>("CTE"); + options.set("CTE").doc() = "Coefficient of thermal expansion"; + return options; } diff --git a/src/neml2/models/solid_mechanics/TotalStrain.cxx b/src/neml2/models/solid_mechanics/TotalStrain.cxx index eccf7b2458..c5843de848 100644 --- a/src/neml2/models/solid_mechanics/TotalStrain.cxx +++ b/src/neml2/models/solid_mechanics/TotalStrain.cxx @@ -33,10 +33,20 @@ OptionSet TotalStrain::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Calculate the total strain by summing the elastic and plastic strain."; + options.set("elastic_strain") = VariableName("state", "internal", "Ee"); + options.set("elastic_strain").doc() = "Elastic strain"; + options.set("plastic_strain") = VariableName("state", "internal", "Ep"); + options.set("plastic_strain").doc() = "Plastic strain"; + options.set("total_strain") = VariableName("state", "E"); + options.set("total_strain").doc() = "Total strain"; + options.set("rate_form") = false; + options.set("rate_form").doc() = "Whether to define the relationship in rate form"; + return options; } diff --git a/src/neml2/models/solid_mechanics/VoceIsotropicHardening.cxx b/src/neml2/models/solid_mechanics/VoceIsotropicHardening.cxx index 610d6a9628..a44952c306 100644 --- a/src/neml2/models/solid_mechanics/VoceIsotropicHardening.cxx +++ b/src/neml2/models/solid_mechanics/VoceIsotropicHardening.cxx @@ -32,8 +32,16 @@ OptionSet VoceIsotropicHardening::expected_options() { OptionSet options = IsotropicHardening::expected_options(); + options.doc() = "Voce isotropic hardening model, \\f$ h = R \\left[ 1 - \\exp(-d \\varepsilon_p) " + "\\right] \\f$, where \\f$ R \\f$ is the isotropic hardening upon saturation, " + "and \\f$ d \\f$ is the hardening rate."; + options.set>("saturated_hardening"); + options.set("saturated_hardening").doc() = "Saturated isotropic hardening"; + options.set>("saturation_rate"); + options.set("saturation_rate").doc() = "Hardening saturation rate"; + return options; } diff --git a/src/neml2/models/solid_mechanics/YieldFunction.cxx b/src/neml2/models/solid_mechanics/YieldFunction.cxx index 2f6e1ea4bc..275fdbc589 100644 --- a/src/neml2/models/solid_mechanics/YieldFunction.cxx +++ b/src/neml2/models/solid_mechanics/YieldFunction.cxx @@ -32,10 +32,23 @@ OptionSet YieldFunction::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = + "Classical macroscale plasticity yield function, \\f$ f = \\bar{\\sigma} - \\sigma_y - h " + "\\f$, where \\f$ \\bar{\\sigma} \\f$ is the effective stress, \\f$ \\sigma_y \\f$ is the " + "yield stress, and \\f$ h \\f$ is the isotropic hardening."; + options.set>("yield_stress"); + options.set("yield_stress").doc() = "Yield stress"; + options.set("effective_stress") = VariableName("state", "internal", "s"); + options.set("effective_stress").doc() = "Effective stress"; + options.set("isotropic_hardening"); + options.set("isotropic_hardening").doc() = "Isotropic hardening"; + options.set("yield_function") = VariableName("state", "internal", "fp"); + options.set("yield_function").doc() = "Yield function"; + return options; } diff --git a/src/neml2/models/solid_mechanics/crystal_plasticity/ElasticStrainRate.cxx b/src/neml2/models/solid_mechanics/crystal_plasticity/ElasticStrainRate.cxx index 9824df6ac1..d9422338dc 100644 --- a/src/neml2/models/solid_mechanics/crystal_plasticity/ElasticStrainRate.cxx +++ b/src/neml2/models/solid_mechanics/crystal_plasticity/ElasticStrainRate.cxx @@ -35,14 +35,29 @@ OptionSet ElasticStrainRate::expected_options() { OptionSet options = Model::expected_options(); + + options.doc() = "Calculates the elastic strain rate as \\f$\\dot{\\varepsilon} = d - d^p - " + "\\varepsilon w + w \\varepsilon \\f$ " + "where \\f$ d \\f$ is the deformation rate, \\f$ d^p \\f$ is the plastic " + "deformation rate, \\f$ w \\f$ is the vorticity, and \\f$ \\varepsilon \\f$ is " + "the elastic strain."; + options.set("elastic_strain_rate") = VariableName("state", "elastic_strain_rate"); + options.set("elastic_strain_rate").doc() = "Name of the elastic strain rate"; + options.set("elastic_strain") = VariableName("state", "elastic_strain"); + options.set("elastic_strain").doc() = "Name of the elastic strain"; options.set("deformation_rate") = VariableName("forces", "deformation_rate"); + options.set("deformation_rate").doc() = "Name of the deformation rate"; + options.set("vorticity") = VariableName("forces", "vorticity"); + options.set("vorticity").doc() = "Name of the vorticity"; options.set("plastic_deformation_rate") = VariableName("state", "internal", "plastic_deformation_rate"); + options.set("plastic_deformation_rate").doc() = "Name of the plastic deformation rate"; + return options; } diff --git a/src/neml2/models/solid_mechanics/crystal_plasticity/FixOrientation.cxx b/src/neml2/models/solid_mechanics/crystal_plasticity/FixOrientation.cxx index 5bb51f7f16..3b127760fe 100644 --- a/src/neml2/models/solid_mechanics/crystal_plasticity/FixOrientation.cxx +++ b/src/neml2/models/solid_mechanics/crystal_plasticity/FixOrientation.cxx @@ -35,9 +35,22 @@ OptionSet FixOrientation::expected_options() { OptionSet options = Model::expected_options(); + + options.doc() = + "Checks the value of the modified Rodrigues parameter by checking if " + "\\f$ \\left\\lVert r \\right\\rVert > t \\f$, with \\f$ t \\f$ a threshold value set " + "to 1.0 by default and replacing all the orientations that exceed this limit " + "with their shadow parameters values."; + options.set("input_orientation") = VariableName("state", "orientation"); + options.set("input_orientation").doc() = "Name of input tensor of orientations to operate on."; + options.set("output_orientation") = VariableName("state", "orientation"); + options.set("output_orientation").doc() = "Name of output tensor"; + options.set("threshold") = 1.0; + options.set("threshold").doc() = "Threshold value for translating to the shadow parameters"; + return options; } diff --git a/src/neml2/models/solid_mechanics/crystal_plasticity/LinearSingleSlipHardeningRule.cxx b/src/neml2/models/solid_mechanics/crystal_plasticity/LinearSingleSlipHardeningRule.cxx index aaad0a6aed..7405639c6c 100644 --- a/src/neml2/models/solid_mechanics/crystal_plasticity/LinearSingleSlipHardeningRule.cxx +++ b/src/neml2/models/solid_mechanics/crystal_plasticity/LinearSingleSlipHardeningRule.cxx @@ -32,7 +32,14 @@ OptionSet LinearSingleSlipHardeningRule::expected_options() { OptionSet options = SingleSlipHardeningRule::expected_options(); + + options.doc() = "Simple linear slip system hardening defined by \\f$ \\dot{\\tau} = \\theta " + "\\sum_{i=1}^{n_{slip}} \\left| \\dot{\\gamma}_i \\right| \\f$ where \\f$ " + "\\theta \\f$ is the hardening slope."; + options.set>("hardening_slope"); + options.set("hardening_slope").doc() = "Hardening rate"; + return options; } diff --git a/src/neml2/models/solid_mechanics/crystal_plasticity/OrientationRate.cxx b/src/neml2/models/solid_mechanics/crystal_plasticity/OrientationRate.cxx index fc12159159..f1ede23b66 100644 --- a/src/neml2/models/solid_mechanics/crystal_plasticity/OrientationRate.cxx +++ b/src/neml2/models/solid_mechanics/crystal_plasticity/OrientationRate.cxx @@ -35,13 +35,30 @@ OptionSet OrientationRate::expected_options() { OptionSet options = Model::expected_options(); + + options.doc() = + "Defines the rate of the crystal orientations as a spin given by \\f$ \\Omega^e = " + "w - w^p - \\varepsilon d^p + d^p \\varepsilon \\f$ where \\f$ \\Omega^e = \\dot{Q} Q^T " + "\\f$, \\f$ Q \\f$ is the orientation, \\f$ w \\f$ is the vorticity, \\f$ w^p \\f$ is the " + "plastic vorticity, \\f$ d^p \\f$ is the plastic deformation rate, and \\f$ \\varepsilon " + "\\f$ is the elastic stretch."; + options.set("orientation_rate") = VariableName("state", "orientation_rate"); + options.set("orientation_rate").doc() = "The name of the orientation rate (spin)"; + options.set("elastic_strain") = VariableName("state", "elastic_strain"); + options.set("elastic_strain").doc() = "The name of the elastic strain tensor"; + options.set("vorticity") = VariableName("forces", "vorticity"); + options.set("vorticity").doc() = "The name of the voriticty tensor"; + options.set("plastic_deformation_rate") = VariableName("state", "internal", "plastic_deformation_rate"); + options.set("plastic_deformation_rate").doc() = "The name of the plastic deformation rate"; + options.set("plastic_vorticity") = VariableName("state", "internal", "plastic_vorticity"); + options.set("plastic_vorticity").doc() = "The name of the plastic vorticity"; return options; } diff --git a/src/neml2/models/solid_mechanics/crystal_plasticity/PlasticDeformationRate.cxx b/src/neml2/models/solid_mechanics/crystal_plasticity/PlasticDeformationRate.cxx index f93aa2c3e9..c307a3fab0 100644 --- a/src/neml2/models/solid_mechanics/crystal_plasticity/PlasticDeformationRate.cxx +++ b/src/neml2/models/solid_mechanics/crystal_plasticity/PlasticDeformationRate.cxx @@ -37,14 +37,25 @@ PlasticDeformationRate::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Caclulates the plastic deformation rate as \\f$ d^p = \\sum_{i=1}^{n_{slip}} " + "\\dot{\\gamma}_i Q \\operatorname{sym}{\\left(d_i \\otimes n_i \\right)} Q^T " + "\\f$ with \\f$ d^p \\f$ the plastic deformation rate, \\f$ \\dot{\\gamma}_i " + "\\f$ the slip rate on the ith slip system, \\f$Q \\f$ the orientation, \\f$ d_i " + "\\f$ the slip system direction, and \\f$ n_i \\f$ the slip system normal."; + options.set("plastic_deformation_rate") = VariableName("state", "internal", "plastic_deformation_rate"); + options.set("plastic_deformation_rate").doc() = "The name of the plastic deformation rate tensor"; options.set("orientation") = VariableName("state", "orientation_matrix"); + options.set("orientation").doc() = "The name of the orientation matrix tensor"; options.set("slip_rates") = VariableName("state", "internal", "slip_rates"); + options.set("slip_rates").doc() = "The name of the tensor containg the current slip rates"; options.set("crystal_geometry_name") = "crystal_geometry"; + options.set("crystal_geometry_name").doc() = + "The name of the Data object containing the crystallographic information for the material"; return options; } diff --git a/src/neml2/models/solid_mechanics/crystal_plasticity/PlasticVorticity.cxx b/src/neml2/models/solid_mechanics/crystal_plasticity/PlasticVorticity.cxx index 38de1a975e..e218fac53c 100644 --- a/src/neml2/models/solid_mechanics/crystal_plasticity/PlasticVorticity.cxx +++ b/src/neml2/models/solid_mechanics/crystal_plasticity/PlasticVorticity.cxx @@ -36,11 +36,27 @@ OptionSet PlasticVorticity::expected_options() { OptionSet options = Model::expected_options(); + + options.doc() = "Caclulates the plastic vorcitity as \\f$ w^p = \\sum_{i=1}^{n_{slip}} " + "\\dot{\\gamma}_i Q \\operatorname{skew}{\\left(d_i \\otimes n_i \\right)} Q^T " + "\\f$ with \\f$ d^p \\f$ the plastic deformation rate, \\f$ \\dot{\\gamma}_i " + "\\f$ the slip rate on the ith slip system, \\f$Q \\f$ the orientation, \\f$ d_i " + "\\f$ the slip system direction, and \\f$ n_i \\f$ the slip system normal."; + options.set("plastic_vorticity") = VariableName("state", "internal", "plastic_vorticity"); + options.set("plastic_vorticity").doc() = "The name of the plastic vorticity tensor"; + options.set("orientation") = VariableName("state", "orientation_matrix"); + options.set("orientation").doc() = "The name of the orientation matrix tensor"; + options.set("slip_rates") = VariableName("state", "internal", "slip_rates"); + options.set("slip_rates").doc() = "The name of the tensor containg the current slip rates"; + options.set("crystal_geometry_name") = "crystal_geometry"; + options.set("crystal_geometry_name").doc() = + "The name of the Data object containing the crystallographic information for the material"; + return options; } diff --git a/src/neml2/models/solid_mechanics/crystal_plasticity/PowerLawSlipRule.cxx b/src/neml2/models/solid_mechanics/crystal_plasticity/PowerLawSlipRule.cxx index 63801849ba..d60d9f09cf 100644 --- a/src/neml2/models/solid_mechanics/crystal_plasticity/PowerLawSlipRule.cxx +++ b/src/neml2/models/solid_mechanics/crystal_plasticity/PowerLawSlipRule.cxx @@ -33,9 +33,18 @@ OptionSet PowerLawSlipRule::expected_options() { OptionSet options = SlipRule::expected_options(); + options.doc() = + "Power law slip rule defined as \\f$ \\dot{\\gamma}_i = \\dot{\\gamma}_0 \\left| " + "\\frac{\\tau_i}{\\hat{\\tau}_i} \\right|^{n-1} \\frac{\\tau_i}{\\hat{\\tau}_i} \\f$ with " + "\\f$ \\dot{\\gamma}_i \\f$ the slip rate on system \\f$ i \\f$, \\f$ \\tau_i \\f$ the " + "resolved shear, \\f$ \\hat{\\tau}_i \\f$ the slip system strength, \\f$ n \\f$ the rate " + "senstivity, and \\f$ \\dot{\\gamma}_0 \\f$ a reference slip rate."; options.set>("gamma0"); + options.set("gamma0").doc() = "Reference slip rate"; + options.set>("n"); + options.set("n").doc() = "Rate sensitivity exponent"; return options; } diff --git a/src/neml2/models/solid_mechanics/crystal_plasticity/ResolvedShear.cxx b/src/neml2/models/solid_mechanics/crystal_plasticity/ResolvedShear.cxx index 7481ddeac8..d60af69d93 100644 --- a/src/neml2/models/solid_mechanics/crystal_plasticity/ResolvedShear.cxx +++ b/src/neml2/models/solid_mechanics/crystal_plasticity/ResolvedShear.cxx @@ -36,11 +36,26 @@ OptionSet ResolvedShear::expected_options() { OptionSet options = Model::expected_options(); + + options.doc() = "Calculates the resolved shears as \\f$ \\tau_i = \\sigma : Q " + "\\operatorname{sym}\\left(d_i \\otimes n_i \\right) Q^T \\f$ where \\f$ \\tau_i " + "\\f$ is the resolved shear on slip system i, \\f$ \\sigma \\f$ is the Cauchy " + "stress \\f$ Q \\f$ is the orientation matrix, \\f$ d_i \\f$ is the slip " + "direction, and \\f$ n_i \\f$ is the slip system normal."; + options.set("resolved_shears") = VariableName("state", "internal", "resolved_shears"); + options.set("resolved_shears").doc() = "The name of the resolved shears"; + options.set("stress") = VariableName("state", "internal", "cauchy_stress"); + options.set("stress").doc() = "The name of the Cauchy stress tensor"; + options.set("orientation") = VariableName("state", "orientation_matrix"); + options.set("orientation").doc() = "The name of the orientation matrix"; + options.set("crystal_geometry_name") = "crystal_geometry"; + options.set("crystal_geometry_name").doc() = + "The name of the data object with the crystallographic information"; return options; } diff --git a/src/neml2/models/solid_mechanics/crystal_plasticity/SingleSlipHardeningRule.cxx b/src/neml2/models/solid_mechanics/crystal_plasticity/SingleSlipHardeningRule.cxx index 9666b0d919..75e49fca35 100644 --- a/src/neml2/models/solid_mechanics/crystal_plasticity/SingleSlipHardeningRule.cxx +++ b/src/neml2/models/solid_mechanics/crystal_plasticity/SingleSlipHardeningRule.cxx @@ -33,10 +33,18 @@ OptionSet SingleSlipHardeningRule::expected_options() { OptionSet options = Model::expected_options(); + + options.doc() = + "Parent class of slip hardening rules where all slip systems share the same strength."; + options.set("slip_hardening_rate") = VariableName("state", "internal", "slip_hardening_rate"); + options.set("slip_hardening_rate").doc() = + "Name of tensor to output the slip system hardening rates into"; options.set("slip_hardening") = VariableName("state", "internal", "slip_hardening"); + options.set("slip_hardening").doc() = "Name of current values of slip hardening"; options.set("sum_slip_rates") = VariableName("state", "internal", "sum_slip_rates"); + options.set("sum_slip_rates").doc() = "Name of tensor containing the sum of the slip rates"; return options; } diff --git a/src/neml2/models/solid_mechanics/crystal_plasticity/SingleSlipStrengthMap.cxx b/src/neml2/models/solid_mechanics/crystal_plasticity/SingleSlipStrengthMap.cxx index 8d8335d441..71fd2e25a7 100644 --- a/src/neml2/models/solid_mechanics/crystal_plasticity/SingleSlipStrengthMap.cxx +++ b/src/neml2/models/solid_mechanics/crystal_plasticity/SingleSlipStrengthMap.cxx @@ -33,8 +33,17 @@ SingleSlipStrengthMap::expected_options() { OptionSet options = SlipStrengthMap::expected_options(); + options.doc() = + "Calculates the slip system strength for all slip systems as \\f$ \\hat{\\tau}_i = " + "\\bar{\\tau} + \\tau_0 \\f$ where \\f$ \\hat{\\tau}_i \\f$ is the strength for slip system " + "i, \\f$ \\bar{\\tau} \\f$ is an evolving slip system strength (one value of all systems), " + "defined by another object, and \\f$ \\tau_0 \\f$ is a constant strength."; + options.set("slip_hardening") = VariableName("state", "internal", "slip_hardening"); + options.set("slip_hardening").doc() = "The name of the evovling, scalar strength"; + options.set>("constant_strength"); + options.set("constant_strength").doc() = "The constant slip system strength"; return options; } diff --git a/src/neml2/models/solid_mechanics/crystal_plasticity/SlipRule.cxx b/src/neml2/models/solid_mechanics/crystal_plasticity/SlipRule.cxx index cc147e2787..965f26e8d2 100644 --- a/src/neml2/models/solid_mechanics/crystal_plasticity/SlipRule.cxx +++ b/src/neml2/models/solid_mechanics/crystal_plasticity/SlipRule.cxx @@ -35,13 +35,22 @@ SlipRule::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Parent class for all slip rules, which define the slip rate in terms of the " + "resolved shear and the slip system strength"; + options.set("slip_rates") = VariableName("state", "internal", "slip_rates"); + options.set("slip_rates").doc() = "Name of the slip rate tensor"; options.set("resolved_shears") = VariableName("state", "internal", "resolved_shears"); + options.set("resolved_shears").doc() = "Name of the resolved shear tensor"; + options.set("slip_strengths") = VariableName("state", "internal", "slip_strengths"); + options.set("slip_strengths").doc() = "Name of the tensor containing the slip system strengths"; options.set("crystal_geometry_name") = "crystal_geometry"; + options.set("crystal_geometry_name").doc() = + "Name of the Data object containing the crystallographic information"; return options; } diff --git a/src/neml2/models/solid_mechanics/crystal_plasticity/SlipStrengthMap.cxx b/src/neml2/models/solid_mechanics/crystal_plasticity/SlipStrengthMap.cxx index 2d8db6098e..634fe6844d 100644 --- a/src/neml2/models/solid_mechanics/crystal_plasticity/SlipStrengthMap.cxx +++ b/src/neml2/models/solid_mechanics/crystal_plasticity/SlipStrengthMap.cxx @@ -34,9 +34,14 @@ OptionSet SlipStrengthMap::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Map between internal variables the slip system strengths."; options.set("slip_strengths") = VariableName("state", "internal", "slip_strengths"); + options.set("slip_strengths").doc() = "Name of the slip system strengths"; + options.set("crystal_geometry_name") = "crystal_geometry"; + options.set("crystal_geometry_name").doc() = + "Name of the Data object containing the crystallographic information"; return options; } diff --git a/src/neml2/models/solid_mechanics/crystal_plasticity/SumSlipRates.cxx b/src/neml2/models/solid_mechanics/crystal_plasticity/SumSlipRates.cxx index e488d879f0..25efc0c440 100644 --- a/src/neml2/models/solid_mechanics/crystal_plasticity/SumSlipRates.cxx +++ b/src/neml2/models/solid_mechanics/crystal_plasticity/SumSlipRates.cxx @@ -37,11 +37,18 @@ OptionSet SumSlipRates::expected_options() { OptionSet options = Model::expected_options(); + options.doc() = "Calculates the sum of the absolute value of all the slip rates as \\f$ " + "\\sum_{i=1}^{n_{slip}} \\left| \\dot{\\gamma}_i \\right| \\f$."; options.set("slip_rates") = VariableName("state", "internal", "slip_rates"); + options.set("slip_rates").doc() = "The name of individual slip rates"; + options.set("sum_slip_rates") = VariableName("state", "internal", "sum_slip_rates"); + options.set("sum_slip_rates").doc() = "The outut name for the scalar sum of the slip rates"; options.set("crystal_geometry_name") = "crystal_geometry"; + options.set("crystal_geometry_name").doc() = + "The name of the Data object containing the crystallographic information"; return options; } diff --git a/src/neml2/models/solid_mechanics/crystal_plasticity/VoceSingleSlipHardeningRule.cxx b/src/neml2/models/solid_mechanics/crystal_plasticity/VoceSingleSlipHardeningRule.cxx index d0339df153..efa76e4d26 100644 --- a/src/neml2/models/solid_mechanics/crystal_plasticity/VoceSingleSlipHardeningRule.cxx +++ b/src/neml2/models/solid_mechanics/crystal_plasticity/VoceSingleSlipHardeningRule.cxx @@ -32,8 +32,18 @@ OptionSet VoceSingleSlipHardeningRule::expected_options() { OptionSet options = SingleSlipHardeningRule::expected_options(); + options.doc() = "Voce hardening for a SingleSlipStrength type model defined by \\f$ \\dot{\\tau} " + "= \\theta_0 \\left( 1 - \\frac{\\tau}{\\tau_f} \\right) " + "\\sum_{i=1}^{n_{slip}} \\left| \\dot{\\gamma}_i \\right| \\f$ where \\f$ " + "\\theta_0 \\f$ is the initial rate of work hardening, \\f$ \\tau_f \\f$ is the " + "saturated, maximum value of the slip system strength, and \\f$ \\dot{\\gamma}_i " + "\\f$ is the slip rate on each system."; + options.set>("initial_slope"); + options.set("initial_slope").doc() = "The initial rate of hardening"; options.set>("saturated_hardening"); + options.set("saturated_hardening").doc() = + "The final, saturated value of the slip system strength"; return options; } diff --git a/src/neml2/solvers/Newton.cxx b/src/neml2/solvers/Newton.cxx index 1595d1cc49..6083cb6390 100644 --- a/src/neml2/solvers/Newton.cxx +++ b/src/neml2/solvers/Newton.cxx @@ -34,6 +34,8 @@ OptionSet Newton::expected_options() { OptionSet options = NonlinearSolver::expected_options(); + options.doc() = "The standard Newton-Raphson solver which always takes the 'full' Newton step."; + return options; } diff --git a/src/neml2/solvers/NewtonWithLineSearch.cxx b/src/neml2/solvers/NewtonWithLineSearch.cxx index 9485d93e8e..513209febf 100644 --- a/src/neml2/solvers/NewtonWithLineSearch.cxx +++ b/src/neml2/solvers/NewtonWithLineSearch.cxx @@ -34,9 +34,21 @@ OptionSet NewtonWithLineSearch::expected_options() { OptionSet options = Newton::expected_options(); + options.doc() = "The Newton-Raphson solver with line search."; + options.set("max_linesearch_iterations") = 10; + options.set("max_linesearch_iterations").doc() = + "Maximum allowable linesearch iterations. No error is produced upon reaching the maximum " + "number of iterations, and the scale factor in the last iteration is used to scale the step."; + options.set("linesearch_cutback") = 2.0; + options.set("linesearch_cutback").doc() = "Linesearch cut-back factor when the current scale " + "factor cannot sufficiently reduce the residual."; + options.set("linesearch_stopping_criteria") = 1.0e-3; + options.set("linesearch_stopping_criteria").doc() = + "The lineseach tolerance slightly relaxing the definition of residual decrease"; + return options; } diff --git a/src/neml2/solvers/NewtonWithTrustRegion.cxx b/src/neml2/solvers/NewtonWithTrustRegion.cxx index e724ececa6..74773ab24e 100644 --- a/src/neml2/solvers/NewtonWithTrustRegion.cxx +++ b/src/neml2/solvers/NewtonWithTrustRegion.cxx @@ -34,16 +34,45 @@ OptionSet NewtonWithTrustRegion::expected_options() { OptionSet options = Newton::expected_options(); + options.doc() = + "A trust-region Newton solver. The step size and direction are modified by solving a " + "constrained minimization problem using the local quadratic approximation. The default " + "solver parameters are chosen based on a limited set of problems we tested on and are " + "expected to be tuned."; + options.set("delta_0") = 1.0; + options.set("delta_0").doc() = "Initial trust region radius"; + options.set("delta_max") = 10.0; + options.set("delta_max").doc() = "Maximum trust region radius"; + options.set("reduce_criteria") = 0.25; + options.set("reduce_criteria").doc() = "The trust region radius is reduced when the merit " + "function reduction is below this threshold"; + options.set("expand_criteria") = 0.75; + options.set("expand_criteria").doc() = "The trust region radius is increased when the merit " + "function reduction is above this threshold"; + options.set("reduce_factor") = 0.25; + options.set("reduce_factor").doc() = "Factor to apply when reducing the trust region radius"; + options.set("expand_factor") = 2.0; + options.set("expand_factor").doc() = "Factor to apply when increasing the trust region radius"; + options.set("accept_criteria") = 0.1; + options.set("accept_criteria").doc() = + "Reject the current step when the merit function reduction is below this threshold"; + options.set("subproblem_rel_tol") = 1e-6; + options.set("subproblem_rel_tol").doc() = "Relative tolerance used for the quadratic sub-problem"; + options.set("subproblem_abs_tol") = 1e-8; + options.set("subproblem_abs_tol").doc() = "Absolute tolerance used for the quadratic sub-problem"; + options.set("subproblem_max_its") = 10; + options.set("subproblem_max_its").doc() = + "Maximum number of allowable iterations when solving the quadratic sub-problem"; return options; } diff --git a/src/neml2/solvers/NonlinearSolver.cxx b/src/neml2/solvers/NonlinearSolver.cxx index d6ffe59f98..22885e0f79 100644 --- a/src/neml2/solvers/NonlinearSolver.cxx +++ b/src/neml2/solvers/NonlinearSolver.cxx @@ -30,9 +30,17 @@ OptionSet NonlinearSolver::expected_options() { OptionSet options = Solver::expected_options(); + options.set("abs_tol") = 1e-10; + options.set("abs_tol").doc() = "Absolute tolerance in the convergence criteria"; + options.set("rel_tol") = 1e-8; + options.set("rel_tol").doc() = "Relative tolerance in the convergence criteria"; + options.set("max_its") = 100; + options.set("max_its").doc() = + "Maximum number of iterations allowed before issuing an error/exception"; + return options; } diff --git a/src/neml2/solvers/NonlinearSystem.cxx b/src/neml2/solvers/NonlinearSystem.cxx index d8a35fbd36..badbaaddad 100644 --- a/src/neml2/solvers/NonlinearSystem.cxx +++ b/src/neml2/solvers/NonlinearSystem.cxx @@ -31,12 +31,41 @@ OptionSet NonlinearSystem::expected_options() { OptionSet options; + options.set("automatic_scaling") = false; + options.set("automatic_scaling").doc() = + "Whether to perform automatic scaling. See neml2::NonlinearSystem::init_scaling for " + "implementation details."; + options.set("automatic_scaling_tol") = 0.01; + options.set("automatic_scaling_tol").doc() = + "Tolerance used in iteratively updating the scaling matrices."; + options.set("automatic_scaling_miter") = 20; + options.set("automatic_scaling_miter").doc() = + "Maximum number of automatic scaling iterations. No error is produced upon reaching the " + "maximum number of scaling iterations, and the scaling matrices obtained at the last " + "iteration are used to scale the nonlinear system."; + return options; } +void +NonlinearSystem::disable_automatic_scaling(OptionSet & options) +{ + options.set("automatic_scaling").suppressed() = true; + options.set("automatic_scaling_tol").suppressed() = true; + options.set("automatic_scaling_miter").suppressed() = true; +} + +void +NonlinearSystem::enable_automatic_scaling(OptionSet & options) +{ + options.set("automatic_scaling").suppressed() = false; + options.set("automatic_scaling_tol").suppressed() = false; + options.set("automatic_scaling_miter").suppressed() = false; +} + NonlinearSystem::NonlinearSystem(const OptionSet & options) : _autoscale(options.get("automatic_scaling")), _autoscale_tol(options.get("automatic_scaling_tol")), diff --git a/src/neml2/solvers/Solver.cxx b/src/neml2/solvers/Solver.cxx index bbdeb81832..b316b0f61e 100644 --- a/src/neml2/solvers/Solver.cxx +++ b/src/neml2/solvers/Solver.cxx @@ -30,7 +30,11 @@ OptionSet Solver::expected_options() { OptionSet options = NEML2Object::expected_options(); + options.section() = "Solvers"; + options.set("verbose"); + options.set("verbose").doc() = "Verbosity of the solver"; + return options; } diff --git a/src/neml2/tensors/user_tensors/EmptyBatchTensor.cxx b/src/neml2/tensors/user_tensors/EmptyBatchTensor.cxx index 3051df9930..79edc70ced 100644 --- a/src/neml2/tensors/user_tensors/EmptyBatchTensor.cxx +++ b/src/neml2/tensors/user_tensors/EmptyBatchTensor.cxx @@ -31,9 +31,16 @@ register_NEML2_object(EmptyBatchTensor); OptionSet EmptyBatchTensor::expected_options() { - OptionSet options = NEML2Object::expected_options(); + OptionSet options = UserTensor::expected_options(); + options.doc() = "Construct an empty BatchTensor given batch and base shapes. Tensor values are " + "**undefined** after construction."; + options.set("batch_shape") = {}; + options.set("batch_shape").doc() = "Batch shape"; + options.set("base_shape") = {}; + options.set("base_shape").doc() = "Base shape"; + return options; } @@ -41,7 +48,7 @@ EmptyBatchTensor::EmptyBatchTensor(const OptionSet & options) : BatchTensor(BatchTensor::empty(options.get("batch_shape"), options.get("base_shape"), default_tensor_options())), - NEML2Object(options) + UserTensor(options) { } } // namespace neml2 diff --git a/src/neml2/tensors/user_tensors/EmptyFixedDimTensor.cxx b/src/neml2/tensors/user_tensors/EmptyFixedDimTensor.cxx index 4b6704c80f..d5c3aace44 100644 --- a/src/neml2/tensors/user_tensors/EmptyFixedDimTensor.cxx +++ b/src/neml2/tensors/user_tensors/EmptyFixedDimTensor.cxx @@ -33,15 +33,24 @@ template OptionSet EmptyFixedDimTensor::expected_options() { - OptionSet options = NEML2Object::expected_options(); + // This is the only way of getting tensor type in a static method like this... + // Trim 6 chars to remove 'neml2::' + auto tensor_type = utils::demangle(typeid(T).name()).substr(7); + + OptionSet options = UserTensor::expected_options(); + options.doc() = "Construct an empty " + tensor_type + + " with given batch shape. Tensor values are **undefined** after construction."; + options.set("batch_shape") = {}; + options.set("batch_shape").doc() = "Batch shape"; + return options; } template EmptyFixedDimTensor::EmptyFixedDimTensor(const OptionSet & options) : T(T::empty(options.get("batch_shape"), default_tensor_options())), - NEML2Object(options) + UserTensor(options) { } diff --git a/src/neml2/tensors/user_tensors/Fill3DVec.cxx b/src/neml2/tensors/user_tensors/Fill3DVec.cxx deleted file mode 100644 index 8022a98e82..0000000000 --- a/src/neml2/tensors/user_tensors/Fill3DVec.cxx +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2023, UChicago Argonne, LLC -// All Rights Reserved -// Software Name: NEML2 -- the New Engineering material Model Library, version 2 -// By: Argonne National Laboratory -// OPEN SOURCE LICENSE (MIT) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#include "neml2/tensors/user_tensors/Fill3DVec.h" - -namespace neml2 -{ -register_NEML2_object(Fill3DVec); - -OptionSet -Fill3DVec::expected_options() -{ - OptionSet options = NEML2Object::expected_options(); - options.set>("values"); - return options; -} - -Fill3DVec::Fill3DVec(const OptionSet & options) - : Vec(fill(options.get>("values"))), - NEML2Object(options) -{ -} - -Vec -Fill3DVec::fill(const std::vector & values) const -{ - if ((values.size() % 3) != 0) - neml_assert(false, "Number of provided values must be a multiple of three!"); - - return Vec(torch::tensor(values, default_tensor_options()).reshape({-1, 3})); -} -} // namespace neml2 diff --git a/src/neml2/tensors/user_tensors/FillR2.cxx b/src/neml2/tensors/user_tensors/FillR2.cxx index 385a5aee2c..23029b9453 100644 --- a/src/neml2/tensors/user_tensors/FillR2.cxx +++ b/src/neml2/tensors/user_tensors/FillR2.cxx @@ -31,14 +31,23 @@ register_NEML2_object(FillR2); OptionSet FillR2::expected_options() { - OptionSet options = NEML2Object::expected_options(); + OptionSet options = UserTensor::expected_options(); + options.doc() = "Construct a R2 with a vector of Scalars. The vector length must be 1, 3, 6, or " + "9. When vector length is 1, the Scalar value is used to fill the diagonals; " + "when vector length is 3, the Scalar values are used to fill the respective " + "diagonal entries; when vector length is 6, the Scalar values are used to fill " + "the tensor following the Voigt notation; when vector length is 9, the Scalar " + "values are used to fill the tensor in the row-major fashion."; + options.set>>("values"); + options.set("values").doc() = "Scalars used to fill the R2"; + return options; } FillR2::FillR2(const OptionSet & options) : R2(fill(options.get>>("values"))), - NEML2Object(options) + UserTensor(options) { } diff --git a/src/neml2/tensors/user_tensors/FillRot.cxx b/src/neml2/tensors/user_tensors/FillRot.cxx index 755d470780..6f355c4881 100644 --- a/src/neml2/tensors/user_tensors/FillRot.cxx +++ b/src/neml2/tensors/user_tensors/FillRot.cxx @@ -31,16 +31,22 @@ register_NEML2_object(FillRot); OptionSet FillRot::expected_options() { - OptionSet options = NEML2Object::expected_options(); + OptionSet options = UserTensor::expected_options(); + options.doc() = "Construct a Rot from a vector of Scalars."; + options.set>>("values"); + options.set("values").doc() = "Scalars used to fill the Rot"; + options.set("method") = "modified"; + options.set("method").doc() = "Fill method, options are 'modified' and 'standard'."; + return options; } FillRot::FillRot(const OptionSet & options) : Rot(fill(options.get>>("values"), options.get("method"))), - NEML2Object(options) + UserTensor(options) { } diff --git a/src/neml2/tensors/user_tensors/FillSR2.cxx b/src/neml2/tensors/user_tensors/FillSR2.cxx index f8296e828a..b6140b0693 100644 --- a/src/neml2/tensors/user_tensors/FillSR2.cxx +++ b/src/neml2/tensors/user_tensors/FillSR2.cxx @@ -31,14 +31,23 @@ register_NEML2_object(FillSR2); OptionSet FillSR2::expected_options() { - OptionSet options = NEML2Object::expected_options(); + OptionSet options = UserTensor::expected_options(); + options.doc() = "Construct a R2 with a vector of Scalars. The vector length must be 1, 3, or 6. " + "See the full documentation on neml2::SR2 on the dispatch of fill method. When " + "vector length is 1, the Scalar value is used to fill the diagonals; when vector " + "length is 3, the Scalar values are used to fill the respective diagonal " + "entries; when vector length is 6, the Scalar values are used to fill the tensor " + "following the Voigt notation."; + options.set>>("values"); + options.set("values").doc() = "Scalars used to fill the R2"; + return options; } FillSR2::FillSR2(const OptionSet & options) : SR2(fill(options.get>>("values"))), - NEML2Object(options) + UserTensor(options) { } diff --git a/src/neml2/tensors/user_tensors/FillWR2.cxx b/src/neml2/tensors/user_tensors/FillWR2.cxx index 54c2f8fd32..e11042c2f1 100644 --- a/src/neml2/tensors/user_tensors/FillWR2.cxx +++ b/src/neml2/tensors/user_tensors/FillWR2.cxx @@ -31,14 +31,18 @@ register_NEML2_object(FillWR2); OptionSet FillWR2::expected_options() { - OptionSet options = NEML2Object::expected_options(); + OptionSet options = UserTensor::expected_options(); + options.doc() = "Construct a Rot from a vector of Scalars."; + options.set>>("values"); + options.set("values").doc() = "Scalars used to fill the WR2"; + return options; } FillWR2::FillWR2(const OptionSet & options) : WR2(fill(options.get>>("values"))), - NEML2Object(options) + UserTensor(options) { } diff --git a/src/neml2/tensors/user_tensors/FullBatchTensor.cxx b/src/neml2/tensors/user_tensors/FullBatchTensor.cxx index de4cdcfe83..bccfe65d51 100644 --- a/src/neml2/tensors/user_tensors/FullBatchTensor.cxx +++ b/src/neml2/tensors/user_tensors/FullBatchTensor.cxx @@ -31,10 +31,19 @@ register_NEML2_object(FullBatchTensor); OptionSet FullBatchTensor::expected_options() { - OptionSet options = NEML2Object::expected_options(); + OptionSet options = UserTensor::expected_options(); + options.doc() = + "Construct a full BatchTensor with given batch and base shapes filled with a given value."; + options.set("batch_shape") = {}; + options.set("batch_shape").doc() = "Batch shape"; + options.set("base_shape") = {}; + options.set("base_shape").doc() = "Base shape"; + options.set("value"); + options.set("value").doc() = "Value used to fill the tensor"; + return options; } @@ -43,7 +52,7 @@ FullBatchTensor::FullBatchTensor(const OptionSet & options) options.get("base_shape"), options.get("value"), default_tensor_options())), - NEML2Object(options) + UserTensor(options) { } } // namespace neml2 diff --git a/src/neml2/tensors/user_tensors/FullFixedDimTensor.cxx b/src/neml2/tensors/user_tensors/FullFixedDimTensor.cxx index 3e1676fef4..79943cd403 100644 --- a/src/neml2/tensors/user_tensors/FullFixedDimTensor.cxx +++ b/src/neml2/tensors/user_tensors/FullFixedDimTensor.cxx @@ -33,9 +33,20 @@ template OptionSet FullFixedDimTensor::expected_options() { - OptionSet options = NEML2Object::expected_options(); + // This is the only way of getting tensor type in a static method like this... + // Trim 6 chars to remove 'neml2::' + auto tensor_type = utils::demangle(typeid(T).name()).substr(7); + + OptionSet options = UserTensor::expected_options(); + options.doc() = + "Construct a full " + tensor_type + " with given batch shape filled with a given value."; + options.set("batch_shape") = {}; + options.set("batch_shape").doc() = "Batch shape"; + options.set("value"); + options.set("value").doc() = "Value used to fill the tensor"; + return options; } @@ -44,7 +55,7 @@ FullFixedDimTensor::FullFixedDimTensor(const OptionSet & options) : T(T::full(options.get("batch_shape"), options.get("value"), default_tensor_options())), - NEML2Object(options) + UserTensor(options) { } diff --git a/src/neml2/tensors/user_tensors/IdentityBatchTensor.cxx b/src/neml2/tensors/user_tensors/IdentityBatchTensor.cxx index 33413ff038..ec83bee80f 100644 --- a/src/neml2/tensors/user_tensors/IdentityBatchTensor.cxx +++ b/src/neml2/tensors/user_tensors/IdentityBatchTensor.cxx @@ -31,9 +31,16 @@ register_NEML2_object(IdentityBatchTensor); OptionSet IdentityBatchTensor::expected_options() { - OptionSet options = NEML2Object::expected_options(); + OptionSet options = UserTensor::expected_options(); + options.doc() = "Construct an identity BatchTensor with given batch shape."; + options.set("batch_shape") = {}; + options.set("batch_shape").doc() = "Batch shape"; + options.set("n"); + options.set("n").doc() = + "Diagonal size of the identity tensor, i.e., base shape of the identity tensor will be (n,n)"; + return options; } @@ -41,7 +48,7 @@ IdentityBatchTensor::IdentityBatchTensor(const OptionSet & options) : BatchTensor(BatchTensor::identity(options.get("batch_shape"), options.get("n"), default_tensor_options())), - NEML2Object(options) + UserTensor(options) { } } // namespace neml2 diff --git a/src/neml2/tensors/user_tensors/LinspaceBatchTensor.cxx b/src/neml2/tensors/user_tensors/LinspaceBatchTensor.cxx index 8241726a89..8814c6ac40 100644 --- a/src/neml2/tensors/user_tensors/LinspaceBatchTensor.cxx +++ b/src/neml2/tensors/user_tensors/LinspaceBatchTensor.cxx @@ -32,13 +32,29 @@ register_NEML2_object(LinspaceBatchTensor); OptionSet LinspaceBatchTensor::expected_options() { - OptionSet options = NEML2Object::expected_options(); + OptionSet options = UserTensor::expected_options(); + options.doc() = "Construct a BatchTensor linearly spaced on the batch dimensions. See " + "neml2::BatchTensorBase::linspace for a detailed explanation."; + options.set>("start"); + options.set("start").doc() = "The starting tensor"; + options.set>("end"); + options.set("end").doc() = "The ending tensor"; + options.set("nstep"); + options.set("nstep").doc() = "The number of steps with even spacing along the new dimension"; + options.set("dim") = 0; + options.set("dim").doc() = "Where to insert the new dimension"; + options.set("batch_dim") = -1; + options.set("batch_dim").doc() = "Batch dimension of the output"; + options.set("batch_expand") = TorchShape(); + options.set("batch_expand").doc() = "After construction, perform an additional batch expanding " + "operation into the given batch shape."; + return options; } @@ -48,7 +64,7 @@ LinspaceBatchTensor::LinspaceBatchTensor(const OptionSet & options) options.get("nstep"), options.get("dim"), options.get("batch_dim"))), - NEML2Object(options) + UserTensor(options) { auto bs = options.get("batch_expand"); if (bs.size() > 0) diff --git a/src/neml2/tensors/user_tensors/LinspaceFixedDimTensor.cxx b/src/neml2/tensors/user_tensors/LinspaceFixedDimTensor.cxx index e9c8fe63c9..e81aba5081 100644 --- a/src/neml2/tensors/user_tensors/LinspaceFixedDimTensor.cxx +++ b/src/neml2/tensors/user_tensors/LinspaceFixedDimTensor.cxx @@ -33,12 +33,30 @@ template OptionSet LinspaceFixedDimTensor::expected_options() { - OptionSet options = NEML2Object::expected_options(); + // This is the only way of getting tensor type in a static method like this... + // Trim 6 chars to remove 'neml2::' + auto tensor_type = utils::demangle(typeid(T).name()).substr(7); + + OptionSet options = UserTensor::expected_options(); + options.doc() = "Construct a " + tensor_type + + " linearly spaced on the batch dimensions. See neml2::BatchTensorBase::linspace " + "for a detailed explanation."; + options.set>("start"); + options.set("start").doc() = "The starting tensor"; + options.set>("end"); + options.set("end").doc() = "The ending tensor"; + options.set("nstep"); + options.set("nstep").doc() = "The number of steps with even spacing along the new dimension"; + options.set("dim") = 0; + options.set("dim").doc() = "Where to insert the new dimension"; + options.set("batch_dim") = -1; + options.set("batch_dim").doc() = "Batch dimension of the output"; + return options; } @@ -49,7 +67,7 @@ LinspaceFixedDimTensor::LinspaceFixedDimTensor(const OptionSet & options) options.get("nstep"), options.get("dim"), options.get("batch_dim"))), - NEML2Object(options) + UserTensor(options) { } diff --git a/src/neml2/tensors/user_tensors/LogspaceBatchTensor.cxx b/src/neml2/tensors/user_tensors/LogspaceBatchTensor.cxx index 65d89c29fb..b79b654728 100644 --- a/src/neml2/tensors/user_tensors/LogspaceBatchTensor.cxx +++ b/src/neml2/tensors/user_tensors/LogspaceBatchTensor.cxx @@ -32,13 +32,28 @@ register_NEML2_object(LogspaceBatchTensor); OptionSet LogspaceBatchTensor::expected_options() { - OptionSet options = NEML2Object::expected_options(); + OptionSet options = UserTensor::expected_options(); + options.doc() = "Construct a BatchTensor with exponents linearly spaced on the batch dimensions. " + "See neml2::BatchTensorBase::logspace for a detailed explanation."; + options.set>("start"); + options.set("start").doc() = "The starting tensor"; + options.set>("end"); + options.set("end").doc() = "The ending tensor"; + options.set("nstep"); + options.set("nstep").doc() = "The number of steps with even spacing along the new dimension"; + options.set("dim") = 0; - options.set("base") = 10; + options.set("dim").doc() = "Where to insert the new dimension"; + options.set("batch_dim") = -1; + options.set("batch_dim").doc() = "Batch dimension of the output"; + + options.set("base") = 10; + options.set("base").doc() = "Exponent base"; + return options; } @@ -49,7 +64,7 @@ LogspaceBatchTensor::LogspaceBatchTensor(const OptionSet & options) options.get("dim"), options.get("batch_dim"), options.get("base"))), - NEML2Object(options) + UserTensor(options) { } } // namespace neml2 diff --git a/src/neml2/tensors/user_tensors/LogspaceFixedDimTensor.cxx b/src/neml2/tensors/user_tensors/LogspaceFixedDimTensor.cxx index 54fbfb4ccd..fecec9edc4 100644 --- a/src/neml2/tensors/user_tensors/LogspaceFixedDimTensor.cxx +++ b/src/neml2/tensors/user_tensors/LogspaceFixedDimTensor.cxx @@ -33,13 +33,33 @@ template OptionSet LogspaceFixedDimTensor::expected_options() { - OptionSet options = NEML2Object::expected_options(); + // This is the only way of getting tensor type in a static method like this... + // Trim 6 chars to remove 'neml2::' + auto tensor_type = utils::demangle(typeid(T).name()).substr(7); + + OptionSet options = UserTensor::expected_options(); + options.doc() = "Construct a " + tensor_type + + " with exponents linearly spaced on the batch dimensions. See " + "neml2::BatchTensorBase::logspace for a detailed explanation."; + options.set>("start"); + options.set("start").doc() = "The starting tensor"; + options.set>("end"); + options.set("end").doc() = "The ending tensor"; + options.set("nstep"); + options.set("nstep").doc() = "The number of steps with even spacing along the new dimension"; + options.set("dim") = 0; + options.set("dim").doc() = "Where to insert the new dimension"; + options.set("batch_dim") = -1; + options.set("batch_dim").doc() = "Batch dimension of the output"; + options.set("base") = 10; + options.set("base").doc() = "Exponent base"; + return options; } @@ -51,7 +71,7 @@ LogspaceFixedDimTensor::LogspaceFixedDimTensor(const OptionSet & options) options.get("dim"), options.get("batch_dim"), options.get("base"))), - NEML2Object(options) + UserTensor(options) { } diff --git a/src/neml2/tensors/user_tensors/OnesBatchTensor.cxx b/src/neml2/tensors/user_tensors/OnesBatchTensor.cxx index c21c941e1e..e4c27071fa 100644 --- a/src/neml2/tensors/user_tensors/OnesBatchTensor.cxx +++ b/src/neml2/tensors/user_tensors/OnesBatchTensor.cxx @@ -31,9 +31,15 @@ register_NEML2_object(OnesBatchTensor); OptionSet OnesBatchTensor::expected_options() { - OptionSet options = NEML2Object::expected_options(); + OptionSet options = UserTensor::expected_options(); + options.doc() = "Construct a BatchTensor with batch and base shapes filled with ones."; + options.set("batch_shape") = {}; + options.set("batch_shape").doc() = "Batch shape"; + options.set("base_shape") = {}; + options.set("base_shape").doc() = "Base shape"; + return options; } @@ -41,7 +47,7 @@ OnesBatchTensor::OnesBatchTensor(const OptionSet & options) : BatchTensor(BatchTensor::ones(options.get("batch_shape"), options.get("base_shape"), default_tensor_options())), - NEML2Object(options) + UserTensor(options) { } } // namespace neml2 diff --git a/src/neml2/tensors/user_tensors/OnesFixedDimTensor.cxx b/src/neml2/tensors/user_tensors/OnesFixedDimTensor.cxx index af394198d6..1da5e22df4 100644 --- a/src/neml2/tensors/user_tensors/OnesFixedDimTensor.cxx +++ b/src/neml2/tensors/user_tensors/OnesFixedDimTensor.cxx @@ -33,15 +33,23 @@ template OptionSet OnesFixedDimTensor::expected_options() { - OptionSet options = NEML2Object::expected_options(); + // This is the only way of getting tensor type in a static method like this... + // Trim 6 chars to remove 'neml2::' + auto tensor_type = utils::demangle(typeid(T).name()).substr(7); + + OptionSet options = UserTensor::expected_options(); + options.doc() = "Construct a " + tensor_type + " with given batch shape filled with ones."; + options.set("batch_shape") = {}; + options.set("batch_shape").doc() = "Batch shape"; + return options; } template OnesFixedDimTensor::OnesFixedDimTensor(const OptionSet & options) : T(T::ones(options.get("batch_shape"), default_tensor_options())), - NEML2Object(options) + UserTensor(options) { } diff --git a/src/neml2/tensors/user_tensors/Orientation.cxx b/src/neml2/tensors/user_tensors/Orientation.cxx index a33e8a3b3e..bdeb53fff9 100644 --- a/src/neml2/tensors/user_tensors/Orientation.cxx +++ b/src/neml2/tensors/user_tensors/Orientation.cxx @@ -35,22 +35,44 @@ register_NEML2_object(Orientation); OptionSet Orientation::expected_options() { - OptionSet options = NEML2Object::expected_options(); + OptionSet options = UserTensor::expected_options(); + + options.doc() = "An orientation, internally defined as a set of Modified Rodrigues parameters " + "given by \\f$ r = n \\tan{\\frac{\\theta}{4}} \\f$ with \\f$ n \\f$ the axis of " + "rotation and \\f$ \\theta \\f$ the rotation angle about that axis. However, " + "this class provides a variety of ways to define the orientation in terms of " + "other, more common representations."; + options.set("input_type") = "euler_angles"; + options.set("input_type").doc() = + "The method used to define the angles, 'euler_angles' or 'random'"; + options.set("angle_convention") = "kocks"; + options.set("angle_convention").doc() = "Euler angle convention, 'Kocks', 'Roe', or 'Bunge'"; + options.set("angle_type") = "degrees"; + options.set("angle_type").doc() = "Type of angles, either 'degrees' or 'radians'"; + options.set>("values") = {}; + options.set("values").doc() = "Input Euler angles, as a flattened n-by-3 matrix"; + options.set("normalize") = false; + options.set("normalize").doc() = + "If true do a shadow parameter replacement of the underlying MRP representation to move the " + "inputs farther away from the singularity"; options.set("random_seed") = -1; + options.set("random_seed").doc() = "Random seed for random angle generation"; options.set("quantity") = 1; + options.set("quantity").doc() = "Number (batch size) of random orientations"; + return options; } Orientation::Orientation(const OptionSet & options) : Rot(fill(options)), - NEML2Object(options) + UserTensor(options) { } diff --git a/src/neml2/tensors/user_tensors/UserBatchTensor.cxx b/src/neml2/tensors/user_tensors/UserBatchTensor.cxx index bd9210a3b2..e602d379ab 100644 --- a/src/neml2/tensors/user_tensors/UserBatchTensor.cxx +++ b/src/neml2/tensors/user_tensors/UserBatchTensor.cxx @@ -31,10 +31,19 @@ register_NEML2_object_alias(UserBatchTensor, "BatchTensor"); OptionSet UserBatchTensor::expected_options() { - OptionSet options = NEML2Object::expected_options(); + OptionSet options = UserTensor::expected_options(); + options.doc() = "Construct a BatchTensor from a vector of values. The vector will be reshaped " + "according to the specified batch and base shapes."; + options.set>("values"); + options.set("values").doc() = "Values in this (flattened) tensor"; + options.set("batch_shape") = {}; + options.set("batch_shape").doc() = "Batch shape"; + options.set("base_shape") = {}; + options.set("base_shape").doc() = "Base shape"; + return options; } @@ -42,7 +51,7 @@ UserBatchTensor::UserBatchTensor(const OptionSet & options) : BatchTensor(BatchTensor::empty(options.get("batch_shape"), options.get("base_shape"), default_tensor_options())), - NEML2Object(options) + UserTensor(options) { auto vals = options.get>("values"); auto flat = torch::tensor(vals, default_tensor_options()); diff --git a/src/neml2/tensors/user_tensors/UserFixedDimTensor.cxx b/src/neml2/tensors/user_tensors/UserFixedDimTensor.cxx index ff4a1dbcf1..543dca7a29 100644 --- a/src/neml2/tensors/user_tensors/UserFixedDimTensor.cxx +++ b/src/neml2/tensors/user_tensors/UserFixedDimTensor.cxx @@ -33,16 +33,28 @@ template OptionSet UserFixedDimTensor::expected_options() { - OptionSet options = NEML2Object::expected_options(); + // This is the only way of getting tensor type in a static method like this... + // Trim 6 chars to remove 'neml2::' + auto tensor_type = utils::demangle(typeid(T).name()).substr(7); + + OptionSet options = UserTensor::expected_options(); + options.doc() = + "Construct a " + tensor_type + + " from a vector values. The vector will be reshaped according to the specified batch shape."; + options.set>("values"); + options.set("values").doc() = "Values in this (flattened) tensor"; + options.set("batch_shape") = {}; + options.set("batch_shape").doc() = "Batch shape"; + return options; } template UserFixedDimTensor::UserFixedDimTensor(const OptionSet & options) : T(T::empty(options.get("batch_shape"), default_tensor_options())), - NEML2Object(options) + UserTensor(options) { auto vals = options.get>("values"); auto flat = torch::tensor(vals, default_tensor_options()); diff --git a/tests/benchmark/main.cxx b/src/neml2/tensors/user_tensors/UserTensor.cxx similarity index 80% rename from tests/benchmark/main.cxx rename to src/neml2/tensors/user_tensors/UserTensor.cxx index 95ff4a12e5..192fa45bcd 100644 --- a/tests/benchmark/main.cxx +++ b/src/neml2/tensors/user_tensors/UserTensor.cxx @@ -22,7 +22,20 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#define CATCH_CONFIG_MAIN -#define CATCH_CONFIG_ENABLE_BENCHMARKING +#include "neml2/tensors/user_tensors/UserTensor.h" -#include +namespace neml2 +{ +OptionSet +UserTensor::expected_options() +{ + OptionSet options = NEML2Object::expected_options(); + options.section() = "Tensors"; + return options; +} + +UserTensor::UserTensor(const OptionSet & options) + : NEML2Object(options) +{ +} +} // namespace neml2 diff --git a/src/neml2/tensors/user_tensors/ZerosBatchTensor.cxx b/src/neml2/tensors/user_tensors/ZerosBatchTensor.cxx index 764cfa126b..bb2adc66f8 100644 --- a/src/neml2/tensors/user_tensors/ZerosBatchTensor.cxx +++ b/src/neml2/tensors/user_tensors/ZerosBatchTensor.cxx @@ -31,9 +31,15 @@ register_NEML2_object(ZerosBatchTensor); OptionSet ZerosBatchTensor::expected_options() { - OptionSet options = NEML2Object::expected_options(); + OptionSet options = UserTensor::expected_options(); + options.doc() = "Construct a BatchTensor with batch and base shapes filled with zeros."; + options.set("batch_shape") = {}; + options.set("batch_shape").doc() = "Batch shape"; + options.set("base_shape") = {}; + options.set("base_shape").doc() = "Base shape"; + return options; } @@ -41,7 +47,7 @@ ZerosBatchTensor::ZerosBatchTensor(const OptionSet & options) : BatchTensor(BatchTensor::zeros(options.get("batch_shape"), options.get("base_shape"), default_tensor_options())), - NEML2Object(options) + UserTensor(options) { } } // namespace neml2 diff --git a/src/neml2/tensors/user_tensors/ZerosFixedDimTensor.cxx b/src/neml2/tensors/user_tensors/ZerosFixedDimTensor.cxx index e9c07c786c..690588d967 100644 --- a/src/neml2/tensors/user_tensors/ZerosFixedDimTensor.cxx +++ b/src/neml2/tensors/user_tensors/ZerosFixedDimTensor.cxx @@ -33,15 +33,23 @@ template OptionSet ZerosFixedDimTensor::expected_options() { - OptionSet options = NEML2Object::expected_options(); + // This is the only way of getting tensor type in a static method like this... + // Trim 6 chars to remove 'neml2::' + auto tensor_type = utils::demangle(typeid(T).name()).substr(7); + + OptionSet options = UserTensor::expected_options(); + options.doc() = "Construct a " + tensor_type + " with given batch shape filled with zeros."; + options.set("batch_shape") = {}; + options.set("batch_shape").doc() = "Batch shape"; + return options; } template ZerosFixedDimTensor::ZerosFixedDimTensor(const OptionSet & options) : T(T::zeros(options.get("batch_shape"), default_tensor_options())), - NEML2Object(options) + UserTensor(options) { } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 59292a6077..631c7048f3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,237 +1,33 @@ -include(NEML2UnityGroup) - -# Catch2, for testing -add_subdirectory(${NEML2_SOURCE_DIR}/extern/Catch2 ${NEML2_BINARY_DIR}/extern/Catch2) -list(APPEND CMAKE_MODULE_PATH ${NEML2_SOURCE_DIR}/extern/Catch2/contrib) +# ---------------------------------------------------------------------------- +# Project-level settings, options, and flags +# ---------------------------------------------------------------------------- +option(NEML2_UNIT "Build NEML2 unit tests" ON) +option(NEML2_REGRESSION "Build NEML2 regression tests" ON) +option(NEML2_VERIFICATION "Build NEML2 verification tests" ON) -# include(CTest) -include(Catch) +# ---------------------------------------------------------------------------- +# Dependencies and 3rd party packages +# ---------------------------------------------------------------------------- +message(STATUS "Configuring Catch2") +FetchContent_MakeAvailable(Catch2) +# ---------------------------------------------------------------------------- +# Subdirectories +# ---------------------------------------------------------------------------- # Test utilities add_subdirectory(src) -# ################################################### # Unit tests -# ################################################### -option(NEML2_UNIT "Build NEML2 unit tests" ON) - if(NEML2_UNIT) - file(GLOB_RECURSE UNIT_TESTS unit/*.cxx) - add_executable(unit_tests - ${TEST_UTILS} - ${UNIT_TESTS} - ) - - target_compile_options(unit_tests PUBLIC -Wall -Wextra -pedantic -Werror) - register_unity_group(unit_tests "Unit test" unit) - target_link_libraries(unit_tests Catch2::Catch2) - target_link_libraries(unit_tests neml2 testutils) - target_include_directories(unit_tests PUBLIC ${NEML2_SOURCE_DIR}/tests/include) - catch_discover_tests(unit_tests) - - if(NOT ${NEML2_BINARY_DIR} STREQUAL ${NEML2_SOURCE_DIR}) - add_custom_command(TARGET unit_tests - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E create_symlink ${NEML2_SOURCE_DIR}/tests/unit ${NEML2_BINARY_DIR}/tests/unit - COMMENT "Creating symlink for unit tests" - ) - endif() - - install(TARGETS unit_tests) - install(DIRECTORY unit - TYPE BIN - FILES_MATCHING - PATTERN "*.i" - PATTERN "*.pt" - PATTERN "*.txt" - PATTERN "*.vtest" - PATTERN "*.xml" - PATTERN "*.py" - PATTERN "*.pyi" - ) + add_subdirectory(unit) endif() -# ################################################### # Regression tests -# ################################################### -option(NEML2_REGRESSION "Build NEML2 regression tests" ON) - if(NEML2_REGRESSION) - file(GLOB_RECURSE REGRESSION_TESTS regression/*.cxx) - add_executable(regression_tests - ${TEST_UTILS} - ${REGRESSION_TESTS} - ) - - target_compile_options(regression_tests PUBLIC -Wall -Wextra -pedantic -Werror) - register_unity_group(regression_tests "Regression test" regression) - target_link_libraries(regression_tests Catch2::Catch2) - target_link_libraries(regression_tests neml2 testutils) - target_include_directories(regression_tests PUBLIC "${NEML2_SOURCE_DIR}/tests/include") - catch_discover_tests(regression_tests) - - if(NOT ${NEML2_BINARY_DIR} STREQUAL ${NEML2_SOURCE_DIR}) - add_custom_command(TARGET regression_tests - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E create_symlink ${NEML2_SOURCE_DIR}/tests/regression ${NEML2_BINARY_DIR}/tests/regression - COMMENT "Creating symlink for regression tests" - ) - endif() - - install(TARGETS regression_tests) - install(DIRECTORY regression - TYPE BIN - FILES_MATCHING - PATTERN "*.i" - PATTERN "*.pt" - PATTERN "*.txt" - PATTERN "*.vtest" - PATTERN "*.xml" - PATTERN "*.py" - PATTERN "*.pyi" - ) + add_subdirectory(regression) endif() -# ################################################### # Verification tests -# ################################################### -option(NEML2_VERIFICATION "Build NEML2 verification tests" ON) - if(NEML2_VERIFICATION) - file(GLOB_RECURSE VERIFICATION_TESTS verification/*.cxx) - add_executable(verification_tests - ${TEST_UTILS} - ${VERIFICATION_TESTS} - ) - - target_compile_options(verification_tests PUBLIC -Wall -Wextra -pedantic -Werror) - register_unity_group(verification_tests "Verification test" verification) - target_link_libraries(verification_tests Catch2::Catch2) - target_link_libraries(verification_tests neml2 testutils) - target_include_directories(verification_tests PUBLIC "${NEML2_SOURCE_DIR}/tests/include") - catch_discover_tests(verification_tests) - - if(NOT ${NEML2_BINARY_DIR} STREQUAL ${NEML2_SOURCE_DIR}) - add_custom_command(TARGET verification_tests - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E create_symlink ${NEML2_SOURCE_DIR}/tests/verification ${NEML2_BINARY_DIR}/tests/verification - COMMENT "Creating symlink for verification tests" - ) - endif() - - install(TARGETS verification_tests) - install(DIRECTORY verification - TYPE BIN - FILES_MATCHING - PATTERN "*.i" - PATTERN "*.pt" - PATTERN "*.txt" - PATTERN "*.vtest" - PATTERN "*.xml" - PATTERN "*.py" - PATTERN "*.pyi" - ) -endif() - -# ################################################### -# Benchmarks -# ################################################### -option(NEML2_BENCHMARK "Build NEML2 benchmark tests" OFF) - -if(NEML2_BENCHMARK) - file(GLOB_RECURSE BENCHMARK_TESTS benchmark/*.cxx) - add_executable(benchmark_tests - ${TEST_UTILS} - ${BENCHMARK_TESTS} - ) - - # compile options - target_compile_options(benchmark_tests PUBLIC -Wall -Wextra -pedantic -Werror) - - register_unity_group(benchmark_tests "Benchmark test" benchmark) - target_link_libraries(benchmark_tests Catch2::Catch2) - target_link_libraries(benchmark_tests neml2 testutils) - target_include_directories(benchmark_tests PUBLIC "${NEML2_SOURCE_DIR}/tests/include") - catch_discover_tests(benchmark_tests) - - if(NOT ${NEML2_BINARY_DIR} STREQUAL ${NEML2_SOURCE_DIR}) - add_custom_command(TARGET benchmark_tests - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E create_symlink ${NEML2_SOURCE_DIR}/tests/benchmark ${NEML2_BINARY_DIR}/tests/benchmark - COMMENT "Creating symlink for benchmark tests" - ) - endif() - - install(TARGETS benchmark_tests) - install(DIRECTORY benchmark - TYPE BIN - FILES_MATCHING - PATTERN "*.i" - PATTERN "*.pt" - PATTERN "*.txt" - PATTERN "*.vtest" - PATTERN "*.xml" - PATTERN "*.py" - PATTERN "*.pyi" - ) -endif() - -# ################################################### -# Profiling -# ################################################### -option(NEML2_PROFILING "Build NEML2 profiling tests" OFF) - -if(NEML2_PROFILING) - # gperftools for profiling - add_subdirectory(${NEML2_SOURCE_DIR}/extern/gperftools ${NEML2_BINARY_DIR}/extern/gperftools EXCLUDE_FROM_ALL) - file(GLOB_RECURSE PROFILING_TESTS profiling/*.cxx) - add_executable(profiling_tests - ${TEST_UTILS} - ${PROFILING_TESTS} - ) - - register_unity_group(profiling_tests "Profiling test" profiling) - target_compile_options(profiling_tests PUBLIC -Wall -Wextra -pedantic -Werror) - target_link_options(profiling_tests PRIVATE "-Wl,-no-as-needed") - target_link_libraries(profiling_tests neml2 testutils profiler) - target_include_directories(profiling_tests PUBLIC "${NEML2_SOURCE_DIR}/tests/include") - - if(NOT ${NEML2_BINARY_DIR} STREQUAL ${NEML2_SOURCE_DIR}) - add_custom_command(TARGET profiling_tests - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E create_symlink ${NEML2_SOURCE_DIR}/tests/profiling ${NEML2_BINARY_DIR}/tests/profiling - COMMENT "Creating symlink for profiling tests" - ) - endif() - - install(TARGETS profiling_tests) - install(DIRECTORY profiling - TYPE BIN - FILES_MATCHING - PATTERN "*.i" - PATTERN "*.pt" - PATTERN "*.txt" - PATTERN "*.vtest" - PATTERN "*.xml" - PATTERN "*.py" - PATTERN "*.pyi" - ) -endif() - -# ################################################### -# Python binding tests -# ################################################### -if(NEML2_PYBIND) - if(NOT ${NEML2_BINARY_DIR} STREQUAL ${NEML2_SOURCE_DIR}) - add_custom_target(link_pybind_tests ALL - COMMAND ${CMAKE_COMMAND} -E create_symlink ${NEML2_SOURCE_DIR}/tests/python ${NEML2_BINARY_DIR}/tests/python - COMMENT "Creating symlink for python binding tests" - ) - endif() - - install(DIRECTORY python - TYPE BIN - FILES_MATCHING - PATTERN "*.py" - ) + add_subdirectory(verification) endif() diff --git a/tests/benchmark/chaboche/chaboche.cxx b/tests/benchmark/chaboche/chaboche.cxx deleted file mode 100644 index a4d2ea8350..0000000000 --- a/tests/benchmark/chaboche/chaboche.cxx +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2023, UChicago Argonne, LLC -// All Rights Reserved -// Software Name: NEML2 -- the New Engineering material Model Library, version 2 -// By: Argonne National Laboratory -// OPEN SOURCE LICENSE (MIT) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#define CATCH_CONFIG_ENABLE_BENCHMARKING - -#include - -#include "utils.h" -#include "neml2/drivers/Driver.h" - -using namespace neml2; - -TEST_CASE("Chaboche") -{ - std::vector nbatches = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192}; - std::vector devices = {"cpu", "cuda"}; - - for (auto && device : devices) - for (TorchSize nbatch : nbatches) - { - const auto config = "nbatch=" + utils::stringify(nbatch) + " device=" + device; - load_model("benchmark/chaboche/model.i", config); - auto & driver = Factory::get_object("Drivers", "driver"); - BENCHMARK("{" + config + "}") { return driver.run(); }; - } -} diff --git a/tests/benchmark/taylor_rolling_fcc/taylor_rolling_fcc.cxx b/tests/benchmark/taylor_rolling_fcc/taylor_rolling_fcc.cxx deleted file mode 100644 index 3b43cfb720..0000000000 --- a/tests/benchmark/taylor_rolling_fcc/taylor_rolling_fcc.cxx +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2023, UChicago Argonne, LLC -// All Rights Reserved -// Software Name: NEML2 -- the New Engineering material Model Library, version 2 -// By: Argonne National Laboratory -// OPEN SOURCE LICENSE (MIT) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#define CATCH_CONFIG_ENABLE_BENCHMARKING - -#include - -#include "utils.h" -#include "neml2/drivers/Driver.h" - -using namespace neml2; - -TEST_CASE("taylor") -{ - std::vector nbatches = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096}; - std::vector devices = {"cpu", "cuda:0"}; - TorchSize ntime = 500; - - for (auto && device : devices) - for (TorchSize nbatch : nbatches) - { - const auto config = "nbatch=" + utils::stringify(nbatch) + " device=" + device + - " ntime=" + utils::stringify(ntime); - load_model("benchmark/taylor_rolling_fcc/model.i", config); - auto & driver = Factory::get_object("Drivers", "driver"); - BENCHMARK("{" + config + "}") { return driver.run(); }; - } -} diff --git a/tests/benchmark/taylor_single_orientation/taylor_single_orientation.cxx b/tests/benchmark/taylor_single_orientation/taylor_single_orientation.cxx deleted file mode 100644 index 08d36bd218..0000000000 --- a/tests/benchmark/taylor_single_orientation/taylor_single_orientation.cxx +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2023, UChicago Argonne, LLC -// All Rights Reserved -// Software Name: NEML2 -- the New Engineering material Model Library, version 2 -// By: Argonne National Laboratory -// OPEN SOURCE LICENSE (MIT) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#define CATCH_CONFIG_ENABLE_BENCHMARKING - -#include - -#include "utils.h" -#include "neml2/drivers/Driver.h" - -using namespace neml2; - -TEST_CASE("single_taylor") -{ - std::vector nbatches = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096}; - std::vector devices = {"cpu", "cuda:0"}; - TorchSize ntime = 500; - - for (auto && device : devices) - for (TorchSize nbatch : nbatches) - { - const auto config = "nbatch=" + utils::stringify(nbatch) + " device=" + device + - " ntime=" + utils::stringify(ntime); - load_model("benchmark/taylor_single_orientation/model.i", config); - auto & driver = Factory::get_object("Drivers", "driver"); - BENCHMARK("{" + config + "}") { return driver.run(); }; - } -} diff --git a/tests/include/utils.h b/tests/include/utils.h index 8f553cce50..d6bc34bf9d 100644 --- a/tests/include/utils.h +++ b/tests/include/utils.h @@ -24,22 +24,10 @@ #pragma once -#include "neml2/base/HITParser.h" +#include "neml2/base/Parser.h" #include "neml2/base/Factory.h" #include "neml2/tensors/Scalar.h" -enum ParserType -{ - HIT, - XML, - YAML, - AUTO -}; - -void load_model(const std::string & path, - const std::string & additional_input = "", - ParserType ptype = ParserType::AUTO); - /** * @brief A simple finite-differencing helper to numerically approximate the derivative of the * function at the given point. diff --git a/tests/profiling/model.i b/tests/profiling/model.i deleted file mode 100644 index 6dcd2d5d05..0000000000 --- a/tests/profiling/model.i +++ /dev/null @@ -1,161 +0,0 @@ -[Tensors] - [end_time] - type = LogspaceScalar - start = -1 - end = 5 - nstep = 20 - [] - [times] - type = LinspaceScalar - start = 0 - end = end_time - nstep = 100 - [] - [exx] - type = FullScalar - batch_shape = '(20)' - value = 0.1 - [] - [eyy] - type = FullScalar - batch_shape = '(20)' - value = -0.05 - [] - [ezz] - type = FullScalar - batch_shape = '(20)' - value = -0.05 - [] - [max_strain] - type = FillSR2 - values = 'exx eyy ezz' - [] - [strains] - type = LinspaceSR2 - start = 0 - end = max_strain - nstep = 100 - [] -[] - -[Drivers] - [driver] - type = SolidMechanicsDriver - model = 'model' - times = 'times' - prescribed_strains = 'strains' - [] -[] - -[Solvers] - [newton] - type = Newton - [] -[] - -[Models] - [isoharden] - type = VoceIsotropicHardening - saturated_hardening = 100 - saturation_rate = 1.2 - [] - [kinharden] - type = SR2SumModel - from_var = 'state/internal/X1 state/internal/X2' - to_var = 'state/internal/X' - [] - [mandel_stress] - type = IsotropicMandelStress - [] - [overstress] - type = OverStress - [] - [vonmises] - type = SR2Invariant - invariant_type = 'VONMISES' - tensor = 'state/internal/O' - invariant = 'state/internal/s' - [] - [yield] - type = YieldFunction - yield_stress = 5 - isotropic_hardening = 'state/internal/k' - [] - [flow] - type = ComposedModel - models = 'overstress vonmises yield' - [] - [normality] - type = Normality - model = 'flow' - function = 'state/internal/fp' - from = 'state/internal/M state/internal/k' - to = 'state/internal/NM state/internal/Nk' - [] - [flow_rate] - type = PerzynaPlasticFlowRate - reference_stress = 100 - exponent = 2 - [] - [eprate] - type = AssociativeIsotropicPlasticHardening - [] - [X1rate] - type = ChabochePlasticHardening - back_stress = 'state/internal/X1' - C = 10000 - g = 100 - A = 1e-8 - a = 1.2 - [] - [X2rate] - type = ChabochePlasticHardening - back_stress = 'state/internal/X2' - C = 1000 - g = 9 - A = 1e-10 - a = 3.2 - [] - [Eprate] - type = AssociativePlasticFlow - [] - [Erate] - type = SR2ForceRate - force = 'E' - [] - [Eerate] - type = ElasticStrain - rate_form = true - [] - [elasticity] - type = LinearIsotropicElasticity - youngs_modulus = 1e5 - poisson_ratio = 0.3 - rate_form = true - [] - [integrate_ep] - type = ScalarBackwardEulerTimeIntegration - variable = 'internal/ep' - [] - [integrate_X1] - type = SR2BackwardEulerTimeIntegration - variable = 'internal/X1' - [] - [integrate_X2] - type = SR2BackwardEulerTimeIntegration - variable = 'internal/X2' - [] - [integrate_stress] - type = SR2BackwardEulerTimeIntegration - variable = 'S' - [] - [implicit_rate] - type = ComposedModel - models = 'isoharden kinharden mandel_stress overstress vonmises yield normality flow_rate eprate Eprate X1rate X2rate Erate Eerate elasticity integrate_stress integrate_ep integrate_X1 integrate_X2' - [] - [model] - type = ImplicitUpdate - implicit_model = 'implicit_rate' - solver = 'newton' - [] -[] diff --git a/tests/regression/CMakeLists.txt b/tests/regression/CMakeLists.txt new file mode 100644 index 0000000000..e7f1555080 --- /dev/null +++ b/tests/regression/CMakeLists.txt @@ -0,0 +1,20 @@ +include(NEML2UnityGroup) + +file(GLOB_RECURSE srcs *.cxx) +add_executable(regression_tests ${srcs}) +set_target_properties(regression_tests PROPERTIES INSTALL_RPATH "${EXEC_DIR}/../lib;${Torch_LINK_DIRECTORIES}") + +target_compile_options(regression_tests PRIVATE -Wall -Wextra -pedantic -Werror) +register_unity_group(regression_tests .) +target_link_libraries(regression_tests PRIVATE testutils Catch2::Catch2WithMain) + +install(TARGETS regression_tests COMPONENT Development) +install(DIRECTORY . + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/regression + COMPONENT Development + FILES_MATCHING + PATTERN "*.i" + PATTERN "*.pt" + PATTERN "*.vtest" + PATTERN "*.xml" +) diff --git a/tests/regression/main.cxx b/tests/regression/main.cxx deleted file mode 100644 index 740a4fd06a..0000000000 --- a/tests/regression/main.cxx +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2023, UChicago Argonne, LLC -// All Rights Reserved -// Software Name: NEML2 -- the New Engineering material Model Library, version 2 -// By: Argonne National Laboratory -// OPEN SOURCE LICENSE (MIT) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#define CATCH_CONFIG_MAIN - -#include diff --git a/tests/regression/solid_mechanics/crystal_plasticity/single_crystal_coupled/model.i b/tests/regression/solid_mechanics/crystal_plasticity/single_crystal_coupled/model.i index 74e47df2d2..6bf0ceb771 100644 --- a/tests/regression/solid_mechanics/crystal_plasticity/single_crystal_coupled/model.i +++ b/tests/regression/solid_mechanics/crystal_plasticity/single_crystal_coupled/model.i @@ -109,7 +109,6 @@ times = 'times' prescribed_deformation_rate = 'deformation_rate' prescribed_vorticity = 'vorticity' - provide_vorticity = true ic_rot_names = 'state/orientation' ic_rot_values = 'initial_orientation' predictor = 'CP_PREVIOUS_STATE' diff --git a/tests/regression/solid_mechanics/crystal_plasticity/single_crystal_decoupled/model.i b/tests/regression/solid_mechanics/crystal_plasticity/single_crystal_decoupled/model.i index fc7fdbe58d..5b176852b7 100644 --- a/tests/regression/solid_mechanics/crystal_plasticity/single_crystal_decoupled/model.i +++ b/tests/regression/solid_mechanics/crystal_plasticity/single_crystal_decoupled/model.i @@ -109,7 +109,6 @@ times = 'times' prescribed_deformation_rate = 'deformation_rate' prescribed_vorticity = 'vorticity' - provide_vorticity = true ic_rot_names = 'state/orientation' ic_rot_values = 'initial_orientation' predictor = 'CP_PREVIOUS_STATE' diff --git a/tests/regression/solid_mechanics/crystal_plasticity/single_crystal_decoupled_explicit_orientation/model.i b/tests/regression/solid_mechanics/crystal_plasticity/single_crystal_decoupled_explicit_orientation/model.i index 4eec4cfadd..543f5e0477 100644 --- a/tests/regression/solid_mechanics/crystal_plasticity/single_crystal_decoupled_explicit_orientation/model.i +++ b/tests/regression/solid_mechanics/crystal_plasticity/single_crystal_decoupled_explicit_orientation/model.i @@ -109,7 +109,6 @@ times = 'times' prescribed_deformation_rate = 'deformation_rate' prescribed_vorticity = 'vorticity' - provide_vorticity = true ic_rot_names = 'state/orientation' ic_rot_values = 'initial_orientation' predictor = 'CP_PREVIOUS_STATE' diff --git a/tests/regression/solid_mechanics/regression_solid_mechanics.cxx b/tests/regression/solid_mechanics/regression_solid_mechanics.cxx index 0800b052e8..5e578115a3 100644 --- a/tests/regression/solid_mechanics/regression_solid_mechanics.cxx +++ b/tests/regression/solid_mechanics/regression_solid_mechanics.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include #include "utils.h" diff --git a/tests/regression/solid_mechanics/viscoplasticity/misc/polynomial/model.i b/tests/regression/solid_mechanics/viscoplasticity/misc/polynomial/model.i index 2134dc6897..e5cc41ef5f 100644 --- a/tests/regression/solid_mechanics/viscoplasticity/misc/polynomial/model.i +++ b/tests/regression/solid_mechanics/viscoplasticity/misc/polynomial/model.i @@ -208,8 +208,6 @@ stress_tile_upper_bounds = 's_ub' temperature_tile_lower_bounds = 'T_lb' temperature_tile_upper_bounds = 'T_ub' - use_AD_first_derivative = true - use_AD_second_derivative = true [] [integrate_ep] type = ScalarBackwardEulerTimeIntegration diff --git a/tests/regression/solid_mechanics/viscoplasticity/misc/torch_script/model.i b/tests/regression/solid_mechanics/viscoplasticity/misc/torch_script/model.i index cf5bcded45..987bbdedf2 100644 --- a/tests/regression/solid_mechanics/viscoplasticity/misc/torch_script/model.i +++ b/tests/regression/solid_mechanics/viscoplasticity/misc/torch_script/model.i @@ -160,8 +160,6 @@ nbatch = 20 internal_state_1_rate = 'state/G_rate' internal_state_2_rate = 'state/C_rate' torch_script = 'gold/surrogate.pt' - use_AD_first_derivative = true - use_AD_second_derivative = true [] [integrate_ep] type = ScalarBackwardEulerTimeIntegration diff --git a/tests/src/CMakeLists.txt b/tests/src/CMakeLists.txt index db3a178bf6..c0f4c13537 100644 --- a/tests/src/CMakeLists.txt +++ b/tests/src/CMakeLists.txt @@ -1,5 +1,4 @@ file(GLOB_RECURSE TEST_UTILS *.cxx) -add_library(testutils SHARED ${TEST_UTILS}) +add_library(testutils OBJECT ${TEST_UTILS}) target_link_libraries(testutils PUBLIC neml2) target_include_directories(testutils PUBLIC ${NEML2_SOURCE_DIR}/tests/include) -install(TARGETS testutils) diff --git a/tests/src/TabulatedPolynomialModel.cxx b/tests/src/TabulatedPolynomialModel.cxx index a42d99f8cf..85a00c011d 100644 --- a/tests/src/TabulatedPolynomialModel.cxx +++ b/tests/src/TabulatedPolynomialModel.cxx @@ -50,6 +50,9 @@ TabulatedPolynomialModel::expected_options() options.set>("temperature_tile_lower_bounds"); options.set>("temperature_tile_upper_bounds"); options.set("index_sharpness") = 1.0; + // Use AD + options.set("_use_AD_first_derivative") = true; + options.set("_use_AD_second_derivative") = true; return options; } diff --git a/tests/src/TorchScriptFlowRate.cxx b/tests/src/TorchScriptFlowRate.cxx index f5b73529bb..7ba98d1ee4 100644 --- a/tests/src/TorchScriptFlowRate.cxx +++ b/tests/src/TorchScriptFlowRate.cxx @@ -43,6 +43,9 @@ TorchScriptFlowRate::expected_options() options.set("internal_state_2_rate") = VariableName("state", "C_rate"); // The machine learning model options.set("torch_script"); + // Use AD + options.set("_use_AD_first_derivative") = true; + options.set("_use_AD_second_derivative") = true; return options; } diff --git a/tests/src/utils.cxx b/tests/src/utils.cxx index 86f22317c4..57ff07e21e 100644 --- a/tests/src/utils.cxx +++ b/tests/src/utils.cxx @@ -23,31 +23,3 @@ // THE SOFTWARE. #include "utils.h" - -using namespace neml2; - -void -load_model(const std::string & path, const std::string & additional_input, ParserType ptype) -{ - // We are being forward looking here - if (ptype == ParserType::AUTO) - { - if (utils::end_with(path, ".i")) - ptype = ParserType::HIT; - else if (utils::end_with(path, ".xml")) - ptype = ParserType::XML; - else if (utils::end_with(path, ".yml")) - ptype = ParserType::YAML; - } - - // but for now we only support HIT - if (ptype == ParserType::HIT) - { - Factory::clear(); - HITParser parser; - const auto all_options = parser.parse(path, additional_input); - Factory::load(all_options); - } - else - neml_assert(false, "Unsupported parser type"); -} diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt new file mode 100644 index 0000000000..290047680e --- /dev/null +++ b/tests/unit/CMakeLists.txt @@ -0,0 +1,20 @@ +include(NEML2UnityGroup) + +file(GLOB_RECURSE srcs *.cxx) +add_executable(unit_tests ${srcs}) +set_target_properties(unit_tests PROPERTIES INSTALL_RPATH "${EXEC_DIR}/../lib;${Torch_LINK_DIRECTORIES}") + +target_compile_options(unit_tests PRIVATE -Wall -Wextra -pedantic -Werror) +register_unity_group(unit_tests .) +target_link_libraries(unit_tests testutils Catch2::Catch2WithMain) + +install(TARGETS unit_tests COMPONENT Development) +install(DIRECTORY . + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/unit + COMPONENT Development + FILES_MATCHING + PATTERN "*.i" + PATTERN "*.pt" + PATTERN "*.vtest" + PATTERN "*.xml" +) diff --git a/tests/unit/base/test_CrossRef.cxx b/tests/unit/base/test_CrossRef.cxx index b0942618dc..1d58b9c5c6 100644 --- a/tests/unit/base/test_CrossRef.cxx +++ b/tests/unit/base/test_CrossRef.cxx @@ -22,7 +22,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include +#include #include "utils.h" #include "neml2/misc/math.h" @@ -55,7 +56,7 @@ TEST_CASE("CrossRef", "[base]") SECTION("empty scalar") { REQUIRE_THROWS_WITH(load_model("unit/base/test_CrossRef_empty_Scalar.i"), - Catch::Matchers::Contains("Failed to parse '' as a")); + Catch::Matchers::ContainsSubstring("Failed to parse '' as a")); } SECTION("SR2 operator=") @@ -75,6 +76,6 @@ TEST_CASE("CrossRef", "[base]") SECTION("empty tensor") { REQUIRE_THROWS_WITH(load_model("unit/base/test_CrossRef_empty_Tensor.i"), - Catch::Matchers::Contains("Failed to parse '' as a")); + Catch::Matchers::ContainsSubstring("Failed to parse '' as a")); } } diff --git a/tests/unit/base/test_Factory.cxx b/tests/unit/base/test_Factory.cxx index 5eaa061b0e..f58fbcd715 100644 --- a/tests/unit/base/test_Factory.cxx +++ b/tests/unit/base/test_Factory.cxx @@ -21,7 +21,7 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "neml2/models/SumModel.h" diff --git a/tests/unit/base/test_HITParser.cxx b/tests/unit/base/test_HITParser.cxx index 8e7edb7713..82a4fce318 100644 --- a/tests/unit/base/test_HITParser.cxx +++ b/tests/unit/base/test_HITParser.cxx @@ -21,7 +21,9 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include + +#include +#include #include "neml2/base/HITParser.h" #include "SampleParserTestingModel.h" @@ -48,8 +50,8 @@ TEST_CASE("HITParser", "[base]") SECTION("default values") { - REQUIRE(options.get("use_AD_first_derivative") == false); - REQUIRE(options.get("use_AD_second_derivative") == false); + REQUIRE(options.get("_use_AD_first_derivative") == false); + REQUIRE(options.get("_use_AD_second_derivative") == false); } SECTION("booleans") @@ -81,7 +83,7 @@ TEST_CASE("HITParser", "[base]") SECTION("Reals") { - REQUIRE(options.get("Real") == Approx(3.14159)); + REQUIRE(options.get("Real") == Catch::Approx(3.14159)); REQUIRE_THAT(options.get>("Real_vec"), Catch::Matchers::Approx(std::vector{-111, 12, 1.1})); REQUIRE_THAT(options.get>>("Real_vec_vec")[0], @@ -123,10 +125,10 @@ TEST_CASE("HITParser", "[base]") { SECTION("setting a suppressed option") { - REQUIRE_THROWS_WITH( - parser.parse("unit/base/test_HITParser2.i"), - Catch::Matchers::Contains("Option named 'suppressed_option' is suppressed, and its " - "value cannot be modified.")); + REQUIRE_THROWS_WITH(parser.parse("unit/base/test_HITParser2.i"), + Catch::Matchers::ContainsSubstring( + "Option named 'suppressed_option' is suppressed, and its " + "value cannot be modified.")); } } } diff --git a/tests/unit/base/test_OptionSet.cxx b/tests/unit/base/test_OptionSet.cxx index c48029b1cc..d5e0e845de 100644 --- a/tests/unit/base/test_OptionSet.cxx +++ b/tests/unit/base/test_OptionSet.cxx @@ -21,7 +21,9 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include + +#include +#include #include "neml2/base/OptionSet.h" @@ -42,7 +44,7 @@ TEST_CASE("OptionSet", "[base]") SECTION("get") { - REQUIRE(options.get("p1") == Approx(1.5)); + REQUIRE(options.get("p1") == Catch::Approx(1.5)); REQUIRE(options.get("p2") == "foo"); REQUIRE(options.get("p3") == 3); REQUIRE_THAT(options.get>("p4"), @@ -70,7 +72,7 @@ TEST_CASE("OptionSet", "[base]") SECTION("copy") { OptionSet options2(options); - REQUIRE(options.get("p1") == Approx(1.5)); + REQUIRE(options.get("p1") == Catch::Approx(1.5)); REQUIRE(options.get("p2") == "foo"); REQUIRE(options.get("p3") == 3); REQUIRE_THAT(options.get>("p4"), diff --git a/tests/unit/crystallography/test_CrystalGeometry.cxx b/tests/unit/crystallography/test_CrystalGeometry.cxx index db5d70d907..f7382b44de 100644 --- a/tests/unit/crystallography/test_CrystalGeometry.cxx +++ b/tests/unit/crystallography/test_CrystalGeometry.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include diff --git a/tests/unit/crystallography/test_CrystalGeometry.i b/tests/unit/crystallography/test_CrystalGeometry.i index cfd9527d78..b9bb005ab4 100644 --- a/tests/unit/crystallography/test_CrystalGeometry.i +++ b/tests/unit/crystallography/test_CrystalGeometry.i @@ -1,10 +1,10 @@ [Data] [scgeom] type = CrystalGeometry - crystal_class = "class_432" - lattice_vectors = "lvecs" - slip_directions = "sdirs" - slip_planes = "splanes" + crystal_class = 'class_432' + lattice_vectors = 'lvecs' + slip_directions = 'sdirs' + slip_planes = 'splanes' [] [] @@ -18,15 +18,14 @@ values = '1 1 1' [] [lvecs] - type = Fill3DVec - values = ' - 1.2 0.0 0.0 - 0.0 1.2 0.0 - 0.0 0.0 1.2 - ' + type = Vec + batch_shape = '(3)' + values = "1.2 0.0 0.0 + 0.0 1.2 0.0 + 0.0 0.0 1.2" [] [class_432] type = SymmetryFromOrbifold - orbifold = "432" + orbifold = 432 [] -[] \ No newline at end of file +[] diff --git a/tests/unit/crystallography/test_CubicCrystal.cxx b/tests/unit/crystallography/test_CubicCrystal.cxx index e844dfed64..73bf744589 100644 --- a/tests/unit/crystallography/test_CubicCrystal.cxx +++ b/tests/unit/crystallography/test_CubicCrystal.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include diff --git a/tests/unit/crystallography/test_MillerIndex.cxx b/tests/unit/crystallography/test_MillerIndex.cxx index d7f324be6c..676e5ced97 100644 --- a/tests/unit/crystallography/test_MillerIndex.cxx +++ b/tests/unit/crystallography/test_MillerIndex.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "neml2/models/crystallography/MillerIndex.h" #include "neml2/tensors/tensors.h" diff --git a/tests/unit/crystallography/user_tensors/test_FillMillerIndex.cxx b/tests/unit/crystallography/user_tensors/test_FillMillerIndex.cxx index f937259128..cc6c697be0 100644 --- a/tests/unit/crystallography/user_tensors/test_FillMillerIndex.cxx +++ b/tests/unit/crystallography/user_tensors/test_FillMillerIndex.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/models/crystallography/user_tensors/FillMillerIndex.h" diff --git a/tests/unit/crystallography/user_tensors/test_SymmetryFromOrbifold.cxx b/tests/unit/crystallography/user_tensors/test_SymmetryFromOrbifold.cxx index 1d1d1c2cf7..370e9f8ce6 100644 --- a/tests/unit/crystallography/user_tensors/test_SymmetryFromOrbifold.cxx +++ b/tests/unit/crystallography/user_tensors/test_SymmetryFromOrbifold.cxx @@ -22,7 +22,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include +#include #include diff --git a/tests/unit/drivers/solid_mechanics/test_SolidMechanicsDriver.cxx b/tests/unit/drivers/solid_mechanics/test_SolidMechanicsDriver.cxx index 3ad1ca8e89..6716c068ba 100644 --- a/tests/unit/drivers/solid_mechanics/test_SolidMechanicsDriver.cxx +++ b/tests/unit/drivers/solid_mechanics/test_SolidMechanicsDriver.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/drivers/solid_mechanics/SolidMechanicsDriver.h" diff --git a/tests/unit/main.cxx b/tests/unit/main.cxx deleted file mode 100644 index 740a4fd06a..0000000000 --- a/tests/unit/main.cxx +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2023, UChicago Argonne, LLC -// All Rights Reserved -// Software Name: NEML2 -- the New Engineering material Model Library, version 2 -// By: Argonne National Laboratory -// OPEN SOURCE LICENSE (MIT) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#define CATCH_CONFIG_MAIN - -#include diff --git a/tests/unit/misc/test_parser_utils.cxx b/tests/unit/misc/test_parser_utils.cxx index 2bc965f88d..04e6bc93dd 100644 --- a/tests/unit/misc/test_parser_utils.cxx +++ b/tests/unit/misc/test_parser_utils.cxx @@ -21,7 +21,9 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include + +#include +#include #include "neml2/misc/parser_utils.h" @@ -60,7 +62,7 @@ TEST_CASE("parser_utils", "[misc]") SECTION("torch::Tensor") { REQUIRE_THROWS_WITH(utils::parse("1"), - Catch::Matchers::Contains("Cannot parse torch::Tensor")); + Catch::Matchers::ContainsSubstring("Cannot parse torch::Tensor")); } SECTION("TorchShape") @@ -74,7 +76,7 @@ TEST_CASE("parser_utils", "[misc]") REQUIRE(utils::parse("()") == TorchShape{}); REQUIRE_THROWS_WITH( utils::parse("1"), - Catch::Matchers::Contains("a shape must start with '(' and end with ')'")); + Catch::Matchers::ContainsSubstring("a shape must start with '(' and end with ')'")); } SECTION("bool") @@ -82,7 +84,7 @@ TEST_CASE("parser_utils", "[misc]") REQUIRE(utils::parse("true")); REQUIRE(!utils::parse("false")); REQUIRE_THROWS_WITH(utils::parse("off"), - Catch::Matchers::Contains("Failed to parse boolean value")); + Catch::Matchers::ContainsSubstring("Failed to parse boolean value")); } } } diff --git a/tests/unit/misc/test_utils.cxx b/tests/unit/misc/test_utils.cxx index ef2d77ba97..b36bbf1196 100644 --- a/tests/unit/misc/test_utils.cxx +++ b/tests/unit/misc/test_utils.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "neml2/misc/utils.h" #include "neml2/tensors/BatchTensor.h" diff --git a/tests/unit/models/test_ParameterStore.cxx b/tests/unit/models/test_ParameterStore.cxx index 5dbd364c44..b9aa31fd30 100644 --- a/tests/unit/models/test_ParameterStore.cxx +++ b/tests/unit/models/test_ParameterStore.cxx @@ -22,7 +22,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include +#include #include "utils.h" #include "neml2/models/Model.h" @@ -89,9 +90,10 @@ TEST_CASE("ParameterStore", "[models]") { E.requires_grad_(true); model.value(); - REQUIRE_THROWS_WITH(math::jacrev(S.value(), E), - Catch::Matchers::Contains("The batch shape of the parameter must be the " - "same as the batch shape of the output")); + REQUIRE_THROWS_WITH( + math::jacrev(S.value(), E), + Catch::Matchers::ContainsSubstring("The batch shape of the parameter must be the " + "same as the batch shape of the output")); } SECTION("Jacobians are correct") diff --git a/tests/unit/models/test_models.cxx b/tests/unit/models/test_models.cxx index 86ec8ceee1..a7809b1472 100644 --- a/tests/unit/models/test_models.cxx +++ b/tests/unit/models/test_models.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include #include "utils.h" diff --git a/tests/unit/solvers/test_NonlinearSolvers.cxx b/tests/unit/solvers/test_NonlinearSolvers.cxx index 694fa2f44d..f578fde38e 100644 --- a/tests/unit/solvers/test_NonlinearSolvers.cxx +++ b/tests/unit/solvers/test_NonlinearSolvers.cxx @@ -22,7 +22,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include +#include #include "SampleNonlinearSystems.h" #include "neml2/solvers/Newton.h" diff --git a/tests/unit/solvers/test_NonlinearSystem.cxx b/tests/unit/solvers/test_NonlinearSystem.cxx index fbf275340b..b10765954d 100644 --- a/tests/unit/solvers/test_NonlinearSystem.cxx +++ b/tests/unit/solvers/test_NonlinearSystem.cxx @@ -22,7 +22,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include +#include #include "SampleNonlinearSystems.h" @@ -45,6 +46,6 @@ TEST_CASE("NonlinearSystem", "[solvers]") { system.set_solution(x0); system.init_scaling(); - REQUIRE(torch::max(torch::linalg_cond(system.Jacobian(x0))).item() == Approx(1.0)); + REQUIRE(torch::max(torch::linalg_cond(system.Jacobian(x0))).item() == Catch::Approx(1.0)); } } diff --git a/tests/unit/tensors/test_BatchTensor.cxx b/tests/unit/tensors/test_BatchTensor.cxx index f76f4d5393..b981406098 100644 --- a/tests/unit/tensors/test_BatchTensor.cxx +++ b/tests/unit/tensors/test_BatchTensor.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "neml2/tensors/BatchTensor.h" diff --git a/tests/unit/tensors/test_BatchTensorBase.cxx b/tests/unit/tensors/test_BatchTensorBase.cxx index c34db41e16..18218a2af0 100644 --- a/tests/unit/tensors/test_BatchTensorBase.cxx +++ b/tests/unit/tensors/test_BatchTensorBase.cxx @@ -22,7 +22,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include +#include #include "neml2/tensors/BatchTensor.h" @@ -378,7 +379,7 @@ TEST_CASE("BatchTensorBase", "[tensors]") REQUIRE(b.storage().data_ptr() == a.storage().data_ptr()); REQUIRE(b.batch_sizes() == s); REQUIRE(b.base_sizes() == a.base_sizes()); - REQUIRE(torch::sum(a - b).item() == Approx(0)); + REQUIRE(torch::sum(a - b).item() == Catch::Approx(0)); } SECTION("base_expand") @@ -394,7 +395,7 @@ TEST_CASE("BatchTensorBase", "[tensors]") // tensors because they have different base shapes. However, they _should_ be broadcastable // based on libTorch's original broadcasting rules. So we need to interpret them as // torch::Tensors first before we can compute a - b. This is the correct behavior. - REQUIRE(torch::sum(torch::Tensor(a) - torch::Tensor(b)).item() == Approx(0)); + REQUIRE(torch::sum(torch::Tensor(a) - torch::Tensor(b)).item() == Catch::Approx(0)); } SECTION("batch_expand_as") @@ -427,7 +428,7 @@ TEST_CASE("BatchTensorBase", "[tensors]") auto b = a.batch_expand_copy(s); REQUIRE(b.batch_sizes() == s); REQUIRE(b.base_sizes() == a.base_sizes()); - REQUIRE(torch::sum(a - b).item() == Approx(0)); + REQUIRE(torch::sum(a - b).item() == Catch::Approx(0)); } SECTION("base_expand_copy") @@ -442,7 +443,7 @@ TEST_CASE("BatchTensorBase", "[tensors]") // tensors because they have different base shapes. However, they _should_ be broadcastable // based on libTorch's original broadcasting rules. So we need to interpret them as // torch::Tensors first before we can compute a - b. This is the correct behavior. - REQUIRE(torch::sum(torch::Tensor(a) - torch::Tensor(b)).item() == Approx(0)); + REQUIRE(torch::sum(torch::Tensor(a) - torch::Tensor(b)).item() == Catch::Approx(0)); } SECTION("batch_unsqueeze") diff --git a/tests/unit/tensors/test_FixedDimTensor.cxx b/tests/unit/tensors/test_FixedDimTensor.cxx index 797ead7128..cfbf1ef000 100644 --- a/tests/unit/tensors/test_FixedDimTensor.cxx +++ b/tests/unit/tensors/test_FixedDimTensor.cxx @@ -22,7 +22,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include +#include #include "neml2/tensors/Scalar.h" #include "neml2/tensors/Vec.h" @@ -94,7 +95,8 @@ TEST_CASE("FixedDimTensor", "[tensors]") #ifndef NDEBUG // Calling .defined() to make sure this doesn't get optimized away... - REQUIRE_THROWS_WITH(SR2(a, Bn).defined(), Catch::Matchers::Contains("Base shape mismatch")); + REQUIRE_THROWS_WITH(SR2(a, Bn).defined(), + Catch::Matchers::ContainsSubstring("Base shape mismatch")); #endif } @@ -110,7 +112,8 @@ TEST_CASE("FixedDimTensor", "[tensors]") #ifndef NDEBUG // Calling .defined() to make sure this doesn't get optimized away... - REQUIRE_THROWS_WITH(SR2(a).defined(), Catch::Matchers::Contains("Base shape mismatch")); + REQUIRE_THROWS_WITH(SR2(a).defined(), + Catch::Matchers::ContainsSubstring("Base shape mismatch")); #endif } } diff --git a/tests/unit/tensors/test_LabeledAxis.cxx b/tests/unit/tensors/test_LabeledAxis.cxx index 100b1eda99..c06eeb9d37 100644 --- a/tests/unit/tensors/test_LabeledAxis.cxx +++ b/tests/unit/tensors/test_LabeledAxis.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "neml2/tensors/LabeledAxis.h" #include "neml2/tensors/tensors.h" diff --git a/tests/unit/tensors/test_LabeledAxisAccesor.cxx b/tests/unit/tensors/test_LabeledAxisAccesor.cxx index 8792b80e18..7c53e90764 100644 --- a/tests/unit/tensors/test_LabeledAxisAccesor.cxx +++ b/tests/unit/tensors/test_LabeledAxisAccesor.cxx @@ -22,7 +22,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include +#include #include "neml2/misc/utils.h" #include "neml2/tensors/LabeledAxisAccessor.h" @@ -38,7 +39,7 @@ TEST_CASE("LabeledAxisAccessor", "[tensors]") SECTION("LabeledAxisAccessor") { REQUIRE_THROWS_WITH(LabeledAxisAccessor("a.b", "c", "d"), - Catch::Matchers::Contains("Invalid item name")); + Catch::Matchers::ContainsSubstring("Invalid item name")); } SECTION("vec") { REQUIRE(a.vec() == std::vector{"a", "b", "c"}); } diff --git a/tests/unit/tensors/test_LabeledTensor.cxx b/tests/unit/tensors/test_LabeledTensor.cxx index a0fa5a3daf..aec6e59069 100644 --- a/tests/unit/tensors/test_LabeledTensor.cxx +++ b/tests/unit/tensors/test_LabeledTensor.cxx @@ -22,7 +22,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include +#include #include "neml2/tensors/LabeledTensor.h" #include "neml2/tensors/LabeledVector.h" @@ -114,21 +115,21 @@ TEST_CASE("LabeledTensor", "[tensors]") { auto A = LabeledVector::zeros(nbatch, {&info1}); A.set(BatchTensor::ones(6).batch_expand(nbatch), "first"); - REQUIRE(torch::sum(A("first")).item() == Approx(nbatch * 6)); - REQUIRE(torch::sum(A("second")).item() == Approx(0)); - REQUIRE(torch::sum(A("third")).item() == Approx(0)); + REQUIRE(torch::sum(A("first")).item() == Catch::Approx(nbatch * 6)); + REQUIRE(torch::sum(A("second")).item() == Catch::Approx(0)); + REQUIRE(torch::sum(A("third")).item() == Catch::Approx(0)); } SECTION("logically 2D LabeledTensor") { auto A = LabeledMatrix::zeros(nbatch, {&info1, &info2}); A.set(BatchTensor::ones({1, 6}).batch_expand(nbatch), "third", "second"); - REQUIRE(torch::sum(A("first", "first")).item() == Approx(0)); - REQUIRE(torch::sum(A("first", "second")).item() == Approx(0)); - REQUIRE(torch::sum(A("second", "first")).item() == Approx(0)); - REQUIRE(torch::sum(A("second", "second")).item() == Approx(0)); - REQUIRE(torch::sum(A("third", "first")).item() == Approx(0)); - REQUIRE(torch::sum(A("third", "second")).item() == Approx(nbatch * 6)); + REQUIRE(torch::sum(A("first", "first")).item() == Catch::Approx(0)); + REQUIRE(torch::sum(A("first", "second")).item() == Catch::Approx(0)); + REQUIRE(torch::sum(A("second", "first")).item() == Catch::Approx(0)); + REQUIRE(torch::sum(A("second", "second")).item() == Catch::Approx(0)); + REQUIRE(torch::sum(A("third", "first")).item() == Catch::Approx(0)); + REQUIRE(torch::sum(A("third", "second")).item() == Catch::Approx(nbatch * 6)); } } @@ -154,8 +155,8 @@ TEST_CASE("LabeledTensor", "[tensors]") // Since B is a deep copy, modifying B shouldn't affect A. B.set(BatchTensor::ones({1, 6}).batch_expand(nbatch), "third", "second"); - REQUIRE(torch::sum(A("third", "second")).item() == Approx(0)); - REQUIRE(torch::sum(B("third", "second")).item() == Approx(nbatch * 6)); + REQUIRE(torch::sum(A("third", "second")).item() == Catch::Approx(0)); + REQUIRE(torch::sum(B("third", "second")).item() == Catch::Approx(nbatch * 6)); } SECTION("slice") @@ -179,8 +180,8 @@ TEST_CASE("LabeledTensor", "[tensors]") auto A = LabeledVector::zeros(nbatch, {&info1}); A.set(2.3 * BatchTensor::ones(7).batch_expand(nbatch), "sub1"); auto B = A.slice("sub1"); - REQUIRE(torch::sum(B("first")).item() == Approx(nbatch * 6 * 2.3)); - REQUIRE(torch::sum(B("second")).item() == Approx(nbatch * 2.3)); + REQUIRE(torch::sum(B("first")).item() == Catch::Approx(nbatch * 6 * 2.3)); + REQUIRE(torch::sum(B("second")).item() == Catch::Approx(nbatch * 2.3)); } SECTION("logically 2D LabeledTensor") @@ -188,10 +189,12 @@ TEST_CASE("LabeledTensor", "[tensors]") auto A = LabeledMatrix::zeros(nbatch, {&info1, &info2}); A.set(-1.9 * BatchTensor::ones({7, 6}).batch_expand(nbatch), "sub1", "second"); auto B = A.slice(0, "sub1"); - REQUIRE(torch::sum(B("first", "first")).item() == Approx(0)); - REQUIRE(torch::sum(B("first", "second")).item() == Approx(nbatch * 6 * 6 * -1.9)); - REQUIRE(torch::sum(B("second", "first")).item() == Approx(0)); - REQUIRE(torch::sum(B("second", "second")).item() == Approx(nbatch * 1 * 6 * -1.9)); + REQUIRE(torch::sum(B("first", "first")).item() == Catch::Approx(0)); + REQUIRE(torch::sum(B("first", "second")).item() == + Catch::Approx(nbatch * 6 * 6 * -1.9)); + REQUIRE(torch::sum(B("second", "first")).item() == Catch::Approx(0)); + REQUIRE(torch::sum(B("second", "second")).item() == + Catch::Approx(nbatch * 1 * 6 * -1.9)); } } } diff --git a/tests/unit/tensors/test_Quaternion.cxx b/tests/unit/tensors/test_Quaternion.cxx index b37fd06b6f..07a647290b 100644 --- a/tests/unit/tensors/test_Quaternion.cxx +++ b/tests/unit/tensors/test_Quaternion.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/tensors.h" diff --git a/tests/unit/tensors/test_R2.cxx b/tests/unit/tensors/test_R2.cxx index 53e992be89..f57dc2377a 100644 --- a/tests/unit/tensors/test_R2.cxx +++ b/tests/unit/tensors/test_R2.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/tensors.h" diff --git a/tests/unit/tensors/test_R3.cxx b/tests/unit/tensors/test_R3.cxx index 38963ad95c..ea48c94cc8 100644 --- a/tests/unit/tensors/test_R3.cxx +++ b/tests/unit/tensors/test_R3.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/tensors.h" diff --git a/tests/unit/tensors/test_R4.cxx b/tests/unit/tensors/test_R4.cxx index 8afe3b28ea..0c2e4dec1d 100644 --- a/tests/unit/tensors/test_R4.cxx +++ b/tests/unit/tensors/test_R4.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/tensors.h" diff --git a/tests/unit/tensors/test_Rot.cxx b/tests/unit/tensors/test_Rot.cxx index 88a64beb53..379317ab1d 100644 --- a/tests/unit/tensors/test_Rot.cxx +++ b/tests/unit/tensors/test_Rot.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/tensors.h" diff --git a/tests/unit/tensors/test_SR2.cxx b/tests/unit/tensors/test_SR2.cxx index ae5d7cd15f..d4729aa6fb 100644 --- a/tests/unit/tensors/test_SR2.cxx +++ b/tests/unit/tensors/test_SR2.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/tensors.h" diff --git a/tests/unit/tensors/test_SSR4.cxx b/tests/unit/tensors/test_SSR4.cxx index 4c570094ef..69e0c57e1e 100644 --- a/tests/unit/tensors/test_SSR4.cxx +++ b/tests/unit/tensors/test_SSR4.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/tensors.h" diff --git a/tests/unit/tensors/test_Scalar.cxx b/tests/unit/tensors/test_Scalar.cxx index 33986c5ea2..5a7a640768 100644 --- a/tests/unit/tensors/test_Scalar.cxx +++ b/tests/unit/tensors/test_Scalar.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/tensors.h" diff --git a/tests/unit/tensors/test_Vec.cxx b/tests/unit/tensors/test_Vec.cxx index 35ed52bccd..b22a6b771f 100644 --- a/tests/unit/tensors/test_Vec.cxx +++ b/tests/unit/tensors/test_Vec.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/tensors.h" diff --git a/tests/unit/tensors/test_VecBase.cxx b/tests/unit/tensors/test_VecBase.cxx index 97b4d2737a..9a4028414c 100644 --- a/tests/unit/tensors/test_VecBase.cxx +++ b/tests/unit/tensors/test_VecBase.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/tensors.h" diff --git a/tests/unit/tensors/test_WR2.cxx b/tests/unit/tensors/test_WR2.cxx index d799770603..5057360db7 100644 --- a/tests/unit/tensors/test_WR2.cxx +++ b/tests/unit/tensors/test_WR2.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/tensors.h" diff --git a/tests/unit/tensors/test_WWR4.cxx b/tests/unit/tensors/test_WWR4.cxx index 6e385291c2..2c6da7221c 100644 --- a/tests/unit/tensors/test_WWR4.cxx +++ b/tests/unit/tensors/test_WWR4.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/tensors.h" diff --git a/tests/unit/tensors/test_list_tensors.cxx b/tests/unit/tensors/test_list_tensors.cxx index d30a098387..4335305558 100644 --- a/tests/unit/tensors/test_list_tensors.cxx +++ b/tests/unit/tensors/test_list_tensors.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/tensors.h" diff --git a/tests/unit/tensors/test_symmetry_operator_definitions.cxx b/tests/unit/tensors/test_symmetry_operator_definitions.cxx index 4ba89f4295..c78aa49cd2 100644 --- a/tests/unit/tensors/test_symmetry_operator_definitions.cxx +++ b/tests/unit/tensors/test_symmetry_operator_definitions.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "neml2/tensors/tensors.h" #include "neml2/tensors/Transformable.h" diff --git a/tests/unit/tensors/test_vector_transform.cxx b/tests/unit/tensors/test_vector_transform.cxx index f30e1847db..c3f92eebc6 100644 --- a/tests/unit/tensors/test_vector_transform.cxx +++ b/tests/unit/tensors/test_vector_transform.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "neml2/tensors/Transformable.h" diff --git a/tests/unit/tensors/user_tensors/test_EmptyBatchTensor.cxx b/tests/unit/tensors/user_tensors/test_EmptyBatchTensor.cxx index 21b3a25f7f..105ab015be 100644 --- a/tests/unit/tensors/user_tensors/test_EmptyBatchTensor.cxx +++ b/tests/unit/tensors/user_tensors/test_EmptyBatchTensor.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/user_tensors/EmptyBatchTensor.h" diff --git a/tests/unit/tensors/user_tensors/test_EmptyFixedDimTensor.cxx b/tests/unit/tensors/user_tensors/test_EmptyFixedDimTensor.cxx index 54c28dd82f..1420edcf62 100644 --- a/tests/unit/tensors/user_tensors/test_EmptyFixedDimTensor.cxx +++ b/tests/unit/tensors/user_tensors/test_EmptyFixedDimTensor.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/user_tensors/EmptyFixedDimTensor.h" diff --git a/tests/unit/tensors/user_tensors/test_Fill3DVec.cxx b/tests/unit/tensors/user_tensors/test_Fill3DVec.cxx deleted file mode 100644 index 01cae308f3..0000000000 --- a/tests/unit/tensors/user_tensors/test_Fill3DVec.cxx +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2023, UChicago Argonne, LLC -// All Rights Reserved -// Software Name: NEML2 -- the New Engineering material Model Library, version 2 -// By: Argonne National Laboratory -// OPEN SOURCE LICENSE (MIT) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#include - -#include "utils.h" -#include "neml2/tensors/user_tensors/Fill3DVec.h" - -using namespace neml2; - -TEST_CASE("Fill3DVec", "[tensors/user_tensors]") -{ - load_model("unit/tensors/user_tensors/test_Fill3DVec.i"); - - const auto valid_1 = Factory::get_object_ptr("Tensors", "v1"); - const auto correct_1 = Vec::fill(1.0, 2.0, 3.0); - REQUIRE(torch::allclose(*valid_1, correct_1)); - - const auto valid_4 = Factory::get_object_ptr("Tensors", "v4"); - const auto correct_4 = - Vec(torch::tensor({{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}, {7.0, 8.0, 9.0}, {10.0, 11.0, 12.0}}, - default_tensor_options())); - REQUIRE(torch::allclose(*valid_4, correct_4)); - - REQUIRE_THROWS(Factory::get_object_ptr("Tensors", "invalid")); -} diff --git a/tests/unit/tensors/user_tensors/test_Fill3DVec.i b/tests/unit/tensors/user_tensors/test_Fill3DVec.i deleted file mode 100644 index 0ec988fb19..0000000000 --- a/tests/unit/tensors/user_tensors/test_Fill3DVec.i +++ /dev/null @@ -1,14 +0,0 @@ -[Tensors] - [v1] - type = Fill3DVec - values = '1 2 3' - [] - [v4] - type = Fill3DVec - values = '1 2 3 4 5 6 7 8 9 10 11 12' - [] - [invalid] - type = Fill3DVec - values = '1 2 3 4 5 6 7 8 9 10 11' - [] -[] diff --git a/tests/unit/tensors/user_tensors/test_FillR2.cxx b/tests/unit/tensors/user_tensors/test_FillR2.cxx index a1d9149c4b..97d5e7cb25 100644 --- a/tests/unit/tensors/user_tensors/test_FillR2.cxx +++ b/tests/unit/tensors/user_tensors/test_FillR2.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/user_tensors/FillR2.h" diff --git a/tests/unit/tensors/user_tensors/test_FillSR2.cxx b/tests/unit/tensors/user_tensors/test_FillSR2.cxx index 3f113758ba..d6a1378ad5 100644 --- a/tests/unit/tensors/user_tensors/test_FillSR2.cxx +++ b/tests/unit/tensors/user_tensors/test_FillSR2.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/user_tensors/FillSR2.h" diff --git a/tests/unit/tensors/user_tensors/test_FullBatchTensor.cxx b/tests/unit/tensors/user_tensors/test_FullBatchTensor.cxx index 6792a1e22c..b27c002314 100644 --- a/tests/unit/tensors/user_tensors/test_FullBatchTensor.cxx +++ b/tests/unit/tensors/user_tensors/test_FullBatchTensor.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/user_tensors/FullBatchTensor.h" diff --git a/tests/unit/tensors/user_tensors/test_FullFixedDimTensor.cxx b/tests/unit/tensors/user_tensors/test_FullFixedDimTensor.cxx index ef7c77a9ec..113f366a23 100644 --- a/tests/unit/tensors/user_tensors/test_FullFixedDimTensor.cxx +++ b/tests/unit/tensors/user_tensors/test_FullFixedDimTensor.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/user_tensors/FullFixedDimTensor.h" diff --git a/tests/unit/tensors/user_tensors/test_IdentityBatchTensor.cxx b/tests/unit/tensors/user_tensors/test_IdentityBatchTensor.cxx index 87df9d203a..a4c5d5cf55 100644 --- a/tests/unit/tensors/user_tensors/test_IdentityBatchTensor.cxx +++ b/tests/unit/tensors/user_tensors/test_IdentityBatchTensor.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/user_tensors/IdentityBatchTensor.h" diff --git a/tests/unit/tensors/user_tensors/test_LinspaceBatchTensor.cxx b/tests/unit/tensors/user_tensors/test_LinspaceBatchTensor.cxx index b1e8732d3a..6a8a48eec8 100644 --- a/tests/unit/tensors/user_tensors/test_LinspaceBatchTensor.cxx +++ b/tests/unit/tensors/user_tensors/test_LinspaceBatchTensor.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/user_tensors/LinspaceBatchTensor.h" diff --git a/tests/unit/tensors/user_tensors/test_LinspaceFixedDimTensor.cxx b/tests/unit/tensors/user_tensors/test_LinspaceFixedDimTensor.cxx index 2d2485f4b9..795eedce15 100644 --- a/tests/unit/tensors/user_tensors/test_LinspaceFixedDimTensor.cxx +++ b/tests/unit/tensors/user_tensors/test_LinspaceFixedDimTensor.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/user_tensors/LinspaceFixedDimTensor.h" diff --git a/tests/unit/tensors/user_tensors/test_LogspaceBatchTensor.cxx b/tests/unit/tensors/user_tensors/test_LogspaceBatchTensor.cxx index 0d1c1eea4a..40d70dea55 100644 --- a/tests/unit/tensors/user_tensors/test_LogspaceBatchTensor.cxx +++ b/tests/unit/tensors/user_tensors/test_LogspaceBatchTensor.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/user_tensors/LogspaceBatchTensor.h" diff --git a/tests/unit/tensors/user_tensors/test_LogspaceFixedDimTensor.cxx b/tests/unit/tensors/user_tensors/test_LogspaceFixedDimTensor.cxx index 4f627939ae..b50232229c 100644 --- a/tests/unit/tensors/user_tensors/test_LogspaceFixedDimTensor.cxx +++ b/tests/unit/tensors/user_tensors/test_LogspaceFixedDimTensor.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/user_tensors/LogspaceFixedDimTensor.h" diff --git a/tests/unit/tensors/user_tensors/test_OnesBatchTensor.cxx b/tests/unit/tensors/user_tensors/test_OnesBatchTensor.cxx index ee7705c0f0..2a89e79785 100644 --- a/tests/unit/tensors/user_tensors/test_OnesBatchTensor.cxx +++ b/tests/unit/tensors/user_tensors/test_OnesBatchTensor.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/user_tensors/OnesBatchTensor.h" diff --git a/tests/unit/tensors/user_tensors/test_OnesFixedDimTensor.cxx b/tests/unit/tensors/user_tensors/test_OnesFixedDimTensor.cxx index dac7b95a47..e5d6c0843a 100644 --- a/tests/unit/tensors/user_tensors/test_OnesFixedDimTensor.cxx +++ b/tests/unit/tensors/user_tensors/test_OnesFixedDimTensor.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/user_tensors/OnesFixedDimTensor.h" diff --git a/tests/unit/tensors/user_tensors/test_Orientation.cxx b/tests/unit/tensors/user_tensors/test_Orientation.cxx index e1b9a164c4..717f6d6e9c 100644 --- a/tests/unit/tensors/user_tensors/test_Orientation.cxx +++ b/tests/unit/tensors/user_tensors/test_Orientation.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/user_tensors/Orientation.h" diff --git a/tests/unit/tensors/user_tensors/test_UserBatchTensor.cxx b/tests/unit/tensors/user_tensors/test_UserBatchTensor.cxx index b7effa3332..fa880b46e6 100644 --- a/tests/unit/tensors/user_tensors/test_UserBatchTensor.cxx +++ b/tests/unit/tensors/user_tensors/test_UserBatchTensor.cxx @@ -22,7 +22,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include +#include #include "utils.h" #include "neml2/tensors/user_tensors/UserBatchTensor.h" @@ -70,7 +71,7 @@ TEST_CASE("UserBatchTensor", "[tensors/user_tensors]") REQUIRE_THROWS_WITH( Factory::get_object_ptr("Tensors", "a"), - Catch::Matchers::Contains("Number of values 1 must equal to either the " - "base storage size 6 or the total storage size 12")); + Catch::Matchers::ContainsSubstring("Number of values 1 must equal to either the " + "base storage size 6 or the total storage size 12")); } } diff --git a/tests/unit/tensors/user_tensors/test_UserFixedDimTensor.cxx b/tests/unit/tensors/user_tensors/test_UserFixedDimTensor.cxx index af9d433c37..2199cd0db8 100644 --- a/tests/unit/tensors/user_tensors/test_UserFixedDimTensor.cxx +++ b/tests/unit/tensors/user_tensors/test_UserFixedDimTensor.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/user_tensors/UserFixedDimTensor.h" diff --git a/tests/unit/tensors/user_tensors/test_ZerosBatchTensor.cxx b/tests/unit/tensors/user_tensors/test_ZerosBatchTensor.cxx index 6719c56d3f..c39d2de2f9 100644 --- a/tests/unit/tensors/user_tensors/test_ZerosBatchTensor.cxx +++ b/tests/unit/tensors/user_tensors/test_ZerosBatchTensor.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/user_tensors/ZerosBatchTensor.h" diff --git a/tests/unit/tensors/user_tensors/test_ZerosFixedDimTensor.cxx b/tests/unit/tensors/user_tensors/test_ZerosFixedDimTensor.cxx index 370cd2e56f..d09d8dea74 100644 --- a/tests/unit/tensors/user_tensors/test_ZerosFixedDimTensor.cxx +++ b/tests/unit/tensors/user_tensors/test_ZerosFixedDimTensor.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include "utils.h" #include "neml2/tensors/user_tensors/ZerosFixedDimTensor.h" diff --git a/tests/verification/CMakeLists.txt b/tests/verification/CMakeLists.txt new file mode 100644 index 0000000000..f72a8d9a32 --- /dev/null +++ b/tests/verification/CMakeLists.txt @@ -0,0 +1,20 @@ +include(NEML2UnityGroup) + +file(GLOB_RECURSE srcs *.cxx) +add_executable(verification_tests ${srcs}) +set_target_properties(verification_tests PROPERTIES INSTALL_RPATH "${EXEC_DIR}/../lib;${Torch_LINK_DIRECTORIES}") + +target_compile_options(verification_tests PRIVATE -Wall -Wextra -pedantic -Werror) +register_unity_group(verification_tests .) +target_link_libraries(verification_tests PRIVATE testutils Catch2::Catch2WithMain) + +install(TARGETS verification_tests COMPONENT Development) +install(DIRECTORY . + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/verification + COMPONENT Development + FILES_MATCHING + PATTERN "*.i" + PATTERN "*.pt" + PATTERN "*.vtest" + PATTERN "*.xml" +) diff --git a/tests/verification/main.cxx b/tests/verification/main.cxx deleted file mode 100644 index 740a4fd06a..0000000000 --- a/tests/verification/main.cxx +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2023, UChicago Argonne, LLC -// All Rights Reserved -// Software Name: NEML2 -- the New Engineering material Model Library, version 2 -// By: Argonne National Laboratory -// OPEN SOURCE LICENSE (MIT) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#define CATCH_CONFIG_MAIN - -#include diff --git a/tests/verification/solid_mechanics/cp_basic/cp_basic.i b/tests/verification/solid_mechanics/cp_basic/cp_basic.i index a14adfd9ec..bb1e3a9ded 100644 --- a/tests/verification/solid_mechanics/cp_basic/cp_basic.i +++ b/tests/verification/solid_mechanics/cp_basic/cp_basic.i @@ -48,7 +48,6 @@ times = 'times' prescribed_deformation_rate = 'deformation_rate' prescribed_vorticity = 'vorticity' - provide_vorticity = true ic_rot_names = 'state/orientation' ic_rot_values = 'initial_orientation' predictor = 'CP_PREVIOUS_STATE' diff --git a/tests/verification/solid_mechanics/cp_taylor/cp_taylor.i b/tests/verification/solid_mechanics/cp_taylor/cp_taylor.i index 4dbe00904c..86bf767bc3 100644 --- a/tests/verification/solid_mechanics/cp_taylor/cp_taylor.i +++ b/tests/verification/solid_mechanics/cp_taylor/cp_taylor.i @@ -58,7 +58,6 @@ times = 'times' prescribed_deformation_rate = 'deformation_rate' prescribed_vorticity = 'vorticity' - provide_vorticity = true ic_rot_names = 'state/orientation' ic_rot_values = 'initial_orientation' predictor = 'CP_PREVIOUS_STATE' diff --git a/tests/verification/solid_mechanics/verification_solid_mechanics.cxx b/tests/verification/solid_mechanics/verification_solid_mechanics.cxx index 2f11e7b4e5..5c712ccf3d 100644 --- a/tests/verification/solid_mechanics/verification_solid_mechanics.cxx +++ b/tests/verification/solid_mechanics/verification_solid_mechanics.cxx @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#include +#include #include #include "utils.h"