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:$ - ${NotebookTest_PROPERTIES} + list( + APPEND + _props + ENVIRONMENT_MODIFICATION + PATH=path_list_prepend:$ + ${NotebookTest_PROPERTIES} + ENVIRONMENT + CI=1 ) set_tests_properties( diff --git a/scripts/cmake/test/OgsTestWrapper.cmake b/scripts/cmake/test/OgsTestWrapper.cmake index 4acf05398e7..d27694faf75 100644 --- a/scripts/cmake/test/OgsTestWrapper.cmake +++ b/scripts/cmake/test/OgsTestWrapper.cmake @@ -7,7 +7,7 @@ execute_process( WORKING_DIRECTORY ${WORKING_DIRECTORY} RESULT_VARIABLE EXIT_CODE OUTPUT_VARIABLE LOG - ERROR_VARIABLE LOG + ERROR_VARIABLE LOG COMMAND_ECHO STDOUT ) if(EXIT_CODE STREQUAL "0") diff --git a/scripts/docker/Dockerfile.pre-commit b/scripts/docker/Dockerfile.pre-commit index 8c495e6642e..daee12d7953 100644 --- a/scripts/docker/Dockerfile.pre-commit +++ b/scripts/docker/Dockerfile.pre-commit @@ -4,8 +4,8 @@ ENV TZ=Europe/Berlin RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN apt-get update \ - && apt-get install -y --no-install-recommends clang-format git gcc g++ python3-pip wget xz-utils \ + && apt-get install -y --no-install-recommends clang-format git gcc g++ golang python3-pip wget xz-utils \ && rm -rf /var/lib/apt/lists/* -RUN pip install pre-commit==2.20.0 +RUN pip install pre-commit==3.4.0 RUN ln -s /usr/bin/python3 /usr/bin/python diff --git a/web/config/_default/config.toml b/web/config/_default/config.toml index f57428dd7cf..4fc71c6df11 100644 --- a/web/config/_default/config.toml +++ b/web/config/_default/config.toml @@ -4,7 +4,24 @@ theme = "ogs" buildDrafts = false buildFuture = true enableGitInfo = true -staticDir = ["dist", "static"] + +[module] +[[module.mounts]] +excludeFiles = '**/notebook-*.md' +source = 'content' +target = 'content' +[[module.mounts]] +source = 'static' +target = 'static' +[[module.mounts]] +source = 'dist' +target = 'static' +[[module.mounts]] +source = '../../build/release/web/content' +target = 'content' +[[module.mounts]] +source = '../../build/release-petsc/web/content' +target = 'content' [markup.goldmark.renderer] unsafe = true diff --git a/web/content/docs/devguide/documentation/jupyter-docs/index.md b/web/content/docs/devguide/documentation/jupyter-docs/index.md index bd94e384ed5..15c3c23bf4a 100644 --- a/web/content/docs/devguide/documentation/jupyter-docs/index.md +++ b/web/content/docs/devguide/documentation/jupyter-docs/index.md @@ -76,45 +76,32 @@ These notebooks are part of the regular CI testing. Please try to keep the noteb ### Create a new notebook -Create a new notebook file in `Tests/Data` (either as regular `.ipynb`-files or as Markdown-based notebooks via [Jupytext](https://jupytext.readthedocs.io/en/latest)). See examples: +Create a new notebook file in `Tests/Data` (if it should appear in the benchmark gallery) or in `web/content/docs` (e.g. for tutorials). Create it as a regular Markdown-file with Python code blocks. The notebook execution and conversion is done via [Jupytext](https://jupytext.readthedocs.io/en/latest). See examples: -- [SimpleMechanics.ipynb](https://gitlab.opengeosys.org/ogs/ogs/-/blob/master/Tests/Data/Mechanics/Linear/SimpleMechanics.ipynb) (regular `.ipynb`-notebook) -- [Linear_Disc_with_hole.md](https://gitlab.opengeosys.org/ogs/ogs/-/blob/master/Tests/Data/Mechanics/Linear/DiscWithHole/Linear_Disc_with_hole.md) (Jupytext-based notebook in Markdown) +- [Linear_Disc_with_hole.md](https://gitlab.opengeosys.org/ogs/ogs/-/blob/master/Tests/Data/Mechanics/Linear/DiscWithHole/Linear_Disc_with_hole.md) (Jupytext-based benchmark notebook in Markdown) +- [notebook-bhe_meshing.md](https://gitlab.opengeosys.org/ogs/ogs/-/blob/master/web/content/docs/tutorials/bhe_meshing/notebook-bhe_meshing.md) (Jupytext-based tutorial notebook in Markdown) +- [SimpleMechanics.ipynb](https://gitlab.opengeosys.org/ogs/ogs/-/blob/master/Tests/Data/Mechanics/Linear/SimpleMechanics.ipynb) (regular `.ipynb`-notebook are also possible but Markdown-based notebooks are preferred) ### Add web meta information If the notebook result should appear as a page on the web documentation a frontmatter with some meta information (similar to [regular web pages]({{< ref "web-docs.md" >}})) is required as the first cell in the notebook: -- Add a new cell and move it to the first position in the notebook -- Change the cell type to `raw`! -- Add meta information, conclude with a end-of-file marker (``) e.g.: - - ```md - title = "SimplePETSc" - date = "2021-11-09" - author = "Lars Bilke" - image = "optional_gallery_image.png" - web_subsection = "small-deformations" - - ``` - -`web_subsection` needs to be set to a sub-folder in [web/content/docs/benchmarks](https://gitlab.opengeosys.org/ogs/ogs/-/tree/master/web/content/docs/benchmarks) (if not set the notebook page will not be linked from navigation bar / benchmark gallery on the web page). - -
- -In Jupytext-based notebooks you can add the frontmatter within the ``- and ``-markers: - -```md - -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" - - +```markdown ++++ +title = "SimplePETSc" +date = "2021-11-09" +author = "Lars Bilke" +image = "optional_gallery_image.png" +web_subsection = "small-deformations" # required for notebooks in Tests/Data only ++++ + <-- Add Two newlines here to separate --> + <-- the frontmatter as its own cell --> ``` -
+- Frontmatter needs to be in [TOML](https://toml.io)-format. +- For notebooks describing benchmarks `web_subsection` needs to be set to a sub-folder in [web/content/docs/benchmarks](https://gitlab.opengeosys.org/ogs/ogs/-/tree/master/web/content/docs/benchmarks) (if not set the notebook page will not be linked from navigation bar / benchmark gallery on the web page). +- If you edit a Markdown-based notebook with Jupyter and the Jupytext extension please don't add the two newlines but make sure that the frontmatter has its own cell (not mixed with markdown content). +- For (deprecated) `.ipynb`-based notebooks the frontmatter has to given as a `raw`-cell containing a special ``-marker. See existing notebooks (e.g. [SimpleMechanics.ipynb](https://gitlab.opengeosys.org/ogs/ogs/-/blob/master/Tests/Data/Mechanics/Linear/SimpleMechanics.ipynb)) for reference. ### Notebook setup @@ -178,9 +165,18 @@ Add the notebook to CTest ([example](https://gitlab.opengeosys.org/ogs/ogs/-/blo ```cmake if(NOT OGS_USE_PETSC) NotebookTest(NOTEBOOKFILE Mechanics/Linear/SimpleMechanics.ipynb RUNTIME 10) + + # Notebooks in web/content need to be prefixed with 'notebook-'! + NotebookTest(NOTEBOOKFILE ../../web/content/docs/tutorials/bhe_meshing/notebook-bhe_meshing.md + PYTHON_PACKAGES openpyxl + RUNTIME 10) endif() ``` +- `NOTEBOOKFILE` is relative to `Tests/Data`. +- If your notebook requires additional dependencies add them with `PYTHON_PACKAGES`. +- If the notebook is in `web/content` it is important to prefix the notebook file name with `notebook-`! The prefix is required to indicate Hugo that this is a notebook and not a regular markdown page. +
If your notebook should **not** appear on the website add the `SKIP_WEB`-option to `NotebookTest()`. This may be useful if the notebook serves as CI test only, e.g. comparing multiple simulation runs or doing performance measurements. But please also note that there will be no artifact produced (except for notebook errors which get reported as usual). diff --git a/web/content/docs/tutorials/bhe_meshing/1bhe1d.png b/web/content/docs/tutorials/bhe_meshing/1bhe1d.png new file mode 100644 index 00000000000..04d975b1d13 Binary files /dev/null and b/web/content/docs/tutorials/bhe_meshing/1bhe1d.png differ diff --git a/web/content/docs/tutorials/bhe_meshing/BHEpoints.png b/web/content/docs/tutorials/bhe_meshing/BHEpoints.png new file mode 100644 index 00000000000..8958e9b2469 Binary files /dev/null and b/web/content/docs/tutorials/bhe_meshing/BHEpoints.png differ diff --git a/web/content/docs/tutorials/bhe_meshing/bhe3d1.png b/web/content/docs/tutorials/bhe_meshing/bhe3d1.png new file mode 100644 index 00000000000..cb77df3d9e8 Binary files /dev/null and b/web/content/docs/tutorials/bhe_meshing/bhe3d1.png differ diff --git a/web/content/docs/tutorials/bhe_meshing/bhe_coordinates.xlsx b/web/content/docs/tutorials/bhe_meshing/bhe_coordinates.xlsx new file mode 100644 index 00000000000..b2d5f478ac0 Binary files /dev/null and b/web/content/docs/tutorials/bhe_meshing/bhe_coordinates.xlsx differ diff --git a/web/content/docs/tutorials/bhe_meshing/notebook-bhe_meshing.md b/web/content/docs/tutorials/bhe_meshing/notebook-bhe_meshing.md new file mode 100644 index 00000000000..be08ba79ddd --- /dev/null +++ b/web/content/docs/tutorials/bhe_meshing/notebook-bhe_meshing.md @@ -0,0 +1,289 @@ ++++ +title = "Creating Mesh with integrated Borehole Heat Exchangers" +date = "2023-10-03" +author = "Joy Brato Shil, Haibing Shao" ++++ + + +This tutorial is made to illustrate the procedure of creating an OGS mesh file with Borehole Heat Exchangers (BHEs) in it. +Such mesh uses prism elements for the soil part, and line elements for the BHEs. +The produced mesh file is made explicitly for the `HEAT_TRANSPORT_BHE` module in OGS and will NOT work with other modules. +Please refer to the [documentation](https://www.opengeosys.org/docs/processes/heat-transport/heat_transport_bhe/) of the HEAT_TRANSPORT_BHE process for more details of the project file configuration. +For a better understanding of the mesh needed for this process, the following snapshot illustrates a 3D model domain with several BHEs in it. + +![1bhe1d.png](./1bhe1d.png) + +This tutorial contains two files, + +- bhe_meshing.ipynb: This tutorial itself. +- [bhe_coordinates.xlsx](./bhe_coordinates.xlsx) :The spreadsheet file containing parameters of the BHEs. + +First, we import external packages, including Gmsh. + +```python +%reset -f +import numpy as np +import pandas as pd +import gmsh +import sys +import os +``` + +Before using any functions in the Python API, Gmsh must be initialized. + +```python +gmsh.initialize() +gmsh.model.add("t1") +``` + +The geometry is a 3D structure that has 25 boreholes in it. Each borehole is represented by a line element where each line is created by adding two end points. Each point requires an optional argument called 'tag'. +It is a strictly positive integer that uniquely identifies the point. +In this tutorial, we will use tags for all the borehole points so that we can identify any specific point. +The first step is to create the surface 1 with all the necessary points. +These points regulate the borehole locations, as well as the mesh size. +Now we define the basic geometry of the BHEs, as well as the element sizes around them. +For better optimal accuracy and better spatial discretizations, the mesh around the borehole point is designed according to relation (`delta = alpha * bhe_radius`) derived from Diersch et al. 2011 Part 2 (DOI: [10.1016/j.cageo.2010.08.002](https://doi.org/10.1016/j.cageo.2010.08.002)) where alpha = 6.134 for 6 surrounding nodes. + +```python +#environment variable for output path +out_dir = os.environ.get("OGS_TESTRUNNER_OUT_DIR", "_out") +if not os.path.exists(out_dir): + os.makedirs(out_dir) + +exe_dir = os.environ.get("OGS_BINARY_DIR") + +# mesh file name +bhe_mesh_file_name = "bhe_mesh_file" + +#geometry parameters +width = 200 +length = 200 +depth = 120 +bhe_depth = depth - 20 + + +#element sizes +bhe_radius = 0.07 +alpha = 6.134 # see Diersch et al. 2011 Part 2 +delta = alpha * bhe_radius # meshsize at BHE and distance of the surrounding optimal mesh points + +elem_size_corner = 40 +elem_size_BHE_relax = 2 +``` + +In the following step, we are going to create the geometry of the bottom surface using the python interface of Gmsh. + +To create a point with the built-in CAD kernel, the Python API function `gmsh.model.geo.addPoint()` is used. + +- the first 3 arguments are the point coordinates (x, y, z) +- the next (optional) argument is the target mesh size close to the point +- the last (optional) argument is the point tag (a strictly positive integer that uniquely identifies the point) + +Here, we have assigned 4 boundary points. + +```python +gmsh.model.geo.addPoint(-width/2.0, 0.0, 0.0, elem_size_corner, 1) +gmsh.model.geo.addPoint(width/2.0, 0.0, 0.0, elem_size_corner, 2) +gmsh.model.geo.addPoint(width/2.0, length, 0.0, elem_size_corner, 3) +gmsh.model.geo.addPoint(-width/2.0, length, 0.0, elem_size_corner, 4) +``` + +Next, we need to create lines connecting the boundary points. +The API to create straight-line segments with the built-in kernel follows the same conventions: +the first 2 arguments are point tags (the start and end points of the line), and the last (optional) is the line tag. +Note that curve tags are separate from point tags. Hence we can reuse tag '1' for our first curve. +And as a general rule, elementary entity tags in Gmsh have to be unique per geometrical dimension. + +```python +gmsh.model.geo.addLine(1, 2, 1) +gmsh.model.geo.addLine(2, 3, 2) +gmsh.model.geo.addLine(3, 4, 3) +gmsh.model.geo.addLine(4, 1, 4) +``` + +Finally, we can create the surface 1. +In order to define the surface using the four lines defined above, a curve loop must first be defined. +An ordered list of connected curves defines a curve loop. +The API function to create curve loops takes a list of integers as the first argument, and the curve loop tag (which must be unique amongst curve loops) as the second (optional) argument. + +We can then define the surface as a list of curve loops. +Before the structure can be meshed, the CAD entities must be synchronized with the Gmsh model, which will create the relevant Gmsh data structures. +This is achieved by the API call `gmsh.model.geo.synchronize()`. Synchronizations can be called at any time. + +```python +gmsh.model.geo.addCurveLoop([1, 2, 3, 4], 1) + +gmsh.model.geo.addPlaneSurface([-1], 1) +gmsh.model.geo.synchronize() +``` + +Next step is to include 25 boreholes in this project, organized in a 5X5 array. +The corresponding coordinates are imported from a Excel spreadsheet file. + +```python +df_bhe = pd.read_excel('bhe_coordinates.xlsx') +df_relax = pd.read_excel('bhe_coordinates.xlsx', sheet_name=1) +``` + +Finally, all the borehole points and surrounding points are added to the surface 1. +Here, 6 surrounding points for each borehole point are added with hexagonal shape like in this picture. + +![BHEpoints.png](./BHEpoints.png) + +A detailed explanation and model verification can be found in Diersch et al. (2011). +Here, the initial borehole point tag can not be smaller than 4 since there are already four boundary points. + +```python +x=df_bhe['X'] # X-coordinates +y=df_bhe['Y'] # Y-coordinates +z=df_bhe['Z'] # Z-coordinates +d=5 # initial borehole point tag + +j=[] +for i in range(len(x)): + X=x[i] + Y=y[i] + Z=z[i] + + gmsh.model.geo.addPoint(X, Y, Z, delta, d) # Diersch et al. 2011 Part 2 + + gmsh.model.geo.addPoint(X, Y - delta, Z, delta, d+1) + gmsh.model.geo.addPoint(X, Y + delta, Z, delta, d+2) + + gmsh.model.geo.addPoint(X+0.866*delta, Y + 0.5*delta, Z, delta, d+3) + gmsh.model.geo.addPoint(X-0.866*delta, Y + 0.5*delta, Z, delta, d+4) + + gmsh.model.geo.addPoint(X+0.866*delta, Y - 0.5*delta, Z, delta, d+5) + gmsh.model.geo.addPoint(X-0.866*delta, Y - 0.5*delta, Z, delta, d+6) + + j.append(d) + gmsh.model.geo.synchronize() + gmsh.model.mesh.embed(0,[d, d+1, d+2, d+3, d+4, d+5, d+6],2,1) + + d = d+7 +``` + +In this step, all mesh element size relaxing points have been added and embedded in surface 1. + +```python +x=df_relax['X'] # X-coordinates +y=df_relax['Y'] # Y-coordinates +z=df_relax['Z'] # Z-coordinates + +for i in range(len(x)): + X=x[i] + Y=y[i] + Z=z[i] + + gmsh.model.geo.addPoint(X, Y, Z, elem_size_BHE_relax, d) + gmsh.model.geo.synchronize() + gmsh.model.mesh.embed(0,[d],2,1) + d = d+1 +``` + +Now, the `gmsh.model.geo.extrude` command extrudes the surface 1 along the z axis and automatically creates a new volume (as well as all the needed points, curves and surfaces). The function takes a vector of (dim, tag) pairs as input as well as the translation vector, and returns a vector of (dim, tag) pairs as output. + +The 2D mesh extrusion is done with the same `extrude()` function, but by specifying element `Layers` (Here, one layer each with 12 subdivisions with a total height of 120). The number of elements for each layer and the (end) height of each layer are specified in two vectors. +The last (optional) argument for the `extrude()` function specifies whether the extruded mesh should be recombined or not. +In this case, it is `True` since we want to recombine and produce prism mesh elements. + +Later `gmsh.model.addPhysicalGroup` command used to group elementary geometrical entities into more meaningful groups, e.g. to define some mathematical ("domain", "boundary"), functional ("left wing", "fuselage") or material ("steel", "carbon") properties. Gmsh will export in output files only mesh elements that belong to at least one physical group. Physical groups are also identified by tags, i.e. strictly positive integers, that should be unique per dimension (0D, 1D, 2D or 3D). + +```python +gmsh.model.geo.synchronize() +R = gmsh.model.geo.extrude([(2, 1)], 0, 0, -depth, [12], [1], True) +gmsh.model.addPhysicalGroup(3, [R[1][1]], 1) + +``` + +In this step, all BHE points will be extruded up to `bhe_depth` and each BHE line will be assigned unique physical group. + +```python +k= 2 +for u in j: + G = gmsh.model.geo.extrude([(0, u)], 0, 0, -bhe_depth, [10], [1], True) + gmsh.model.addPhysicalGroup(1, [G[1][1]], k) + k = k + 1 +gmsh.model.geo.synchronize() +``` + +Meshes generated with Gmsh must be converted to VTU file format later. Currently, the only supported Gmsh format is 2.2 + +```python +gmsh.option.setNumber("Mesh.MshFileVersion", 2.2) +``` + +We can then generate a 3D mesh and save it to disk + +```python +gmsh.model.mesh.generate(3) + +gmsh.model.mesh.removeDuplicateNodes() + +gmsh.write(f"{out_dir}/{bhe_mesh_file_name}.msh") +``` + +Launch the GUI to see the results. Later `gmsh.finalize()` will be called when done using Gmsh Python API + +```python +if "CI" not in os.environ: + gmsh.fltk.run() + + +gmsh.finalize() +``` + +If everything runs well, you will see the following mesh. + +![bhe3d1.png](./bhe3d1.png) + +Now checking whether the Gmsh format mesh file is properly created. If not give an error message. + +```python +check_file = os.path.isfile(f"{out_dir}/{bhe_mesh_file_name}.msh") +if check_file: + print("Creation of BHE mesh in Gmsh format was successful.") +else: + raise Exception("Error! Gmsh file is not properly created in the BHE meshing tutorial.") +``` + +In this step, we will investigate the number of nodes, lines, prisms, and Material ID of the created mesh file, to validate if it is created properly for simulation. + +```python +gmsh.initialize() +gmsh.open(f"{out_dir}/{bhe_mesh_file_name}.msh") +nodeTags, coord, parametricCoord = gmsh.model.mesh.getNodes(-1,-1) +elemTypes, line_elemTags, elemNodeTags =gmsh.model.mesh.getElements(1,-1) +elemTypes, volume_elemTags, elemNodeTags =gmsh.model.mesh.getElements(3,-1) +dimTags = gmsh.model.getPhysicalGroups(1) +Material_ID = len(dimTags) +gmsh.finalize() + +print("Total Nodes", np.size(nodeTags)) +print("Nr of lines", np.size(line_elemTags)) +print("Nr. of prisms", np.size(volume_elemTags)) +print("Material ID", Material_ID) + +#if not ((np.size(nodeTags) == 119886) and (np.size(line_elemTags) == 250) and (np.size(volume_elemTags)==221064) and (Material_ID == 25)): +# raise Exception("Error! The number of nodes, elements and Material_IDs in the generated Gmsh file is not correct.") +``` + +Finally, the mesh file which has been created using the Python interface of Gmsh, will be converted to OGS mesh, in particular to VTU file format. +Please, add the executable `GMSH2OGS` to the directory of this example file, or add the path to the OGS binary folder into the running environment. +Here, option `-v` (`--validation`) validates the mesh and shows crucial information about the mesh. +Option `-i` takes Gmsh input file name as a string and `-o` is the output file name as a string as well + +```python +!GMSH2OGS -i {out_dir}/{bhe_mesh_file_name}.msh -o {out_dir}/{bhe_mesh_file_name}.vtu -v +``` + +The above conversion tool also shows that there exist 250 lines and 221064 prism elements. +An assertion check is added here to make sure that the VTU format mesh file is properly created. + +```python +check_file = os.path.isfile(f"{out_dir}/{bhe_mesh_file_name}.vtu") +if check_file: + print("Conversion of mesh file from Gmsh to VTU format was successful.") +else: + raise Exception("Error! Gmsh file is not properly converted to VTU format in the BHE meshing tutorial.") +```