diff --git a/README.md b/README.md index ae35f80..f4203c7 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,112 @@ # GCAT: grid convergence analysis toolkit +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) +[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/) [![black](https://github.com/gabrielbdsantos/gcat/actions/workflows/black.yaml/badge.svg?branch=master&event=push)](https://github.com/gabrielbdsantos/gcat/actions/workflows/black.yaml) The implementation is completed. Thus, do not expect updates to the repository anymore. -## References +## Installation + -1. I. B. Celik, U. Ghia, P. J. Roache, C. J. Freitas, H. Coleman, and P. E. - Raad, Procedure for Estimation and Reporting of Uncertainty Due to - Discretization in CFD Applications,” J. Fluids Eng., vol. 130, no. 7, p. - 078001, Jul. 2008, doi: [10.1115/1.2960953][1]. +### Poetry (recommended) -2. P. J. Roache, Quantification of Uncertainty in Computational Fluid Dynamics, - Annu. Rev. Fluid Mech., vol. 29, no. 1, pp. 123–160, Jan. 1997, doi: - [10.1146/annurev.fluid.29.1.123][2]. +Clone the repository + + git clone https://github.com/gabrielbdsantos/gcat + cd gcat + +Create a virtual environment (optional) + + poetry env use python3 + +Install GCAT + + poetry install --with cli + +Activate it + + source $(poetry env info --path)/bin/activate + + +### Pip + +Clone the repository + + git clone https://github.com/gabrielbdsantos/gcat + cd gcat + +Create a virtual environment (optional) + + python3 -m venv .venv --clear + +Install + + pip install -r requirements.txt -e . + +Activate it + + source .venv/bin/activate + + +## Quick start + +1. Check the refinement ratios and representative grid sizes. + + $ gcat check --n1 1000 --n2 2000 --n3 3000 --area 0.5 + # Grid summary + + ------------ + N1 = 2900 elements + N2 = 1700 elements + N3 = 1000 elements + Area = 0.2 m^2 + + # Representative grid size + + ------------------------ + h1 = 8.304548 mm + h2 = 10.846523 mm + h3 = 14.142136 mm + + # Refinement ratio + + ---------------- + r21 = 1.306094 + r32 = 1.303840 + +2. Compute the grid convergence index + + $ gcat gci --h1 8.30 --h2 10.84 --h3 14.14 --f1 1 --f2 1.02 --f3 1.08 + # Grid summary + + --------------------------------------- + h1 = 8.300000e+00 m, f1 = 1.000000e+00 + h2 = 1.084000e+01 m, f2 = 1.020000e+00 + h3 = 1.414000e+01 m, f3 = 1.080000e+00 + + # GCI (safety factor = 1.25) + + --------------------------------------- + GCI21_fine = 1.562500e-02 + GCI21_coarse = 4.687500e-02 + + GCI32_fine = 4.630449e-02 + GCI32_coarse = 1.382163e-01 + + Asymptotic ratio = 1.012321 + + +## References -3. Examining Spatial (Grid) Convergence. - https://www.grc.nasa.gov/WWW/wind/valid/tutorial/spatconv.html (accessed - Oct. 22, 2020). +1 . I. B. Celik, U. Ghia, P. J. Roache, C. J. Freitas, H. Coleman, and P. E. + Raad, Procedure for Estimation and Reporting of Uncertainty Due to + Discretization in CFD Applications,” J. Fluids Eng., vol. 130, no. 7, p. + 078001, Jul. 2008, doi: [10.1115/1.2960953][1]. + +2 . P. J. Roache, Quantification of Uncertainty in Computational Fluid Dynamics, + Annu. Rev. Fluid Mech., vol. 29, no. 1, pp. 123–160, Jan. 1997, doi: + [10.1146/annurev.fluid.29.1.123][2]. + +3 . Examining Spatial (Grid) Convergence. + https://www.grc.nasa.gov/WWW/wind/valid/tutorial/spatconv.html (accessed + Oct. 22, 2020). ## License diff --git a/gcat/__init__.py b/gcat/__init__.py index 2f8283d..4c395fa 100644 --- a/gcat/__init__.py +++ b/gcat/__init__.py @@ -2,7 +2,7 @@ # coding=utf-8 """Grid Convergence Analysis Toolkit (GCAT).""" -__version__ = "1.0.0" +__version__ = "1.1.0" from .convergence import ( apparent_order_of_convergence, diff --git a/gcat/__main__.py b/gcat/__main__.py new file mode 100644 index 0000000..8a5bf64 --- /dev/null +++ b/gcat/__main__.py @@ -0,0 +1,6 @@ +# coding: utf-8 + +from .cli import app + +if __name__ == "__main__": + app() diff --git a/gcat/cli.py b/gcat/cli.py new file mode 100644 index 0000000..698ccd6 --- /dev/null +++ b/gcat/cli.py @@ -0,0 +1,206 @@ +# coding: utf-8 +"""Provide utilities for the command line interface.""" + +import math + +import typer + +from gcat.convergence import asymptotic_ratio + +app = typer.Typer( + help="A simple cli for the grid convergence analysis toolkit (GCAT)", + add_completion=False, + no_args_is_help=True, +) + + +def MutuallyExclusiveGroup(size: int = 2, at_least_one: bool = True): + """Create a mutually exclusive callback for typer.""" + group = set() + active = set() + + def callback(_: typer.Context, param: typer.CallbackParam, value: str): + group.add(param.name) + + if ( + value != param.default + and value is not None + and param.name not in active + ): + active.add(param.name) + + if len(active) > 1: + raise typer.BadParameter( + f"{param.name} is mutually exclusive with {active.pop()}" + ) + + if at_least_one and len(group) == size and len(active) == 0: + typer.echo( + f"Error: Expected one of the options: {[x for x in group]}" + ) + raise typer.Exit(2) + + return value + + return callback + + +exclusivity_callback = MutuallyExclusiveGroup() + + +@app.command() +def check( + n1: int = typer.Option( + ..., + help="Number of elements of the fine grid.", + metavar="", + ), + n2: int = typer.Option( + ..., + help="Number of elements of the medium grid.", + metavar="", + ), + n3: int = typer.Option( + ..., + help="Number of elements of the coarse grid.", + metavar="", + ), + area: float = typer.Option( + default=0.0, + help="Area of the computational domain (in squared meters).", + show_default=False, + metavar="", + callback=exclusivity_callback, + ), + volume: float = typer.Option( + default=0.0, + help="Volume of the computational domain (in cubic meters).", + show_default=False, + metavar="", + callback=exclusivity_callback, + ), +) -> None: + """Check the representative size and refinement ratios.""" + # Note that area and volume are mutually exclusive. Thus, if volume is + # zero, it is a two-dimensional case. Otherwise, it will be a + # three-dimensional one. + num_dimensions: int = 2 + 1 * (volume > 0.0) + + total_size = volume + area + + def representative_size( + num_elements: int, total_size: float, num_dimensions: int + ) -> float: + """Compute the representative size of a given mesh.""" + return math.pow((total_size / num_elements), (1 / num_dimensions)) + + # Compute the individual representative sizes + h1 = representative_size(n1, total_size, num_dimensions) # in meters + h2 = representative_size(n2, total_size, num_dimensions) # in meters + h3 = representative_size(n3, total_size, num_dimensions) # in meters + + # Compute the refinement ratio + ratio21 = h2 / h1 + ratio32 = h3 / h2 + + log = ( + "# Grid summary", + "+ ------------", + f" N1 = {n1} elements", + f" N2 = {n2} elements", + f" N3 = {n3} elements", + f" Area = {area} m^2" if area > 0 else f" Volume = {volume} m^3", + "", + "# Representative grid size", + "+ ------------------------", + f" h1 = {h1 * 1e3:.6f} mm", + f" h2 = {h2 * 1e3:.6f} mm", + f" h3 = {h3 * 1e3:.6f} mm", + "", + "# Refinement ratio", + "+ ----------------", + f" r21 = {ratio21:.6f}", + f" r32 = {ratio32:.6f}", + ) + + print("\n".join([x for x in log])) + + +@app.command() +def gci( + h1: float = typer.Option( + ..., + help="Representative grid size of the fine mesh (in mm).", + metavar="", + ), + h2: float = typer.Option( + ..., + help="Representative grid size of the medium mesh (in mm).", + metavar="", + ), + h3: float = typer.Option( + ..., + help="Representative grid size of the coarse mesh (in mm).", + metavar="", + ), + f1: float = typer.Option( + ..., + help="Model output for the fine mesh.", + metavar="", + ), + f2: float = typer.Option( + ..., + help="Model output for the medium mesh.", + metavar="", + ), + f3: float = typer.Option( + ..., + help="Model output for the coarse mesh.", + metavar="", + ), + safety: float = typer.Option( + default=1.25, + help="Safety factor", + metavar="", + ), +) -> None: + """Compute the grid convergence index.""" + from .convergence import ( + apparent_order_of_convergence, + asymptotic_ratio, + gci_coarse, + gci_fine, + ) + + p = apparent_order_of_convergence(h1, h2, h3, f1, f2, f3) + + r21 = h2 / h1 + r32 = h3 / h2 + + gci21_fine = gci_fine(f1, f2, r21, p) + gci21_coarse = gci_coarse(f1, f2, r21, p) + + gci32_fine = gci_fine(f2, f3, r32, p) + gci32_coarse = gci_coarse(f2, f3, r32, p) + + r = asymptotic_ratio(gci21_fine, gci32_fine, r21, p) + + log = ( + "# Grid summary", + "+ ---------------------------------------", + f" h1 = {h1:.6e} m, f1 = {f1:.6e}", + f" h2 = {h2:.6e} m, f2 = {f2:.6e}", + f" h3 = {h3:.6e} m, f3 = {f3:.6e}", + "", + f"# GCI (safety factor = {safety})", + "+ ---------------------------------------", + f" GCI21_fine = {gci21_fine * safety:.6e}", + f" GCI21_coarse = {gci21_coarse * safety:.6e}", + "", + f" GCI32_fine = {gci32_fine * safety:.6e}", + f" GCI32_coarse = {gci32_coarse * safety:.6e}", + "", + f" Asymptotic ratio = {r:.6f}", + ) + + print("\n".join([x for x in log])) diff --git a/poetry.lock b/poetry.lock index f03c78c..8bac4af 100644 --- a/poetry.lock +++ b/poetry.lock @@ -63,9 +63,9 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy", "pytest-perf (>=0.9.2)"] [[package]] name = "mypy-extensions" @@ -133,6 +133,23 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "typer" +version = "0.7.0" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +click = ">=7.1.1,<9.0.0" + +[package.extras] +all = ["colorama (>=0.4.3,<0.5.0)", "rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)"] +dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"] +doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"] +test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)"] + [[package]] name = "typing-extensions" version = "4.0.1" @@ -150,13 +167,13 @@ optional = false python-versions = ">=3.6" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] +testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] [metadata] lock-version = "1.1" python-versions = ">=3.6.2" -content-hash = "7311c1bbcf05d3bf298b126d7cbdfd594bfc752c8e939e38e30d85e09dd247d4" +content-hash = "afb957a7d917282c8983a29729323454c05ead686bdf8021dc628ed5f89d5f70" [metadata.files] black = [ @@ -245,6 +262,10 @@ typed-ast = [ {file = "typed_ast-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:b6d17f37f6edd879141e64a5db17b67488cfeffeedad8c5cec0392305e9bc775"}, {file = "typed_ast-1.5.1.tar.gz", hash = "sha256:484137cab8ecf47e137260daa20bafbba5f4e3ec7fda1c1e69ab299b75fa81c5"}, ] +typer = [ + {file = "typer-0.7.0-py3-none-any.whl", hash = "sha256:b5e704f4e48ec263de1c0b3a2387cd405a13767d2f907f44c1a08cbad96f606d"}, + {file = "typer-0.7.0.tar.gz", hash = "sha256:ff797846578a9f2a201b53442aedeb543319466870fbe1c701eab66dd7681165"}, +] typing-extensions = [ {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, diff --git a/pyproject.toml b/pyproject.toml index 383000e..40eff79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,18 @@ [tool.poetry] name = "gcat" -version = "1.0.0" +version = "1.1.0" description = "A grid convergence analysis toolkit (GCAT) in Python." authors = ["Gabriel B. Santos "] license = "MIT" readme = "README.md" repository = "https://github.com/gabrielbdsantos/gcat" keywords = ["mesh independence", "grid convergence analysis", "GCI"] +packages = [ + { include = "gcat" }, +] + +[tool.poetry.scripts] +gcat = 'gcat.cli:app' [tool.poetry.dependencies] python = ">=3.6.2" @@ -15,6 +21,9 @@ python = ">=3.6.2" black = "^22.1.0" pydocstyle = "^6.1.1" +[tool.poetry.group.cli.dependencies] +typer = "^0.7.0" + [build-system] requires = ["poetry-core>=1.0.8"] build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c479d8b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +click==8.0.3 ; python_full_version >= "3.6.2" +colorama==0.4.4 ; platform_system == "Windows" and python_full_version >= "3.6.2" +importlib-metadata==4.8.3 ; python_version < "3.8" and python_full_version >= "3.6.2" +typer==0.7.0 ; python_full_version >= "3.6.2" +typing-extensions==4.0.1 ; python_version < "3.8" and python_full_version >= "3.6.2" +zipp==3.6.0 ; python_version < "3.8" and python_full_version >= "3.6.2"