diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eb6f4cfcb72..c500d5ec5c8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,7 +38,7 @@ repos: rev: v2.2.2 hooks: - id: codespell - exclude: 'ThirdParty/.*|Tests/Data/.*|.*\.ya?ml|.*\.bib|^web/content/imprint.md|^GeoLib/IO/XmlIO/OpenGeoSysSTN.xsd|^Applications/FileIO/Legacy/OGSIOVer4.cpp|^scripts/cmake/CPM.cmake|Documentation/.vale/.*' + exclude: 'ThirdParty/.*|Tests/Data/.*|.*\.ya?ml|.*\.bib|^web/content/imprint.md|^GeoLib/IO/XmlIO/OpenGeoSysSTN.xsd|^Applications/FileIO/Legacy/OGSIOVer4.cpp|^scripts/cmake/CPM.cmake|Documentation/.vale/.*|.*\.ipynb|.*\.svg' - repo: https://github.com/cheshirekow/cmake-format-precommit rev: v0.6.13 hooks: @@ -73,11 +73,15 @@ repos: language: system pass_filenames: false files: \.(h|cpp)$ + - repo: https://github.com/errata-ai/vale + rev: v2.24.4 + hooks: - id: vale - name: Check spelling with vale - entry: bash -c 'if command -v vale &> /dev/null; then vale sync; VALE_MIN_ALERT_LEVEL=error scripts/ci/helper/run-vale; else exit 0; fi' + name: vale sync pass_filenames: false - language: system + args: [sync] + - id: vale + args: [--output=line, --minAlertLevel=error] - repo: https://github.com/charliermarsh/ruff-pre-commit rev: "v0.0.262" hooks: diff --git a/Applications/Utils/Tests.cmake b/Applications/Utils/Tests.cmake index 5861f9dacac..9b402600a50 100644 --- a/Applications/Utils/Tests.cmake +++ b/Applications/Utils/Tests.cmake @@ -1403,3 +1403,9 @@ AddTest( square_1x1_geometry_bottom.vtu square_1x1_geometry_bottom.vtu 1e-16 square_1x1_geometry_top.vtu square_1x1_geometry_top.vtu 1e-16 ) + +if(NOT OGS_USE_PETSC) + NotebookTest(NOTEBOOKFILE ../../web/content/docs/tutorials/bhe_meshing/notebook-bhe_meshing.md + PYTHON_PACKAGES openpyxl + RUNTIME 10) +endif() diff --git a/Documentation/.vale/Vocab/ogs/accept.txt b/Documentation/.vale/Vocab/ogs/accept.txt index c105e986edb..7dcf5e309f6 100644 --- a/Documentation/.vale/Vocab/ogs/accept.txt +++ b/Documentation/.vale/Vocab/ogs/accept.txt @@ -40,7 +40,7 @@ fmt Forchheimer Fortran Francesco -frontmatter +[Ff]rontmatter Galerkin Genuchten Geoenergy diff --git a/ProcessLib/LiquidFlow/Tests.cmake b/ProcessLib/LiquidFlow/Tests.cmake index 14a043de936..c21870da756 100644 --- a/ProcessLib/LiquidFlow/Tests.cmake +++ b/ProcessLib/LiquidFlow/Tests.cmake @@ -611,7 +611,7 @@ if(NOT OGS_USE_MPI) OgsTest(PROJECTFILE Parabolic/LiquidFlow/SimpleSynthetics/PrimaryVariableConstraintDirichletBC/cuboid_1x1x1_hex_1000_Dirichlet_Dirichlet_3.prj) OgsTest(PROJECTFILE Parabolic/LiquidFlow/SimpleSynthetics/FunctionParameterTest.prj) OgsTest(PROJECTFILE Parabolic/LiquidFlow/BlockingConductingFracture/block_conduct_frac.prj) - NotebookTest(NOTEBOOKFILE Parabolic/LiquidFlow/BlockingConductingFracture/LiquidFlow.ipynb RUNTIME 6) + NotebookTest(NOTEBOOKFILE Parabolic/LiquidFlow/BlockingConductingFracture/BlockingConductingFracture.ipynb RUNTIME 6) endif() # inclined mesh diff --git a/Tests/Data/Elliptic/cube_1x1x1_SteadyStateDiffusion/ssd-cube.ipynb b/Tests/Data/Elliptic/cube_1x1x1_SteadyStateDiffusion/ssd-cube.ipynb index 933e2d5d9a9..b77bc7d1d80 100644 --- a/Tests/Data/Elliptic/cube_1x1x1_SteadyStateDiffusion/ssd-cube.ipynb +++ b/Tests/Data/Elliptic/cube_1x1x1_SteadyStateDiffusion/ssd-cube.ipynb @@ -10,14 +10,24 @@ "author = \"Lars Bilke\"\n", "web_subsection = \"elliptic\"\n", "draft = true\n", - "" + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "35326dbe", + "metadata": {}, + "source": [ + "This notebook is just for testing and does not show up on the web page because `draft = true` in the metadata." ] }, { "cell_type": "code", "execution_count": 5, "id": "59877add-6b0e-4ee4-9bca-301ea5c23f6d", - "metadata": {}, + "metadata": { + "lines_to_next_cell": 2 + }, "outputs": [], "source": [ "import os\n", @@ -41,7 +51,9 @@ "cell_type": "code", "execution_count": 14, "id": "6c5503b0-ba41-4273-b19a-0fafdb717bb0", - "metadata": {}, + "metadata": { + "lines_to_next_cell": 2 + }, "outputs": [], "source": [ "resolution = \"2e4\"\n", @@ -78,14 +90,6 @@ "plotter.add_mesh(mesh, scalars=\"v\") # pressure\n", "plotter.show()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "651a2d8c-52d8-448c-ab63-30f95ad70f3d", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/Tests/Data/Mechanics/Linear/DiscWithHole/Linear_Disc_with_hole.md b/Tests/Data/Mechanics/Linear/DiscWithHole/Linear_Disc_with_hole.md index d32816ba60c..b0e481c5e4f 100644 --- a/Tests/Data/Mechanics/Linear/DiscWithHole/Linear_Disc_with_hole.md +++ b/Tests/Data/Mechanics/Linear/DiscWithHole/Linear_Disc_with_hole.md @@ -1,26 +1,12 @@ ---- -jupyter: - jupytext: - text_representation: - extension: .md - format_name: markdown - format_version: '1.3' - jupytext_version: 1.14.5 - kernelspec: - display_name: Python 3 (ipykernel) - language: python - name: python3 ---- - - ++++ title = "Linear elasticity: disc with hole" date = "2022-04-27" author = "Linda Günther, Sophia Einspänner, Robert Habel, Christoph Lehmann and Thomas Nagel" web_subsection = "small-deformations" - - ++++ -|
||| + +|||| |---|---|--:| In this benchmark example we consider a linear elastic small deformation problem. More specifically, a plate with a central hole that is put under tension on its top boundary is simulated. By exploiting symmetries, below we evaluate this problem just for the top right quarter of this disc. @@ -119,7 +105,7 @@ def kirsch_sig_rt(sig, r, theta, a): ### Stress distribution along the $x$-axis ($\theta= -90°$, orthogonal to the load) -At larger distances from the hole the stresses are approximately distibuted as if the plate had no hole ($\sigma_{rr} \equiv \sigma_{xx} \approx 0$, $\sigma_{r\theta} \equiv \sigma_{xy} = 0$, $\sigma_{\theta\theta} \equiv \sigma_{yy} = 10\,\text{kPa}$). +At larger distances from the hole the stresses are approximately distributed as if the plate had no hole ($\sigma_{rr} \equiv \sigma_{xx} \approx 0$, $\sigma_{r\theta} \equiv \sigma_{xy} = 0$, $\sigma_{\theta\theta} \equiv \sigma_{yy} = 10\,\text{kPa}$). As the hole is approached, the tangential stress increases until it reaches its maximum value directly at the contour. Interestingly, that value is three times as high as the applied traction. It can be concluded that the hole leads to a **threefold stress concentration**. @@ -244,7 +230,7 @@ $$ For the simulation of the numerical model, some assumptions have to be made: -* The analytical solution is stricty correct only for an infinitely extended plate. +* The analytical solution is strictly correct only for an infinitely extended plate. This requirement can only be approximated in the numerical method. The dimensions of the considered plate must be much larger than the hole. With a hole diameter of $4\, \text{cm}$ and a size of $20\times20\, \text{cm}$ the plate can be considered as infinitely extended. Indeed, a general rule implied that the range of borders should be about five times the size of the cavity. * For numerical simulations, it is necessary to specify the mesh resolution with which the model is calculated. A finer mesh provides more detailed gradients but takes more time for calculation, whereas a coarser mesh yields less accurate values, but in a shorter time. * Another assumption that enables us to work with a two-dimensional plate model is the plain strain condition. That means that deformations only occur in one plane. diff --git a/Tests/Data/Notebooks/testrunner.py b/Tests/Data/Notebooks/testrunner.py index caa9c61df08..27710943c0b 100644 --- a/Tests/Data/Notebooks/testrunner.py +++ b/Tests/Data/Notebooks/testrunner.py @@ -19,7 +19,22 @@ def save_to_website(exec_notebook_file, web_path): output_path = "docs/benchmarks" notebook = nbformat.read(exec_notebook_file, as_version=4) first_cell = notebook.cells[0] - if first_cell.cell_type == "raw": + if is_jupytext: + if "Tests/Data" not in exec_notebook_file: + output_path = str(Path(exec_notebook_file).parent.parent) + else: + lines = first_cell.source.splitlines() + toml_begin = lines.index("+++") + toml_end = max(loc for loc, val in enumerate(lines) if val == "+++") + toml_lines = lines[toml_begin + 1 : toml_end] + parsed_frontmatter = toml.loads("\n".join(toml_lines)) + output_path = ( + Path(build_dir) + / Path("web/content") + / Path(output_path) + / Path(parsed_frontmatter["web_subsection"]) + ) + elif first_cell.cell_type == "raw": lines = first_cell.source.splitlines() last_line = lines[-1] if "" not in last_line: @@ -34,6 +49,7 @@ def save_to_website(exec_notebook_file, web_path): "'web_subsection'!" ) output_path = os.path.join(output_path, parsed_frontmatter["web_subsection"]) + output_path = Path(build_dir) / (Path("web/content") / Path(output_path)) else: print( f"Warning: {exec_notebook_file} does not contain a RAW cell as its first " @@ -85,11 +101,14 @@ def save_to_website(exec_notebook_file, web_path): if "OGS_DATA_DIR" not in os.environ: os.environ["OGS_DATA_DIR"] = os.path.join(ogs_source_path, "Tests/Data") os.makedirs(args.out, exist_ok=True) +build_dir = Path(args.out).parent.parent success = True for notebook_file_path in args.notebooks: notebook_success = True - convert_notebook_file = notebook_file_path + is_jupytext = False + if Path(notebook_file_path).suffix in [".md", ".py"]: + is_jupytext = True notebook_file_path_relative = ( Path(notebook_file_path).absolute().relative_to(ogs_source_path) ) @@ -105,13 +124,17 @@ def save_to_website(exec_notebook_file, web_path): os.makedirs(notebook_output_path, exist_ok=True) os.environ["OGS_TESTRUNNER_OUT_DIR"] = notebook_output_path notebook_filename = os.path.basename(notebook_file_path) - convert_notebook_file = os.path.join( - notebook_output_path, Path(notebook_filename).stem - ) + convert_notebook_file = notebook_output_path + if not is_jupytext: + convert_notebook_file = os.path.join( + convert_notebook_file, Path(notebook_filename).stem + ) convert_notebook_file += ".ipynb" - if Path(notebook_file_path).suffix == ".md": + if is_jupytext: nb = jupytext.read(notebook_file_path) + convert_notebook_file = convert_notebook_file.replace("notebook-", "") + jupytext.write(nb, convert_notebook_file) else: with open(notebook_file_path, mode="r", encoding="utf-8") as f: nb = nbformat.read(f, as_version=4) @@ -154,6 +177,21 @@ def save_to_website(exec_notebook_file, web_path): if "CI_MERGE_REQUEST_SOURCE_PROJECT_URL" in os.environ: repo = os.environ["CI_MERGE_REQUEST_SOURCE_PROJECT_URL"] branch = os.environ["CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"] + + # Modify metadata + meta_cell = nb["cells"][0] + if is_jupytext: + if meta_cell.source.startswith("---"): + print( + f"Error: {notebook_filename} frontmatter is not in TOML format! Use +++ delimitiers!" + ) + success = False + meta_cell.source = meta_cell.source.replace( + "+++\n", "+++\nnotebook = true\n", 1 + ) + else: + meta_cell.source = f"notebook = true\n{meta_cell.source}" + # Insert Jupyter header with notebook source and binderhub link binder_link = f"https://mybinder.org/v2/gh/bilke/binder-ogs-requirements/master?urlpath=git-pull%3Frepo={repo}%26urlpath=lab/tree/ogs/{notebook_file_path_relative}%26branch={branch}" text = f""" @@ -161,20 +199,41 @@ def save_to_website(exec_notebook_file, web_path):- This page is based on a Jupyter notebook. - - - - -
- - """ - meta_cell = nb["cells"][0] - if meta_cell.cell_type == "raw": - meta_cell.source = f"notebook = true\n{meta_cell.source}" - nb["cells"].insert(1, nbformat.v4.new_markdown_cell(text)) + This page is based on a Jupyter notebook.""" + if is_jupytext: + download_file_name = ( + Path(convert_notebook_file) + .rename(Path(convert_notebook_file).with_suffix(".ipynb")) + .name + ) + text += f""" +""" + text += f""" + + + +""" + text += f"""\n\n""" + + for cell in nb["cells"]: + # Check frontmatter has its own cell + if ( + cell.cell_type == "markdown" + and cell.source.startswith("+++") + and not cell.source.endswith("+++") + ): + print( + f"Error: {notebook_filename} notebook metadata is not a separate cell (in markdown: separate by two newlines)!" + ) + success = False + # Get first regular markdown cell + if cell.cell_type == "markdown" and not cell.source.startswith("+++"): + first_markdown_cell = cell + break + + first_markdown_cell.source = text + first_markdown_cell.source nbformat.write(nb, f) # 5. Symlink images or figures subfolder diff --git a/Tests/Data/Parabolic/LiquidFlow/BlockingConductingFracture/LiquidFlow.ipynb b/Tests/Data/Parabolic/LiquidFlow/BlockingConductingFracture/BlockingConductingFracture.ipynb similarity index 100% rename from Tests/Data/Parabolic/LiquidFlow/BlockingConductingFracture/LiquidFlow.ipynb rename to Tests/Data/Parabolic/LiquidFlow/BlockingConductingFracture/BlockingConductingFracture.ipynb diff --git a/scripts/ci/helper/run-vale b/scripts/ci/helper/run-vale deleted file mode 100755 index 061ad491d5e..00000000000 --- a/scripts/ci/helper/run-vale +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash - -# From https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/scripts/lint-docs - -set -o pipefail -GIT_ROOT=$(cd "${BASH_SOURCE%/*}" && git rev-parse --show-toplevel) -cd ${GIT_ROOT} -VALE_MIN_ALERT_LEVEL=${VALE_MIN_ALERT_LEVEL:-} -VALE_OUTPUT=${VALE_OUTPUT:-} -ERROR_RESULTS=0 -if command -v vale >/dev/null 2>&1; then - args=() - if [ -n "${VALE_MIN_ALERT_LEVEL}" ]; then - args+=("--minAlertLevel" "${VALE_MIN_ALERT_LEVEL}") - fi - if [ "${VALE_OUTPUT}" == "line" ]; then - args+=("--output" "line" "--sort" "--normalize") - fi - if [ "${VALE_OUTPUT}" == "json" ]; then - args+=("--output" "./Documentation/.vale/vale-json.tmpl") - else - echo "Lint prose" - fi - - # TODO: "Documentation" - vale "${args[@]}" web/content || ((ERROR_RESULTS++)) -else - echo "vale is missing, please install it from https://errata-ai.gitbook.io/vale/#installation" -fi -if [ "${VALE_OUTPUT}" == "json" ]; then - exit 0 -fi - -if [ "${ERROR_RESULTS}" -ne 0 ]; then - echo "✖ ${ERROR_RESULTS} Vale spell check failed. Review the log carefully to see full listing." - exit 1 -else - echo "✔ Vale spell check passed" - exit 0 -fi diff --git a/scripts/ci/jobs/build-docs.yml b/scripts/ci/jobs/build-docs.yml index 9ce2be9f4d6..05b56e3a887 100644 --- a/scripts/ci/jobs/build-docs.yml +++ b/scripts/ci/jobs/build-docs.yml @@ -102,10 +102,8 @@ spell check docs: image: registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.18-vale-2.27.0-markdownlint-0.35.0-markdownlint2-0.8.1 stage: check needs: [] - variables: - VALE_OUTPUT: json script: - - scripts/ci/helper/run-vale > vale-report.json + - vale web/content --output ./Documentation/.vale/vale-json.tmpl > vale-report.json artifacts: paths: - vale-report.json diff --git a/scripts/cmake/test/NotebookTest.cmake b/scripts/cmake/test/NotebookTest.cmake index d42c2c58bf9..f5ea8c6af15 100644 --- a/scripts/cmake/test/NotebookTest.cmake +++ b/scripts/cmake/test/NotebookTest.cmake @@ -7,7 +7,7 @@ function(NotebookTest) set(options DISABLED SKIP_WEB) set(oneValueArgs NOTEBOOKFILE RUNTIME) - set(multiValueArgs WRAPPER RESOURCE_LOCK PROPERTIES LABELS) + set(multiValueArgs WRAPPER RESOURCE_LOCK PROPERTIES LABELS PYTHON_PACKAGES) cmake_parse_arguments( NotebookTest "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) @@ -63,6 +63,26 @@ function(NotebookTest) endif() list(APPEND _exe_args ${NotebookTest_SOURCE_DIR}/${NotebookTest_NAME}) + if(NotebookTest_PYTHON_PACKAGES) + list(APPEND labels python_modules) + if(OGS_USE_PIP) + # Info has to be passed by global property because it is not + # possible to set cache variables from inside a function. + set_property( + GLOBAL APPEND PROPERTY AddTest_PYTHON_PACKAGES + ${NotebookTest_PYTHON_PACKAGES} + ) + else() + message( + STATUS + "Warning: Benchmark ${NotebookTest_NAME} requires these " + "Python packages: ${NotebookTest_PYTHON_PACKAGES}!\n Make sure to " + "have them installed in your current Python environment OR " + "set OGS_USE_PIP=ON!" + ) + endif() + endif() + add_test( NAME ${TEST_NAME} COMMAND @@ -84,9 +104,14 @@ function(NotebookTest) list(APPEND labels default) endif() - list(APPEND _props ENVIRONMENT_MODIFICATION - PATH=path_list_prepend:$