Compare commits
81 Commits
Author | SHA1 | Date | |
---|---|---|---|
f3191aeafb | |||
f06eb9aaad | |||
e0c6686d2b | |||
c6136b6e4b | |||
e26eaf01eb | |||
da63856466 | |||
fc1b8432cb | |||
c5d2bf1783 | |||
fea53d0db7 | |||
13e02b57a4 | |||
c701f0452b | |||
fb52e4c6c8 | |||
34313205d6 | |||
12ee8ab0b0 | |||
33ff54bd3b | |||
6bfbb83b77 | |||
a4e9a8f8dc | |||
00f7279c6d | |||
ac9e938a50 | |||
7dce03e97a | |||
d724b45e85 | |||
c7111dc9cc | |||
8fa62bba38 | |||
e501b8b52b | |||
479e7d666a | |||
3eb1029cb5 | |||
b41c730e48 | |||
29a9a7fa7f | |||
59e44dd08d | |||
11b612a99f | |||
9d4aac4652 | |||
fba3e53bb6 | |||
49368cc681 | |||
8e769ec1ce | |||
6601525d22 | |||
c166be7801 | |||
49a7390473 | |||
f94333ea0b | |||
ace2cd0247 | |||
7454714d70 | |||
c35d5cfe3c | |||
64a5cf1a6d | |||
7d0fe5a4a9 | |||
c2ef046f74 | |||
9e98df1ffd | |||
17934fe17a | |||
604a877b6e | |||
8df065d5aa | |||
e977c39496 | |||
f0ce49a0d8 | |||
71d25cb510 | |||
f64864a29a | |||
c22aa7f3f2 | |||
8f4c664a74 | |||
c56504f120 | |||
88f07abf4a | |||
b08a798c08 | |||
3cf2391399 | |||
2c27c25389 | |||
8920e89928 | |||
6d90fa1c33 | |||
06dcb26391 | |||
085ec5a0e2 | |||
120c2f260c | |||
0859b6bdf5 | |||
493094749d | |||
b2343aabb4 | |||
3071a81d1b | |||
b45954d153 | |||
77647b3370 | |||
c57ac1154a | |||
1d36353628 | |||
d43c4bd012 | |||
0b9a6d3f5c | |||
3b896fb3fc | |||
73e6c98675 | |||
1374ce0000 | |||
eda9a56a28 | |||
8c2953b3ec | |||
2ea14a5770 | |||
8ed50bc9a3 |
28
.circleci/config.yml
Normal file
28
.circleci/config.yml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
version: 2
|
||||||
|
test:
|
||||||
|
jobs:
|
||||||
|
- test-3.10
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-3.10:
|
||||||
|
docker:
|
||||||
|
- image: cimg/python:3.10
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run:
|
||||||
|
name: install packages
|
||||||
|
command: |
|
||||||
|
pip install poetry
|
||||||
|
poetry config virtualenvs.in-project true
|
||||||
|
poetry install
|
||||||
|
- run:
|
||||||
|
name: run tests
|
||||||
|
command: |
|
||||||
|
mkdir test-reports
|
||||||
|
poetry run pytest --cov=anisotropy --json-report --json-report-file test-reports/pytest.json -v tests
|
||||||
|
- store_artifacts:
|
||||||
|
path: test-reports
|
||||||
|
|
9
.gitignore
vendored
9
.gitignore
vendored
@ -4,6 +4,7 @@ logs/
|
|||||||
storage/
|
storage/
|
||||||
temp/
|
temp/
|
||||||
env/
|
env/
|
||||||
|
*output/
|
||||||
*.gz
|
*.gz
|
||||||
*.xz
|
*.xz
|
||||||
*.fls
|
*.fls
|
||||||
@ -18,3 +19,11 @@ env/
|
|||||||
*.toc
|
*.toc
|
||||||
*.out
|
*.out
|
||||||
*.egg-info
|
*.egg-info
|
||||||
|
.ipynb_checkpoints/
|
||||||
|
.coverage
|
||||||
|
.venv
|
||||||
|
.pytest_cache
|
||||||
|
dist/
|
||||||
|
doc/anisotropy/*
|
||||||
|
!doc/anisotropy/.gitkeep
|
||||||
|
/.idea
|
||||||
|
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "anisotropy compute",
|
||||||
|
"type": "python",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/anisotropy/core/cli.py",
|
||||||
|
"args": ["compute", "-v"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"cwd": "/tmp/anisotropy",
|
||||||
|
"preLaunchTask": "prepare"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
12
.vscode/settings.json
vendored
Normal file
12
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"python.pythonPath": "${env:HOME}/.local/share/mambaforge/envs/anisotropy/bin/python",
|
||||||
|
"python.testing.pytestArgs": [
|
||||||
|
"tests"
|
||||||
|
],
|
||||||
|
"python.testing.unittestEnabled": false,
|
||||||
|
"python.testing.pytestEnabled": true,
|
||||||
|
"python.linting.enabled": true,
|
||||||
|
"python.linting.pylintEnabled": false,
|
||||||
|
"python.linting.flake8Enabled": true,
|
||||||
|
"python.linting.flake8Args": ["--ignore=E402,E251,E501,E201,E202,W293,W291,W504,E203", "--verbose"]
|
||||||
|
}
|
9
.vscode/tasks.json
vendored
Normal file
9
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [{
|
||||||
|
"label": "prepare",
|
||||||
|
"command": "mkdir",
|
||||||
|
"args": ["-p", "/tmp/anisotropy"],
|
||||||
|
"type": "shell",
|
||||||
|
}]
|
||||||
|
}
|
2
ISSUES.rst
Normal file
2
ISSUES.rst
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
List of known issues
|
||||||
|
====================
|
12
README.rst
12
README.rst
@ -1,3 +1,6 @@
|
|||||||
|
.. image:: https://circleci.com/gh/L-Nafaryus/anisotropy/tree/devel.svg?style=shield&circle-token=423bc964a997ded671ebd4ceacc25f9967acdffa
|
||||||
|
:target: https://circleci.com/gh/L-Nafaryus/anisotropy/tree/devel
|
||||||
|
|
||||||
anisotropy
|
anisotropy
|
||||||
==========
|
==========
|
||||||
|
|
||||||
@ -31,7 +34,14 @@ Dependencies
|
|||||||
|
|
||||||
.. Installation
|
.. Installation
|
||||||
|
|
||||||
.. include:: INSTALL.rst
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ git clone https://github.com/L-Nafaryus/anisotropy.git
|
||||||
|
$ cd anisotropy
|
||||||
|
$ conda env create
|
||||||
|
$ conda activate anisotropy
|
||||||
|
$ poetry install
|
||||||
|
$ anisotropy --help
|
||||||
|
|
||||||
Getting Started
|
Getting Started
|
||||||
===============
|
===============
|
||||||
|
@ -1,40 +1,42 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
"""anisotropy
|
"""anisotropy
|
||||||
|
|
||||||
*anisotropy* is a ``Python`` package that is the result of science-research work
|
*anisotropy* is a ``Python`` package that is the result of science-research work
|
||||||
on the anisotropy of permeability in the periodic porous media.
|
on the anisotropy of permeability in the periodic porous media.
|
||||||
A project uses own wrappers around external applications
|
|
||||||
for constructing a shapes and meshes (``Salome``) and computing a flow (``OpenFOAM``).
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = "GPL3"
|
__license__ = "GPL3"
|
||||||
__version__ = "1.2.0"
|
__version__ = "1.2.0"
|
||||||
__author__ = __maintainer__ = "George Kusayko"
|
__author__ = __maintainer__ = "George Kusayko"
|
||||||
__email__ = "gkusayko@gmail.com"
|
__email__ = "gkusayko@gmail.com"
|
||||||
|
__repository__ = "https://github.com/L-Nafaryus/anisotropy"
|
||||||
|
|
||||||
###
|
###
|
||||||
# Environment
|
# Environment
|
||||||
##
|
##
|
||||||
import os
|
from os import path, environ
|
||||||
|
|
||||||
env = dict(
|
|
||||||
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
||||||
)
|
|
||||||
env.update(
|
|
||||||
BUILD = os.path.join(env["ROOT"], "build"),
|
|
||||||
LOG = os.path.join(env["ROOT"], "logs"),
|
|
||||||
CONFIG = os.path.join(env["ROOT"], "anisotropy/config/default.toml"),
|
|
||||||
DOCS = os.path.join(env["ROOT"], "docs")
|
|
||||||
)
|
|
||||||
env.update(
|
|
||||||
logger_name = "anisotropy",
|
|
||||||
db_name = "anisotropy",
|
|
||||||
db_path = env["BUILD"],
|
|
||||||
salome_timeout = 15 * 60,
|
|
||||||
openfoam_template = os.path.join(env["ROOT"], "anisotropy/openfoam/template")
|
|
||||||
)
|
|
||||||
|
|
||||||
del os
|
PROJECT = path.abspath(path.dirname(__file__))
|
||||||
|
TMP = "/tmp/anisotropy"
|
||||||
|
|
||||||
|
env = {
|
||||||
|
"PROJECT": path.abspath(path.dirname(__file__)),
|
||||||
|
"TMP": TMP,
|
||||||
|
"CWD": TMP,
|
||||||
|
"BUILD_DIR": "build",
|
||||||
|
"CONF_FILE": "anisotropy.toml",
|
||||||
|
"LOG_FILE": "anisotropy.log",
|
||||||
|
"DB_FILE": "anisotropy.db"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def loadEnv():
|
||||||
|
prefix = "AP_"
|
||||||
|
|
||||||
|
for k, v in env.items():
|
||||||
|
environ[f"{ prefix }{ k }"] = v
|
||||||
|
|
||||||
|
|
||||||
|
del path, PROJECT, TMP
|
||||||
|
15
anisotropy/cli/__init__.py
Normal file
15
anisotropy/cli/__init__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
from .cli import anisotropy_cli
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"utils",
|
||||||
|
"anisotropy_cli"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
anisotropy_cli()
|
181
anisotropy/cli/cli.py
Normal file
181
anisotropy/cli/cli.py
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import click
|
||||||
|
import pathlib
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
@click.version_option()
|
||||||
|
def anisotropy_cli():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@anisotropy_cli.command(
|
||||||
|
help = "Initialize new anisotropy project."
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-P", "--path", "path",
|
||||||
|
default = os.getcwd(),
|
||||||
|
help = "Specify directory to use (instead of cwd)"
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-v", "--verbose", "verbose",
|
||||||
|
count = True,
|
||||||
|
help = "Increase verbose level"
|
||||||
|
)
|
||||||
|
def init(path, verbose):
|
||||||
|
from anisotropy.core import config as core_config
|
||||||
|
from anisotropy.core import utils as core_utils
|
||||||
|
|
||||||
|
core_utils.setupLogger(utils.verbose_level(verbose))
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
config = core_config.default_config()
|
||||||
|
filepath = os.path.abspath(os.path.join(path, "anisotropy.toml"))
|
||||||
|
|
||||||
|
logger.info(f"Saving file at { filepath }")
|
||||||
|
config.dump(filepath)
|
||||||
|
|
||||||
|
|
||||||
|
@anisotropy_cli.command()
|
||||||
|
@click.option(
|
||||||
|
"-P", "--path", "path",
|
||||||
|
default = os.getcwd(),
|
||||||
|
help = "Specify directory to use (instead of cwd)"
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-C", "--conf", "configFile"
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-N", "--nprocs", "nprocs",
|
||||||
|
type = click.INT,
|
||||||
|
# default = 1,
|
||||||
|
help = "Count of parallel processes"
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-s", "--stage", "stage",
|
||||||
|
type = click.Choice(["all", "shape", "mesh", "flow", "postProcess"]),
|
||||||
|
# default = "all",
|
||||||
|
help = "Current computation stage"
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-f", "--force", "overwrite",
|
||||||
|
is_flag = True,
|
||||||
|
default = None,
|
||||||
|
help = "Overwrite existing entries"
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-p", "--params", "params",
|
||||||
|
metavar = "key=value",
|
||||||
|
multiple = True,
|
||||||
|
cls = utils.KeyValueOption,
|
||||||
|
help = "Overwrite existing parameter (except control variables)"
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-v", "--verbose", "verbose",
|
||||||
|
count = True,
|
||||||
|
help = "Increase verbose level"
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--exec-id", "execution"
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--pid", "pid",
|
||||||
|
help = "Specify pid file path"
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--logfile", "logfile",
|
||||||
|
help = "Specify log file path"
|
||||||
|
)
|
||||||
|
def compute(path, configFile, nprocs, stage, overwrite, params, verbose, execution, pid, logfile):
|
||||||
|
import anisotropy
|
||||||
|
from anisotropy.core import UltimateRunner
|
||||||
|
from anisotropy.core import config as core_config
|
||||||
|
from anisotropy.core import utils as core_utils
|
||||||
|
|
||||||
|
anisotropy.loadEnv()
|
||||||
|
|
||||||
|
if path:
|
||||||
|
path = pathlib.Path(path).resolve()
|
||||||
|
|
||||||
|
os.makedirs(path, exist_ok = True)
|
||||||
|
os.chdir(path)
|
||||||
|
os.environ["AP_CWD"] = str(path)
|
||||||
|
|
||||||
|
core_utils.setupLogger(utils.verbose_level(verbose), logfile)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
config = core_config.default_config()
|
||||||
|
|
||||||
|
if configFile:
|
||||||
|
configFile = pathlib.Path(configFile).resolve()
|
||||||
|
|
||||||
|
logger.info(f"Loading file from { configFile }")
|
||||||
|
|
||||||
|
try:
|
||||||
|
config.load(configFile)
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
config.dump(configFile)
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.info("Using default configuration")
|
||||||
|
|
||||||
|
args = {
|
||||||
|
"nprocs": nprocs,
|
||||||
|
"stage": stage,
|
||||||
|
"overwrite": overwrite
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v in args.items():
|
||||||
|
if v is not None:
|
||||||
|
config.update(**{ k: v })
|
||||||
|
|
||||||
|
if pid:
|
||||||
|
pid = pathlib.Path(pid).resolve()
|
||||||
|
|
||||||
|
with open(pid, "w") as io:
|
||||||
|
io.write(str(os.getpid()))
|
||||||
|
|
||||||
|
runner = UltimateRunner(config = config, exec_id = execution)
|
||||||
|
runner.prepare_queue()
|
||||||
|
runner.start()
|
||||||
|
|
||||||
|
if pid:
|
||||||
|
os.remove(pid)
|
||||||
|
|
||||||
|
logger.info("Computation done. Exiting ...")
|
||||||
|
|
||||||
|
|
||||||
|
@anisotropy_cli.command()
|
||||||
|
@click.option(
|
||||||
|
"-P", "--path", "path",
|
||||||
|
default = os.getcwd(),
|
||||||
|
help = "Specify directory to use (instead of cwd)"
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-v", "--verbose", "verbose",
|
||||||
|
count = True,
|
||||||
|
help = "Increase verbose level"
|
||||||
|
)
|
||||||
|
def gui(path, verbose):
|
||||||
|
import anisotropy
|
||||||
|
from anisotropy.core import utils as core_utils
|
||||||
|
from anisotropy import gui
|
||||||
|
|
||||||
|
anisotropy.loadEnv()
|
||||||
|
|
||||||
|
path = pathlib.Path(path).resolve()
|
||||||
|
path.mkdir(parents = True, exist_ok = True)
|
||||||
|
|
||||||
|
os.chdir(path)
|
||||||
|
os.environ["ANISOTROPY_CWD"] = str(path)
|
||||||
|
|
||||||
|
core_utils.setupLogger(utils.verbose_level(verbose))
|
||||||
|
# logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
gui.run(debug = True)
|
74
anisotropy/cli/utils.py
Normal file
74
anisotropy/cli/utils.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import click
|
||||||
|
import ast
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class LiteralOption(click.Option):
|
||||||
|
def type_cast_value(self, ctx, value):
|
||||||
|
try:
|
||||||
|
return ast.literal_eval(value)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
raise click.BadParameter(f"{ value } (Type error)")
|
||||||
|
|
||||||
|
|
||||||
|
class KeyValueOption(click.Option):
|
||||||
|
def _convert(self, ctx, value):
|
||||||
|
if not value:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if value.find("=") == -1:
|
||||||
|
raise click.BadParameter(f"{ value } (Missed '=')")
|
||||||
|
|
||||||
|
params = value.split("=")
|
||||||
|
|
||||||
|
if not len(params) == 2:
|
||||||
|
raise click.BadParameter(f"{ value } (Syntax error)")
|
||||||
|
|
||||||
|
key, val = params[0].strip(), params[1].strip()
|
||||||
|
|
||||||
|
if val[0].isalpha():
|
||||||
|
val = f"'{ val }'"
|
||||||
|
|
||||||
|
try:
|
||||||
|
return { key: ast.literal_eval(val) }
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
raise click.BadParameter(f"{ value } (Type error)")
|
||||||
|
|
||||||
|
def type_cast_value(self, ctx, value):
|
||||||
|
if isinstance(value, list):
|
||||||
|
return [ self._convert(ctx, val) for val in value ]
|
||||||
|
|
||||||
|
else:
|
||||||
|
return self._convert(ctx, value)
|
||||||
|
|
||||||
|
|
||||||
|
class CliListOption(click.Option):
|
||||||
|
def _convert(self, ctx, value):
|
||||||
|
if not value:
|
||||||
|
return []
|
||||||
|
|
||||||
|
output = [ val for val in value.split(",") ]
|
||||||
|
|
||||||
|
if "" in output:
|
||||||
|
raise click.BadParameter(f"{ value } (Trying to pass empty item)")
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def type_cast_value(self, ctx, value):
|
||||||
|
if isinstance(value, list):
|
||||||
|
return [ self._convert(ctx, val) for val in value ]
|
||||||
|
|
||||||
|
else:
|
||||||
|
return self._convert(ctx, value)
|
||||||
|
|
||||||
|
|
||||||
|
def verbose_level(level: int):
|
||||||
|
return {
|
||||||
|
0: logging.ERROR,
|
||||||
|
1: logging.INFO,
|
||||||
|
2: logging.DEBUG
|
||||||
|
}.get(level, logging.ERROR)
|
@ -1,3 +0,0 @@
|
|||||||
# Example of anisotropy bashrc entries (modify it)
|
|
||||||
export PATH="${HOME}/programs/salome/SALOME-9.7.0-MPI:${PATH}"
|
|
||||||
source "${HOME}/programs/OpenFOAM/OpenFOAM-v2012/etc/bashrc"
|
|
@ -1,281 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
[logger]
|
|
||||||
name = "anisotropy"
|
|
||||||
format = "[ %(levelname)s ] %(message)s"
|
|
||||||
|
|
||||||
[base]
|
|
||||||
simple = true
|
|
||||||
bodyCentered = true
|
|
||||||
faceCentered = true
|
|
||||||
|
|
||||||
[[structures]]
|
|
||||||
[structures.structure]
|
|
||||||
type = "simple"
|
|
||||||
# auto # from theta: list # theta: float
|
|
||||||
theta = [0.01, 0.28, 0.01] # [min, max, step]
|
|
||||||
# auto # from directions:list # direction: list
|
|
||||||
directions = [
|
|
||||||
[1, 0, 0],
|
|
||||||
[0, 0, 1],
|
|
||||||
[1, 1, 1]
|
|
||||||
]
|
|
||||||
|
|
||||||
# r0 =
|
|
||||||
# L =
|
|
||||||
# radius =
|
|
||||||
|
|
||||||
filletsEnabled = true
|
|
||||||
# auto # fillets: float
|
|
||||||
|
|
||||||
[structures.mesh]
|
|
||||||
maxSize = 0.5
|
|
||||||
minSize = 0.05
|
|
||||||
|
|
||||||
fineness = 5
|
|
||||||
growthRate = 0.5
|
|
||||||
nbSegPerEdge = 2
|
|
||||||
nbSegPerRadius = 1
|
|
||||||
|
|
||||||
chordalErrorEnabled = true
|
|
||||||
chordalError = 0.25
|
|
||||||
|
|
||||||
secondOrder = false
|
|
||||||
optimize = true
|
|
||||||
quadAllowed = false
|
|
||||||
useSurfaceCurvature = true
|
|
||||||
fuseEdges = true
|
|
||||||
checkChartBoundary = false
|
|
||||||
|
|
||||||
viscousLayers = true
|
|
||||||
thickness = [0.01, 0.005] # [min, max] # step is controlled by theta count
|
|
||||||
numberOfLayers = 1
|
|
||||||
stretchFactor = 1
|
|
||||||
isFacesToIgnore = true
|
|
||||||
facesToIgnore = ["inlet", "outlet"]
|
|
||||||
# auto # faces: list
|
|
||||||
extrusionMethod = "SURF_OFFSET_SMOOTH"
|
|
||||||
|
|
||||||
[[structures.submesh]]
|
|
||||||
name = "strips"
|
|
||||||
maxSize = 0.5
|
|
||||||
minSize = 0.05
|
|
||||||
|
|
||||||
fineness = 5
|
|
||||||
growthRate = 0.2
|
|
||||||
nbSegPerEdge = 2
|
|
||||||
nbSegPerRadius = 3
|
|
||||||
|
|
||||||
chordalErrorEnabled = true
|
|
||||||
chordalError = 0.25
|
|
||||||
|
|
||||||
secondOrder = false
|
|
||||||
optimize = true
|
|
||||||
quadAllowed = false
|
|
||||||
useSurfaceCurvature = true
|
|
||||||
fuseEdges = true
|
|
||||||
checkChartBoundary = false
|
|
||||||
|
|
||||||
[structures.flowapproximation]
|
|
||||||
transportProperties.nu = 1e-6
|
|
||||||
|
|
||||||
pressure.boundaryField.inlet = { type = "fixedValue", value = 1e-3 }
|
|
||||||
pressure.boundaryField.outlet = { type = "fixedValue", value = 0.0 }
|
|
||||||
|
|
||||||
# multiplication velocity value with flow direction vector
|
|
||||||
velocity.boundaryField.inlet = { type = "fixedValue", value = 6e-5 }
|
|
||||||
velocity.boundaryField.outlet = { type = "zeroGradient", value = "None" }
|
|
||||||
|
|
||||||
[structures.flow]
|
|
||||||
scale = [ 1e-5, 1e-5, 1e-5 ]
|
|
||||||
|
|
||||||
transportProperties.nu = 1e-6
|
|
||||||
|
|
||||||
pressure.boundaryField.inlet = { type = "fixedValue", value = 1e-3 }
|
|
||||||
pressure.boundaryField.outlet = { type = "fixedValue", value = 0.0 }
|
|
||||||
|
|
||||||
velocity.boundaryField.inlet = { type = "pressureInletVelocity", value = 0.0 }
|
|
||||||
velocity.boundaryField.outlet = { type = "zeroGradient", value = "None" }
|
|
||||||
|
|
||||||
|
|
||||||
[[structures]]
|
|
||||||
[structures.structure]
|
|
||||||
type = "bodyCentered"
|
|
||||||
# auto # from theta: list # theta: float
|
|
||||||
theta = [0.01, 0.17, 0.01] # [min, max, step]
|
|
||||||
# auto # from directions:list # direction: list
|
|
||||||
directions = [
|
|
||||||
[1, 0, 0],
|
|
||||||
[0, 0, 1],
|
|
||||||
[1, 1, 1]
|
|
||||||
]
|
|
||||||
|
|
||||||
# r0 =
|
|
||||||
# L =
|
|
||||||
# radius =
|
|
||||||
|
|
||||||
filletsEnabled = true
|
|
||||||
# auto # fillets: float
|
|
||||||
|
|
||||||
[structures.mesh]
|
|
||||||
maxSize = 0.5
|
|
||||||
minSize = 0.05
|
|
||||||
|
|
||||||
fineness = 5
|
|
||||||
growthRate = 0.5
|
|
||||||
nbSegPerEdge = 2
|
|
||||||
nbSegPerRadius = 1
|
|
||||||
|
|
||||||
chordalErrorEnabled = true
|
|
||||||
chordalError = 0.25
|
|
||||||
|
|
||||||
secondOrder = false
|
|
||||||
optimize = true
|
|
||||||
quadAllowed = false
|
|
||||||
useSurfaceCurvature = true
|
|
||||||
fuseEdges = true
|
|
||||||
checkChartBoundary = false
|
|
||||||
|
|
||||||
viscousLayers = true
|
|
||||||
thickness = [0.005, 0.0005] # [min, max] # step is controlled by theta count
|
|
||||||
numberOfLayers = 1
|
|
||||||
stretchFactor = 1
|
|
||||||
isFacesToIgnore = true
|
|
||||||
facesToIgnore = ["inlet", "outlet"]
|
|
||||||
# auto # faces: list
|
|
||||||
extrusionMethod = "SURF_OFFSET_SMOOTH"
|
|
||||||
|
|
||||||
[[structures.submesh]]
|
|
||||||
name = "strips"
|
|
||||||
maxSize = 0.5
|
|
||||||
minSize = 0.05
|
|
||||||
|
|
||||||
fineness = 5
|
|
||||||
growthRate = 0.2
|
|
||||||
nbSegPerEdge = 2
|
|
||||||
nbSegPerRadius = 3
|
|
||||||
|
|
||||||
chordalErrorEnabled = true
|
|
||||||
chordalError = 0.25
|
|
||||||
|
|
||||||
secondOrder = false
|
|
||||||
optimize = true
|
|
||||||
quadAllowed = false
|
|
||||||
useSurfaceCurvature = true
|
|
||||||
fuseEdges = true
|
|
||||||
checkChartBoundary = false
|
|
||||||
|
|
||||||
[structures.flowapproximation]
|
|
||||||
transportProperties.nu = 1e-6
|
|
||||||
|
|
||||||
pressure.boundaryField.inlet = { type = "fixedValue", value = 1e-3 }
|
|
||||||
pressure.boundaryField.outlet = { type = "fixedValue", value = 0.0 }
|
|
||||||
|
|
||||||
# multiplication velocity value with direction vector
|
|
||||||
velocity.boundaryField.inlet = { type = "fixedValue", value = 6e-5 }
|
|
||||||
velocity.boundaryField.outlet = { type = "zeroGradient", value = "None" }
|
|
||||||
|
|
||||||
[structures.flow]
|
|
||||||
scale = [ 1e-5, 1e-5, 1e-5 ]
|
|
||||||
|
|
||||||
transportProperties.nu = 1e-6
|
|
||||||
|
|
||||||
pressure.boundaryField.inlet = { type = "fixedValue", value = 1e-3 }
|
|
||||||
pressure.boundaryField.outlet = { type = "fixedValue", value = 0.0 }
|
|
||||||
|
|
||||||
velocity.boundaryField.inlet = { type = "pressureInletVelocity", value = 0.0 }
|
|
||||||
velocity.boundaryField.outlet = { type = "zeroGradient", value = "None" }
|
|
||||||
|
|
||||||
|
|
||||||
[[structures]]
|
|
||||||
[structures.structure]
|
|
||||||
type = "faceCentered"
|
|
||||||
# auto # from theta: list # theta: float
|
|
||||||
theta = [0.01, 0.12, 0.01] # [min, max, step]
|
|
||||||
# auto # from directions:list # direction: list
|
|
||||||
directions = [
|
|
||||||
[1, 0, 0],
|
|
||||||
[0, 0, 1],
|
|
||||||
[1, 1, 1]
|
|
||||||
]
|
|
||||||
|
|
||||||
# r0 =
|
|
||||||
# L =
|
|
||||||
# radius =
|
|
||||||
|
|
||||||
filletsEnabled = true
|
|
||||||
# auto # fillets: float
|
|
||||||
|
|
||||||
[structures.mesh]
|
|
||||||
maxSize = 0.5
|
|
||||||
minSize = 0.05
|
|
||||||
|
|
||||||
fineness = 5
|
|
||||||
growthRate = 0.5
|
|
||||||
nbSegPerEdge = 2
|
|
||||||
nbSegPerRadius = 1
|
|
||||||
|
|
||||||
chordalErrorEnabled = true
|
|
||||||
chordalError = 0.25
|
|
||||||
|
|
||||||
secondOrder = false
|
|
||||||
optimize = true
|
|
||||||
quadAllowed = false
|
|
||||||
useSurfaceCurvature = true
|
|
||||||
fuseEdges = true
|
|
||||||
checkChartBoundary = false
|
|
||||||
|
|
||||||
viscousLayers = true
|
|
||||||
thickness = [0.001, 0.0005] # [min, max] # step is controlled by theta count
|
|
||||||
numberOfLayers = 1
|
|
||||||
stretchFactor = 1
|
|
||||||
isFacesToIgnore = true
|
|
||||||
facesToIgnore = ["inlet", "outlet"]
|
|
||||||
# auto # faces: list
|
|
||||||
extrusionMethod = "SURF_OFFSET_SMOOTH"
|
|
||||||
|
|
||||||
[[structures.submesh]]
|
|
||||||
name = "strips"
|
|
||||||
maxSize = 0.5
|
|
||||||
minSize = 0.05
|
|
||||||
|
|
||||||
fineness = 5
|
|
||||||
growthRate = 0.2
|
|
||||||
nbSegPerEdge = 2
|
|
||||||
nbSegPerRadius = 3
|
|
||||||
|
|
||||||
chordalErrorEnabled = true
|
|
||||||
chordalError = 0.25
|
|
||||||
|
|
||||||
secondOrder = false
|
|
||||||
optimize = true
|
|
||||||
quadAllowed = false
|
|
||||||
useSurfaceCurvature = true
|
|
||||||
fuseEdges = true
|
|
||||||
checkChartBoundary = false
|
|
||||||
|
|
||||||
[structures.flowapproximation]
|
|
||||||
transportProperties.nu = 1e-6
|
|
||||||
|
|
||||||
pressure.boundaryField.inlet = { type = "fixedValue", value = 1e-3 }
|
|
||||||
pressure.boundaryField.outlet = { type = "fixedValue", value = 0.0 }
|
|
||||||
|
|
||||||
# multiplication velocity value with direction vector
|
|
||||||
velocity.boundaryField.inlet = { type = "fixedValue", value = 6e-5 }
|
|
||||||
velocity.boundaryField.outlet = { type = "zeroGradient", value = "None" }
|
|
||||||
|
|
||||||
[structures.flow]
|
|
||||||
scale = [ 1e-5, 1e-5, 1e-5 ]
|
|
||||||
|
|
||||||
transportProperties.nu = 1e-6
|
|
||||||
|
|
||||||
pressure.boundaryField.inlet = { type = "fixedValue", value = 1e-3 }
|
|
||||||
pressure.boundaryField.outlet = { type = "fixedValue", value = 0.0 }
|
|
||||||
|
|
||||||
velocity.boundaryField.inlet = { type = "pressureInletVelocity", value = 0.0 }
|
|
||||||
velocity.boundaryField.outlet = { type = "zeroGradient", value = "None" }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,5 +1,17 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
from . import utils
|
||||||
|
from . import config
|
||||||
|
from . import postprocess
|
||||||
|
|
||||||
|
from .parallel import ParallelRunner
|
||||||
|
from .runner import UltimateRunner
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"utils",
|
||||||
|
"config",
|
||||||
|
"postprocess",
|
||||||
|
"ParallelRunner",
|
||||||
|
"UltimateRunner"
|
||||||
|
]
|
||||||
|
@ -1,579 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
import click
|
|
||||||
import ast
|
|
||||||
import os, sys, shutil
|
|
||||||
import logging
|
|
||||||
|
|
||||||
class LiteralOption(click.Option):
|
|
||||||
def type_cast_value(self, ctx, value):
|
|
||||||
try:
|
|
||||||
return ast.literal_eval(value)
|
|
||||||
|
|
||||||
except:
|
|
||||||
raise click.BadParameter(f"{ value } (Type error)")
|
|
||||||
|
|
||||||
class KeyValueOption(click.Option):
|
|
||||||
def _convert(self, ctx, value):
|
|
||||||
if not value:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
if value.find("=") == -1:
|
|
||||||
raise click.BadParameter(f"{ value } (Missed '=')")
|
|
||||||
|
|
||||||
params = value.split("=")
|
|
||||||
|
|
||||||
if not len(params) == 2:
|
|
||||||
raise click.BadParameter(f"{ value } (Syntax error)")
|
|
||||||
|
|
||||||
key, val = params[0].strip(), params[1].strip()
|
|
||||||
|
|
||||||
if val[0].isalpha():
|
|
||||||
val = f"'{ val }'"
|
|
||||||
|
|
||||||
try:
|
|
||||||
return { key: ast.literal_eval(val) }
|
|
||||||
|
|
||||||
except:
|
|
||||||
raise click.BadParameter(f"{ value } (Type error)")
|
|
||||||
|
|
||||||
def type_cast_value(self, ctx, value):
|
|
||||||
if isinstance(value, list):
|
|
||||||
return [ self._convert(ctx, val) for val in value ]
|
|
||||||
|
|
||||||
else:
|
|
||||||
return self._convert(ctx, value)
|
|
||||||
|
|
||||||
class CliListOption(click.Option):
|
|
||||||
def _convert(self, ctx, value):
|
|
||||||
if not value:
|
|
||||||
return []
|
|
||||||
|
|
||||||
output = [ val for val in value.split(",") ]
|
|
||||||
|
|
||||||
if "" in output:
|
|
||||||
raise click.BadParameter(f"{ value } (Trying to pass empty item)")
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
|
|
||||||
def type_cast_value(self, ctx, value):
|
|
||||||
if isinstance(value, list):
|
|
||||||
return [ self._convert(ctx, val) for val in value ]
|
|
||||||
|
|
||||||
else:
|
|
||||||
return self._convert(ctx, value)
|
|
||||||
|
|
||||||
|
|
||||||
def version():
|
|
||||||
msg = "Missed package anisotropy"
|
|
||||||
|
|
||||||
try:
|
|
||||||
from anisotropy.core.main import Anisotropy
|
|
||||||
msg = Anisotropy.version()
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return msg
|
|
||||||
|
|
||||||
@click.group()
|
|
||||||
@click.version_option(version = "", message = version())
|
|
||||||
def anisotropy():
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@anisotropy.command(
|
|
||||||
help = "Initialize new anisotropy project."
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"-P", "--path", "path",
|
|
||||||
default = os.getcwd(),
|
|
||||||
help = "Specify directory to use (instead of cwd)"
|
|
||||||
)
|
|
||||||
def init(path):
|
|
||||||
from anisotropy import env
|
|
||||||
from anisotropy.core.main import Database
|
|
||||||
|
|
||||||
if not os.path.exists(path) or not os.path.isdir(path):
|
|
||||||
click.echo(f"Cannot find directory { path }")
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
wds = [ "build", "logs" ]
|
|
||||||
|
|
||||||
for wd in wds:
|
|
||||||
os.makedirs(os.path.join(path, wd), exist_ok = True)
|
|
||||||
|
|
||||||
shutil.copy(env["CONFIG"], os.path.join(path, "anisotropy.toml"), follow_symlinks = True)
|
|
||||||
|
|
||||||
db = Database(env["db_name"], path)
|
|
||||||
db.setup()
|
|
||||||
|
|
||||||
click.echo(f"Initialized anisotropy project in { path }")
|
|
||||||
|
|
||||||
|
|
||||||
@anisotropy.command(
|
|
||||||
help = "Load parameters from configuration file and update database."
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"-f", "--force", "force",
|
|
||||||
is_flag = True,
|
|
||||||
default = False,
|
|
||||||
help = "Overwrite existing entries"
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"-p", "--params", "params",
|
|
||||||
metavar = "key=value",
|
|
||||||
multiple = True,
|
|
||||||
cls = KeyValueOption,
|
|
||||||
help = "Specify control parameters to update (type, direction, theta)"
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"-P", "--path", "path",
|
|
||||||
default = os.getcwd(),
|
|
||||||
help = "Specify directory to use (instead of cwd)"
|
|
||||||
)
|
|
||||||
def update(force, params, path):
|
|
||||||
from anisotropy import env
|
|
||||||
from anisotropy.core.main import Anisotropy, Database
|
|
||||||
|
|
||||||
env.update(
|
|
||||||
LOG = os.path.join(path, "logs"),
|
|
||||||
BUILD = os.path.join(path, "build"),
|
|
||||||
CONFIG = os.path.join(path, "anisotropy.toml"),
|
|
||||||
db_path = path
|
|
||||||
)
|
|
||||||
|
|
||||||
args = dict()
|
|
||||||
|
|
||||||
for param in params:
|
|
||||||
args.update(param)
|
|
||||||
|
|
||||||
|
|
||||||
model = Anisotropy()
|
|
||||||
database = Database(env["db_name"], env["db_path"])
|
|
||||||
|
|
||||||
click.echo("Configuring database ...")
|
|
||||||
database.setup()
|
|
||||||
|
|
||||||
if database.isempty() or update:
|
|
||||||
paramsAll = model.loadFromScratch(env["CONFIG"])
|
|
||||||
|
|
||||||
if args.get("type"):
|
|
||||||
paramsAll = [ entry for entry in paramsAll if args["type"] == entry["structure"]["type"] ]
|
|
||||||
|
|
||||||
if args.get("direction"):
|
|
||||||
paramsAll = [ entry for entry in paramsAll if args["direction"] == entry["structure"]["direction"] ]
|
|
||||||
|
|
||||||
if args.get("theta"):
|
|
||||||
paramsAll = [ entry for entry in paramsAll if args["theta"] == entry["structure"]["theta"] ]
|
|
||||||
|
|
||||||
for entry in paramsAll:
|
|
||||||
database.update(entry)
|
|
||||||
|
|
||||||
click.echo("{} entries was updated.".format(len(paramsAll)))
|
|
||||||
|
|
||||||
else:
|
|
||||||
click.echo("Database was not modified.")
|
|
||||||
|
|
||||||
|
|
||||||
@anisotropy.command(
|
|
||||||
help = """Compute cases by chain (mesh -> flow)
|
|
||||||
|
|
||||||
Control parameters: type, direction, theta (each parameter affects on a queue)
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"-s", "--stage", "stage",
|
|
||||||
type = click.Choice(["all", "mesh", "flow", "postProcessing"]),
|
|
||||||
default = "all",
|
|
||||||
help = "Current computation stage"
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"-N", "--nprocs", "nprocs",
|
|
||||||
type = click.INT,
|
|
||||||
default = 1,
|
|
||||||
help = "Count of parallel processes"
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"-f", "--force", "force",
|
|
||||||
is_flag = True,
|
|
||||||
default = False,
|
|
||||||
help = "Overwrite existing entries"
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"-p", "--params", "params",
|
|
||||||
metavar = "key=value",
|
|
||||||
multiple = True,
|
|
||||||
cls = KeyValueOption,
|
|
||||||
help = "Overwrite existing parameter (except control variables)"
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"-P", "--path", "path",
|
|
||||||
default = os.getcwd(),
|
|
||||||
help = "Specify directory to use (instead of cwd)"
|
|
||||||
)
|
|
||||||
def compute(stage, nprocs, force, params, path):
|
|
||||||
from anisotropy import env
|
|
||||||
from anisotropy.core.main import Anisotropy, Database, logger
|
|
||||||
from anisotropy.core.utils import setupLogger, parallel
|
|
||||||
|
|
||||||
env.update(
|
|
||||||
LOG = os.path.join(path, "logs"),
|
|
||||||
BUILD = os.path.join(path, "build"),
|
|
||||||
CONFIG = os.path.join(path, "anisotropy.toml"),
|
|
||||||
db_path = path
|
|
||||||
)
|
|
||||||
|
|
||||||
setupLogger(logger, logging.INFO, env["LOG"])
|
|
||||||
args = dict()
|
|
||||||
|
|
||||||
for param in params:
|
|
||||||
args.update(param)
|
|
||||||
|
|
||||||
###
|
|
||||||
logger.info("Writing pid ...")
|
|
||||||
pidpath = os.path.join(path, "anisotropy.pid")
|
|
||||||
|
|
||||||
with open(pidpath, "w") as io:
|
|
||||||
io.write(str(os.getpid()))
|
|
||||||
|
|
||||||
###
|
|
||||||
# Preparations
|
|
||||||
##
|
|
||||||
database = Database(env["db_name"], env["db_path"])
|
|
||||||
|
|
||||||
logger.info("Loading database ...")
|
|
||||||
database.setup()
|
|
||||||
|
|
||||||
params = database.loadGeneral(
|
|
||||||
args.get("type"),
|
|
||||||
args.get("direction"),
|
|
||||||
args.get("theta")
|
|
||||||
)
|
|
||||||
queueargs = []
|
|
||||||
|
|
||||||
for p in params:
|
|
||||||
s = p["structure"]
|
|
||||||
|
|
||||||
queueargs.append((s["type"], s["direction"], s["theta"]))
|
|
||||||
|
|
||||||
###
|
|
||||||
# Wrap function
|
|
||||||
##
|
|
||||||
def computeCase(type, direction, theta):
|
|
||||||
case = Anisotropy()
|
|
||||||
case.db = database
|
|
||||||
case.load(type, direction, theta)
|
|
||||||
case.evalParams()
|
|
||||||
case.update()
|
|
||||||
|
|
||||||
logger.info(f"Case: type = { type }, direction = { direction }, theta = { theta }")
|
|
||||||
logger.info(f"Stage mode: { stage }")
|
|
||||||
|
|
||||||
if stage in ["mesh", "all"]:
|
|
||||||
case.load(type, direction, theta)
|
|
||||||
|
|
||||||
if not case.params["meshresult"]["meshStatus"] == "Done" or force:
|
|
||||||
logger.info("Current stage: mesh")
|
|
||||||
out, err, returncode = case.computeMesh(path)
|
|
||||||
|
|
||||||
if out: logger.info(out)
|
|
||||||
if err: logger.error(err)
|
|
||||||
if returncode:
|
|
||||||
logger.error("Mesh computation failed. Skipping flow computation ...")
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.info("Mesh exists. Skipping ...")
|
|
||||||
|
|
||||||
if stage in ["flow", "all"]:
|
|
||||||
case.load(type, direction, theta)
|
|
||||||
|
|
||||||
if not case.params["flowresult"]["flowStatus"] == "Done" or force:
|
|
||||||
logger.info("Current stage: flow")
|
|
||||||
out, err, returncode = case.computeFlow(path)
|
|
||||||
|
|
||||||
if out: logger.info(out)
|
|
||||||
if err: logger.error(err)
|
|
||||||
if returncode:
|
|
||||||
logger.error("Flow computation failed.")
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.info("Flow exists. Skipping ...")
|
|
||||||
|
|
||||||
if stage in ["postProcessing", "all"]:
|
|
||||||
case.load(type, direction, theta)
|
|
||||||
|
|
||||||
if case.params["meshresult"]["meshStatus"] == "Done":
|
|
||||||
logger.info("Current stage: mesh postProcessing")
|
|
||||||
case.porosity()
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.warning("Cannot compute mesh post processing values.")
|
|
||||||
|
|
||||||
if case.params["flowresult"]["flowStatus"] == "Done":
|
|
||||||
logger.info("Current stage: flow postProcessing")
|
|
||||||
case.flowRate()
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.warning("Cannot compute flow post processing values.")
|
|
||||||
|
|
||||||
|
|
||||||
###
|
|
||||||
# Run
|
|
||||||
##
|
|
||||||
if nprocs == 1:
|
|
||||||
for pos, qarg in enumerate(queueargs):
|
|
||||||
computeCase(*qarg)
|
|
||||||
|
|
||||||
else:
|
|
||||||
parallel(nprocs, queueargs, computeCase)
|
|
||||||
|
|
||||||
if os.path.exists(pidpath):
|
|
||||||
logger.info("Removing pid ...")
|
|
||||||
os.remove(pidpath)
|
|
||||||
|
|
||||||
logger.info("Computation done.")
|
|
||||||
|
|
||||||
|
|
||||||
@anisotropy.command(
|
|
||||||
help = "Kill process by pid file"
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"-P", "--path", "path",
|
|
||||||
default = os.getcwd(),
|
|
||||||
help = "Specify directory to use (instead of cwd)"
|
|
||||||
)
|
|
||||||
@click.argument("pidfile")
|
|
||||||
def kill(path, pidfile):
|
|
||||||
from anisotropy.salomepl.utils import SalomeManager
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(os.path.join(path, pidfile), "r") as io:
|
|
||||||
pid = io.read()
|
|
||||||
|
|
||||||
os.kill(int(pid), 9)
|
|
||||||
|
|
||||||
except FileNotFoundError:
|
|
||||||
click.echo(f"Unknown file { pidfile }")
|
|
||||||
|
|
||||||
except ProcessLookupError:
|
|
||||||
click.echo(f"Cannot find process with pid { pid }")
|
|
||||||
|
|
||||||
# TODO: killall method kills all salome instances. Not a good way
|
|
||||||
SalomeManager().killall()
|
|
||||||
|
|
||||||
@anisotropy.command(
|
|
||||||
help = "! Internal command"
|
|
||||||
)
|
|
||||||
@click.argument("root")
|
|
||||||
@click.argument("type")
|
|
||||||
@click.argument("direction")
|
|
||||||
@click.argument("theta")
|
|
||||||
@click.argument("path")
|
|
||||||
def computemesh(root, type, direction, theta, path):
|
|
||||||
# ISSUE: can't hide command from help, 'hidden = True' doesn't work
|
|
||||||
# [Salome Environment]
|
|
||||||
|
|
||||||
###
|
|
||||||
# Args
|
|
||||||
##
|
|
||||||
direction = [ float(num) for num in direction[1:-1].split(" ") if num ]
|
|
||||||
theta = float(theta)
|
|
||||||
|
|
||||||
###
|
|
||||||
# Modules
|
|
||||||
##
|
|
||||||
import os, sys
|
|
||||||
|
|
||||||
pyversion = "{}.{}".format(3, 9) #(*sys.version_info[:2])
|
|
||||||
sys.path.extend([
|
|
||||||
root,
|
|
||||||
os.path.join(root, "env/lib/python{}/site-packages".format(pyversion)),
|
|
||||||
os.path.abspath(".local/lib/python{}/site-packages".format(pyversion))
|
|
||||||
])
|
|
||||||
|
|
||||||
from anisotropy import env
|
|
||||||
from anisotropy.core.main import Anisotropy, Database
|
|
||||||
import salome
|
|
||||||
|
|
||||||
###
|
|
||||||
model = Anisotropy()
|
|
||||||
model.db = Database(env["db_name"], path)
|
|
||||||
model.load(type, direction, theta)
|
|
||||||
|
|
||||||
salome.salome_init()
|
|
||||||
model.genmesh(path)
|
|
||||||
salome.salome_close()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@anisotropy.command()
|
|
||||||
@click.option(
|
|
||||||
"-p", "--params", "params",
|
|
||||||
metavar = "key=value",
|
|
||||||
multiple = True,
|
|
||||||
cls = KeyValueOption,
|
|
||||||
help = "Select by control parameters (type, direction, theta)"
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"-P", "--path", "path",
|
|
||||||
metavar = "PATH",
|
|
||||||
default = os.getcwd(),
|
|
||||||
help = "Specify directory to use (instead of cwd)"
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--list", "printlist",
|
|
||||||
is_flag = True,
|
|
||||||
help = "Print a list of avaliable fields."
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--export",
|
|
||||||
metavar = "PATH",
|
|
||||||
help = "Export output."
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--fields", "fields",
|
|
||||||
metavar = "f1,f2,...",
|
|
||||||
multiple = True,
|
|
||||||
cls = CliListOption,
|
|
||||||
help = "Select fields to use."
|
|
||||||
)
|
|
||||||
@click.argument(
|
|
||||||
"output",
|
|
||||||
required = False,
|
|
||||||
type = click.Choice(["cli", "plot"]),
|
|
||||||
default = "cli"
|
|
||||||
)
|
|
||||||
def show(params, path, printlist, export, fields, output):
|
|
||||||
from anisotropy import env
|
|
||||||
from anisotropy.core.database import Database, Structure
|
|
||||||
from pandas import DataFrame, Series
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
|
|
||||||
env.update(
|
|
||||||
LOG = os.path.join(path, "logs"),
|
|
||||||
BUILD = os.path.join(path, "build"),
|
|
||||||
CONFIG = os.path.join(path, "anisotropy.toml"),
|
|
||||||
db_path = path
|
|
||||||
)
|
|
||||||
|
|
||||||
args = dict()
|
|
||||||
|
|
||||||
for param in params:
|
|
||||||
args.update(param)
|
|
||||||
|
|
||||||
|
|
||||||
###
|
|
||||||
db = Database(env["db_name"], env["db_path"])
|
|
||||||
db.setup()
|
|
||||||
|
|
||||||
searchargs = []
|
|
||||||
|
|
||||||
if args.get("type"):
|
|
||||||
searchargs.append(Structure.type == args["type"])
|
|
||||||
|
|
||||||
if args.get("direction"):
|
|
||||||
searchargs.append(Structure.direction == str(args["direction"]))
|
|
||||||
|
|
||||||
if args.get("theta"):
|
|
||||||
searchargs.append(Structure.theta == args["theta"])
|
|
||||||
|
|
||||||
result = db.search(searchargs)
|
|
||||||
result.sort(key = lambda src: f"{ src['type'] }{ src['direction'] }{ src['theta'] }")
|
|
||||||
|
|
||||||
df = DataFrame(result)
|
|
||||||
df_keys = [ key for key in df.keys() ]
|
|
||||||
|
|
||||||
if printlist:
|
|
||||||
click.echo("Avaliable fields for query:")
|
|
||||||
click.echo("\t{}".format("\n\t".join(df_keys)))
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
if not result:
|
|
||||||
click.echo("Empty result.")
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
tables = []
|
|
||||||
|
|
||||||
if fields:
|
|
||||||
for fieldslist in fields:
|
|
||||||
for field in fieldslist:
|
|
||||||
if field not in df_keys:
|
|
||||||
click.echo(f"Unknown field '{ field }'. Try to use '--list' flag to see all avaliable fields.")
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
tables.append(df[fieldslist])
|
|
||||||
else:
|
|
||||||
tables.append(df)
|
|
||||||
|
|
||||||
if output == "plot":
|
|
||||||
fig, ax = plt.subplots(nrows = 1, ncols = 1)
|
|
||||||
|
|
||||||
for table in tables:
|
|
||||||
table.plot(table.keys()[0], table.keys()[1], ax = ax, style = "o")
|
|
||||||
|
|
||||||
plt.legend()
|
|
||||||
plt.grid()
|
|
||||||
|
|
||||||
if export:
|
|
||||||
supported = ["csv", "jpg"]
|
|
||||||
filepath, ext = os.path.splitext(export)
|
|
||||||
ext = ext.replace(".", "")
|
|
||||||
|
|
||||||
if ext not in supported:
|
|
||||||
click.echo(f"Unknown extension '{ ext }'.")
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
if ext == "csv":
|
|
||||||
if len(tables) == 1:
|
|
||||||
tables[0].to_csv(export, sep = ";")
|
|
||||||
|
|
||||||
else:
|
|
||||||
for n, table in enumerate(tables):
|
|
||||||
table.to_csv("{}.{}.{}".format(filepath, n, ext), sep = ";")
|
|
||||||
|
|
||||||
elif ext == "jpg":
|
|
||||||
plt.savefig(export)
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
if output == "cli":
|
|
||||||
res = "\n\n".join([ table.to_string() for table in tables ])
|
|
||||||
click.echo(res)
|
|
||||||
|
|
||||||
elif output == "plot":
|
|
||||||
plt.show()
|
|
||||||
|
|
||||||
|
|
||||||
###
|
|
||||||
# CLI entry
|
|
||||||
##
|
|
||||||
if __name__ == "__main__":
|
|
||||||
#try:
|
|
||||||
anisotropy()
|
|
||||||
|
|
||||||
#except KeyboardInterrupt:
|
|
||||||
# click.echo("Interrupted!")
|
|
||||||
|
|
||||||
#finally:
|
|
||||||
# from anisotropy.salomepl.utils import SalomeManager
|
|
||||||
# click.echo("Exiting ...")
|
|
||||||
|
|
||||||
# if os.path.exists("anisotropy.pid"):
|
|
||||||
# os.remove("anisotropy.pid")
|
|
||||||
|
|
||||||
# SalomeManager().killall()
|
|
||||||
|
|
||||||
# sys.exit(0)
|
|
143
anisotropy/core/config.py
Normal file
143
anisotropy/core/config.py
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# This file is part of anisotropy.
|
||||||
|
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import toml
|
||||||
|
import copy
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
class Config(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.content = {}
|
||||||
|
self.options = {}
|
||||||
|
self.params = {}
|
||||||
|
self.cases = None
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.options[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self.options[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
del self.options[key]
|
||||||
|
|
||||||
|
def update(self, **kwargs):
|
||||||
|
self.options.update(**kwargs)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.options)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
for key in self.options:
|
||||||
|
yield key
|
||||||
|
|
||||||
|
def load(self, filename: str):
|
||||||
|
path = os.path.abspath(filename)
|
||||||
|
|
||||||
|
if not os.path.exists(path):
|
||||||
|
raise FileNotFoundError(path)
|
||||||
|
|
||||||
|
self.content = toml.load(path)
|
||||||
|
self.options = copy.deepcopy(self.content["options"])
|
||||||
|
self.content.pop("options")
|
||||||
|
|
||||||
|
def dump(self, filename: str):
|
||||||
|
path = os.path.abspath(filename)
|
||||||
|
ext = os.path.splitext(path)[1]
|
||||||
|
|
||||||
|
if not ext == ".toml":
|
||||||
|
path += ".toml"
|
||||||
|
|
||||||
|
os.makedirs(os.path.split(path)[0], exist_ok = True)
|
||||||
|
|
||||||
|
self.content.update(options = self.options)
|
||||||
|
|
||||||
|
with open(path, "w") as io:
|
||||||
|
toml.dump(self.content, io, encoder = toml.TomlNumpyEncoder())
|
||||||
|
|
||||||
|
def minimize(self):
|
||||||
|
self.content = None
|
||||||
|
self.cases = None
|
||||||
|
|
||||||
|
def purge(self):
|
||||||
|
self.minimize()
|
||||||
|
self.params = None
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return copy.deepcopy(self)
|
||||||
|
|
||||||
|
def expand(self):
|
||||||
|
self.cases = []
|
||||||
|
|
||||||
|
# Expand structures for each direction and each alpha
|
||||||
|
for structure in self.content["structures"]:
|
||||||
|
structure = copy.deepcopy(structure)
|
||||||
|
alphaA = np.round(np.arange(
|
||||||
|
structure["alpha"][0],
|
||||||
|
structure["alpha"][1] + structure["alphaStep"],
|
||||||
|
structure["alphaStep"]
|
||||||
|
), 9)
|
||||||
|
directionA = np.array(structure["directions"], dtype = float)
|
||||||
|
structure.pop("alpha")
|
||||||
|
structure.pop("directions")
|
||||||
|
|
||||||
|
for direction in directionA:
|
||||||
|
for alpha in alphaA:
|
||||||
|
self.cases.append({
|
||||||
|
"alpha": alpha,
|
||||||
|
"direction": direction,
|
||||||
|
**structure
|
||||||
|
})
|
||||||
|
|
||||||
|
def chooseParams(self, idn: int):
|
||||||
|
if len(self.cases) > 0:
|
||||||
|
self.params = self.cases[idn]
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise IndexError("list index out of range in cause of zero length of 'cases'")
|
||||||
|
|
||||||
|
|
||||||
|
def default_config() -> Config:
|
||||||
|
config = Config()
|
||||||
|
|
||||||
|
config.options = {
|
||||||
|
"nprocs": 1,
|
||||||
|
"stage": "all",
|
||||||
|
"overwrite": False,
|
||||||
|
"database": "anisotropy.db",
|
||||||
|
"build": "build",
|
||||||
|
"logs": "logs",
|
||||||
|
"shapefile": "shape.step",
|
||||||
|
"meshfile": "mesh.mesh"
|
||||||
|
}
|
||||||
|
|
||||||
|
config.content = {
|
||||||
|
"structures": []
|
||||||
|
}
|
||||||
|
|
||||||
|
labels = ["simple", "bodyCentered", "faceCentered"]
|
||||||
|
alphas = [[0.01, 0.28], [0.01, 0.17], [0.01, 0.13]]
|
||||||
|
|
||||||
|
for label, alpha in zip(labels, alphas):
|
||||||
|
config.content["structures"].append({
|
||||||
|
"label": label,
|
||||||
|
"alpha": alpha,
|
||||||
|
"alphaStep": 0.01,
|
||||||
|
"directions": [[1., 0., 0.], [0., 0., 1.], [1., 1., 1.]],
|
||||||
|
"r0": 1,
|
||||||
|
"filletsEnabled": True,
|
||||||
|
"pressureInlet": 1,
|
||||||
|
"pressureOutlet": 0,
|
||||||
|
"pressureInternal": 0,
|
||||||
|
"velocityInlet": [0., 0., 0.],
|
||||||
|
"velocityOutlet": None,
|
||||||
|
"velocityInternal": [0., 0., 0.],
|
||||||
|
"density": 1000,
|
||||||
|
"viscosity": 1e-3,
|
||||||
|
"scale": [1e-5, 1e-5, 1e-5]
|
||||||
|
})
|
||||||
|
|
||||||
|
return config
|
@ -1,405 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
import os
|
|
||||||
import logging
|
|
||||||
from copy import deepcopy
|
|
||||||
|
|
||||||
from anisotropy import env
|
|
||||||
from anisotropy.core.utils import setupLogger
|
|
||||||
from anisotropy.core.models import (
|
|
||||||
db, JOIN,
|
|
||||||
Structure,
|
|
||||||
Mesh, SubMesh, MeshResult,
|
|
||||||
Flow, FlowApproximation, FlowResult
|
|
||||||
)
|
|
||||||
from peewee import OperationalError
|
|
||||||
|
|
||||||
logger = logging.getLogger(env["logger_name"])
|
|
||||||
#setupLogger(logger, logging.INFO, env["LOG"])
|
|
||||||
|
|
||||||
# NOTE: Temporary solution for multiprocessing mode when database is locked
|
|
||||||
def tryUntilDone(func):
|
|
||||||
def inner(*args, **kwargs):
|
|
||||||
done = False
|
|
||||||
|
|
||||||
while not done:
|
|
||||||
try:
|
|
||||||
ret = func(*args, **kwargs)
|
|
||||||
done = True
|
|
||||||
|
|
||||||
except OperationalError as e:
|
|
||||||
logger.error(e)
|
|
||||||
|
|
||||||
return ret
|
|
||||||
|
|
||||||
return inner
|
|
||||||
|
|
||||||
|
|
||||||
class Database(object):
|
|
||||||
def __init__(self, name: str, filepath: str):
|
|
||||||
self.name = name
|
|
||||||
self.filepath = filepath
|
|
||||||
self.__db = db
|
|
||||||
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
os.makedirs(self.filepath, exist_ok = True)
|
|
||||||
|
|
||||||
fullpath = os.path.join(self.filepath, "{}.db".format(self.name))
|
|
||||||
self.__db.init(fullpath)
|
|
||||||
|
|
||||||
if not os.path.exists(fullpath):
|
|
||||||
self.__db.create_tables([
|
|
||||||
Structure,
|
|
||||||
Mesh,
|
|
||||||
SubMesh,
|
|
||||||
MeshResult,
|
|
||||||
Flow,
|
|
||||||
FlowApproximation,
|
|
||||||
FlowResult
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
def isempty(self) -> bool:
|
|
||||||
"""Checks DB for main table existence (Structure)
|
|
||||||
|
|
||||||
:return: True if exists
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
query = Structure.select()
|
|
||||||
|
|
||||||
return not query.exists()
|
|
||||||
|
|
||||||
|
|
||||||
def load(self, structure_type: str, structure_direction: list, structure_theta: float) -> dict:
|
|
||||||
structureQuery = (
|
|
||||||
Structure
|
|
||||||
.select()
|
|
||||||
.where(
|
|
||||||
Structure.type == structure_type,
|
|
||||||
Structure.direction == str(structure_direction),
|
|
||||||
Structure.theta == structure_theta
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
params = {}
|
|
||||||
|
|
||||||
with self.__db.atomic():
|
|
||||||
if structureQuery.exists():
|
|
||||||
params["structure"] = structureQuery.dicts().get()
|
|
||||||
|
|
||||||
meshQuery = structureQuery.get().meshes
|
|
||||||
|
|
||||||
if meshQuery.exists():
|
|
||||||
params["mesh"] = meshQuery.dicts().get()
|
|
||||||
|
|
||||||
submeshQuery = meshQuery.get().submeshes
|
|
||||||
|
|
||||||
if submeshQuery.exists():
|
|
||||||
params["submesh"] = [ entry for entry in submeshQuery.dicts() ]
|
|
||||||
|
|
||||||
meshresultQuery = meshQuery.get().meshresults
|
|
||||||
|
|
||||||
if meshresultQuery.exists():
|
|
||||||
params["meshresult"] = meshresultQuery.dicts().get()
|
|
||||||
|
|
||||||
flowQuery = structureQuery.get().flows
|
|
||||||
|
|
||||||
if flowQuery.exists():
|
|
||||||
params["flow"] = flowQuery.dicts().get()
|
|
||||||
|
|
||||||
flowapproximationQuery = flowQuery.get().flowapproximations
|
|
||||||
|
|
||||||
if flowapproximationQuery.exists():
|
|
||||||
params["flowapproximation"] = flowapproximationQuery.dicts().get()
|
|
||||||
|
|
||||||
flowresultsQuery = flowQuery.get().flowresults
|
|
||||||
|
|
||||||
if flowresultsQuery.exists():
|
|
||||||
params["flowresult"] = flowresultsQuery.dicts().get()
|
|
||||||
else:
|
|
||||||
logger.error("Missed Structure table")
|
|
||||||
|
|
||||||
return params
|
|
||||||
|
|
||||||
|
|
||||||
def loadGeneral(self, type: str = None, direction: list = None, theta: float = None) -> list:
|
|
||||||
query = (
|
|
||||||
Structure
|
|
||||||
.select()
|
|
||||||
.order_by(Structure.type, Structure.direction, Structure.theta)
|
|
||||||
)
|
|
||||||
response = []
|
|
||||||
|
|
||||||
if type:
|
|
||||||
query = query.where(Structure.type == type)
|
|
||||||
|
|
||||||
if direction:
|
|
||||||
query = query.where(Structure.direction == str(direction))
|
|
||||||
|
|
||||||
if theta:
|
|
||||||
query = query.where(Structure.theta == theta)
|
|
||||||
|
|
||||||
for entry in query.dicts():
|
|
||||||
response.append({ "structure": entry })
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def update(self, params: dict):
|
|
||||||
if not params:
|
|
||||||
logger.error("Trying to update db from empty parameters")
|
|
||||||
return
|
|
||||||
|
|
||||||
query = (
|
|
||||||
Structure
|
|
||||||
.select(Structure, Mesh, Flow)
|
|
||||||
.join(
|
|
||||||
Mesh,
|
|
||||||
JOIN.INNER,
|
|
||||||
on = (Mesh.structure_id == Structure.structure_id)
|
|
||||||
)
|
|
||||||
.join(
|
|
||||||
Flow,
|
|
||||||
JOIN.INNER,
|
|
||||||
on = (Flow.structure_id == Structure.structure_id)
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
Structure.type == params["structure"]["type"],
|
|
||||||
Structure.direction == str(params["structure"]["direction"]),
|
|
||||||
Structure.theta == params["structure"]["theta"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
structureID = tryUntilDone(self._updateStructure)(params.get("structure", {}), query)
|
|
||||||
|
|
||||||
meshID = tryUntilDone(self._updateMesh)(params.get("mesh", {}), query, structureID)
|
|
||||||
|
|
||||||
for submeshParams in params.get("submesh", []):
|
|
||||||
tryUntilDone(self._updateSubMesh)(submeshParams, query, meshID)
|
|
||||||
|
|
||||||
tryUntilDone(self._updateMeshResult)(params.get("meshresult", {}), query, meshID)
|
|
||||||
|
|
||||||
flowID = tryUntilDone(self._updateFlow)(params.get("flow", {}), query, structureID)
|
|
||||||
|
|
||||||
tryUntilDone(self._updateFlowApproximation)(params.get("flowapproximation", {}), query, flowID)
|
|
||||||
|
|
||||||
tryUntilDone(self._updateFlowResult)(params.get("flowresult", {}), query, flowID)
|
|
||||||
|
|
||||||
|
|
||||||
def search(self, args: list):
|
|
||||||
result = {}
|
|
||||||
query = (
|
|
||||||
Structure
|
|
||||||
.select(Structure, Mesh, SubMesh, MeshResult, Flow, FlowApproximation, FlowResult)
|
|
||||||
.join(
|
|
||||||
Mesh,
|
|
||||||
JOIN.INNER,
|
|
||||||
on = (Mesh.structure_id == Structure.structure_id)
|
|
||||||
)
|
|
||||||
.join(
|
|
||||||
SubMesh,
|
|
||||||
JOIN.INNER,
|
|
||||||
on = (SubMesh.mesh_id == Mesh.mesh_id)
|
|
||||||
)
|
|
||||||
.join(
|
|
||||||
MeshResult,
|
|
||||||
JOIN.INNER,
|
|
||||||
on = (MeshResult.mesh_id == Mesh.mesh_id)
|
|
||||||
)
|
|
||||||
.join(
|
|
||||||
Flow,
|
|
||||||
JOIN.INNER,
|
|
||||||
on = (Flow.structure_id == Structure.structure_id)
|
|
||||||
)
|
|
||||||
.join(
|
|
||||||
FlowApproximation,
|
|
||||||
JOIN.INNER,
|
|
||||||
on = (FlowApproximation.flow_id == Flow.flow_id)
|
|
||||||
)
|
|
||||||
.join(
|
|
||||||
FlowResult,
|
|
||||||
JOIN.INNER,
|
|
||||||
on = (FlowResult.flow_id == Flow.flow_id)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for arg in args:
|
|
||||||
query = query.where(arg)
|
|
||||||
|
|
||||||
|
|
||||||
with self.__db.atomic():
|
|
||||||
if not self.isempty():
|
|
||||||
result = [ entry for entry in query.dicts() ]
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.error("Missed Structure table")
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def _updateStructure(self, src: dict, queryMain) -> int:
|
|
||||||
raw = deepcopy(src)
|
|
||||||
|
|
||||||
with self.__db.atomic():
|
|
||||||
if not queryMain.exists():
|
|
||||||
tabID = Structure.create(**raw)
|
|
||||||
|
|
||||||
else:
|
|
||||||
req = queryMain.dicts().get()
|
|
||||||
tabID = req["structure_id"]
|
|
||||||
|
|
||||||
query = (
|
|
||||||
Structure.update(**raw)
|
|
||||||
.where(
|
|
||||||
Structure.type == req["type"],
|
|
||||||
Structure.direction == str(req["direction"]),
|
|
||||||
Structure.theta == req["theta"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
query.execute()
|
|
||||||
|
|
||||||
return tabID
|
|
||||||
|
|
||||||
def _updateMesh(self, src: dict, queryMain, structureID) -> int:
|
|
||||||
raw = deepcopy(src)
|
|
||||||
|
|
||||||
with self.__db.atomic():
|
|
||||||
if not queryMain.exists():
|
|
||||||
tabID = Mesh.create(
|
|
||||||
structure_id = structureID,
|
|
||||||
**raw
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
req = queryMain.dicts().get()
|
|
||||||
tabID = req["mesh_id"]
|
|
||||||
|
|
||||||
query = (
|
|
||||||
Mesh.update(**raw)
|
|
||||||
.where(
|
|
||||||
Mesh.structure_id == structureID
|
|
||||||
)
|
|
||||||
)
|
|
||||||
query.execute()
|
|
||||||
|
|
||||||
return tabID
|
|
||||||
|
|
||||||
def _updateSubMesh(self, src: dict, queryMain, meshID):
|
|
||||||
#if not src:
|
|
||||||
# return
|
|
||||||
|
|
||||||
raw = deepcopy(src)
|
|
||||||
|
|
||||||
with self.__db.atomic():
|
|
||||||
if not SubMesh.select().where(SubMesh.mesh_id == meshID).exists():
|
|
||||||
tabID = SubMesh.create(
|
|
||||||
mesh_id = meshID,
|
|
||||||
**raw
|
|
||||||
)
|
|
||||||
logger.debug(f"[ DB ] Created SubMesh entry { tabID }")
|
|
||||||
|
|
||||||
else:
|
|
||||||
query = (
|
|
||||||
SubMesh.update(**raw)
|
|
||||||
.where(
|
|
||||||
SubMesh.mesh_id == meshID,
|
|
||||||
SubMesh.name == src["name"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
query.execute()
|
|
||||||
|
|
||||||
def _updateMeshResult(self, src: dict, queryMain, meshID):
|
|
||||||
#if not src:
|
|
||||||
# return
|
|
||||||
|
|
||||||
raw = deepcopy(src)
|
|
||||||
|
|
||||||
with self.__db.atomic():
|
|
||||||
if not MeshResult.select().where(MeshResult.mesh_id == meshID).exists():
|
|
||||||
tabID = MeshResult.create(
|
|
||||||
mesh_id = meshID,
|
|
||||||
**raw
|
|
||||||
)
|
|
||||||
logger.debug(f"[ DB ] Created MeshResult entry { tabID }")
|
|
||||||
|
|
||||||
else:
|
|
||||||
query = (
|
|
||||||
MeshResult.update(**raw)
|
|
||||||
.where(
|
|
||||||
MeshResult.mesh_id == meshID
|
|
||||||
)
|
|
||||||
)
|
|
||||||
query.execute()
|
|
||||||
|
|
||||||
def _updateFlow(self, src: dict, queryMain, structureID):
|
|
||||||
raw = deepcopy(src)
|
|
||||||
|
|
||||||
with self.__db.atomic():
|
|
||||||
if not queryMain.exists():
|
|
||||||
tabID = Flow.create(
|
|
||||||
structure_id = structureID,
|
|
||||||
**raw
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
req = queryMain.dicts().get()
|
|
||||||
tabID = req["flow_id"]
|
|
||||||
|
|
||||||
query = (
|
|
||||||
Flow.update(**raw)
|
|
||||||
.where(
|
|
||||||
Flow.structure_id == structureID
|
|
||||||
)
|
|
||||||
)
|
|
||||||
query.execute()
|
|
||||||
|
|
||||||
return tabID
|
|
||||||
|
|
||||||
def _updateFlowApproximation(self, src: dict, queryMain, flowID):
|
|
||||||
#if not src:
|
|
||||||
# return
|
|
||||||
|
|
||||||
raw = deepcopy(src)
|
|
||||||
|
|
||||||
with self.__db.atomic():
|
|
||||||
if not FlowApproximation.select().where(FlowApproximation.flow_id == flowID).exists():
|
|
||||||
tabID = FlowApproximation.create(
|
|
||||||
flow_id = flowID,
|
|
||||||
**raw
|
|
||||||
)
|
|
||||||
logger.debug(f"[ DB ] Created FlowApproximation entry { tabID }")
|
|
||||||
|
|
||||||
else:
|
|
||||||
query = (
|
|
||||||
FlowApproximation.update(**raw)
|
|
||||||
.where(
|
|
||||||
FlowApproximation.flow_id == flowID
|
|
||||||
)
|
|
||||||
)
|
|
||||||
query.execute()
|
|
||||||
|
|
||||||
def _updateFlowResult(self, src: dict, queryMain, flowID):
|
|
||||||
#if not src:
|
|
||||||
# return
|
|
||||||
|
|
||||||
raw = deepcopy(src)
|
|
||||||
|
|
||||||
with self.__db.atomic():
|
|
||||||
if not FlowResult.select().where(FlowResult.flow_id == flowID).exists():
|
|
||||||
tabID = FlowResult.create(
|
|
||||||
flow_id = flowID,
|
|
||||||
**raw
|
|
||||||
)
|
|
||||||
logger.debug(f"[ DB ] Created FlowResult entry { tabID }")
|
|
||||||
|
|
||||||
else:
|
|
||||||
query = (
|
|
||||||
FlowResult.update(**raw)
|
|
||||||
.where(
|
|
||||||
FlowResult.flow_id == flowID
|
|
||||||
)
|
|
||||||
)
|
|
||||||
query.execute()
|
|
@ -1,580 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
import os, sys
|
|
||||||
import time
|
|
||||||
from datetime import timedelta, datetime
|
|
||||||
import shutil
|
|
||||||
import logging
|
|
||||||
from copy import deepcopy
|
|
||||||
from math import sqrt
|
|
||||||
|
|
||||||
import toml
|
|
||||||
|
|
||||||
from anisotropy import (
|
|
||||||
__version__, env,
|
|
||||||
openfoam
|
|
||||||
)
|
|
||||||
from anisotropy.core.utils import setupLogger, Timer
|
|
||||||
from anisotropy.core.database import Database
|
|
||||||
from anisotropy import salomepl
|
|
||||||
import anisotropy.salomepl.utils
|
|
||||||
import anisotropy.salomepl.geometry
|
|
||||||
import anisotropy.salomepl.mesh
|
|
||||||
from anisotropy.samples import Simple, FaceCentered, BodyCentered
|
|
||||||
|
|
||||||
logger = logging.getLogger(env["logger_name"])
|
|
||||||
#setupLogger(logger, logging.INFO, env["LOG"])
|
|
||||||
|
|
||||||
#peeweeLogger = logging.getLogger("peewee")
|
|
||||||
#peeweeLogger.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
|
|
||||||
class Anisotropy(object):
|
|
||||||
"""Ultimate class that organizes whole working process"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
"""Constructor method"""
|
|
||||||
|
|
||||||
self.env = env
|
|
||||||
self.db = None #Database(self.env["db_name"], self.env["db_path"])
|
|
||||||
self.params = []
|
|
||||||
|
|
||||||
|
|
||||||
def load(self, structure_type: str, structure_direction: list, structure_theta: float):
|
|
||||||
"""Shortcut for `Database.setup` and `Database.load`.
|
|
||||||
|
|
||||||
See :class:`anisotropy.core.database.Database` for more details.
|
|
||||||
"""
|
|
||||||
self.db.setup()
|
|
||||||
self.params = self.db.load(structure_type, structure_direction, structure_theta)
|
|
||||||
|
|
||||||
def update(self, params: dict = None):
|
|
||||||
"""Shortcut for `Database.setup` and `Database.update`.
|
|
||||||
|
|
||||||
See :class:`anisotropy.core.database.Database` for more details.
|
|
||||||
"""
|
|
||||||
self.db.setup()
|
|
||||||
self.db.update(self.params if not params else params)
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def version():
|
|
||||||
"""Returns versions of all used main programs
|
|
||||||
|
|
||||||
:return:
|
|
||||||
Versions joined by next line symbol
|
|
||||||
"""
|
|
||||||
versions = {
|
|
||||||
"anisotropy": __version__,
|
|
||||||
"Python": sys.version.split(" ")[0],
|
|
||||||
"Salome": "[missed]",
|
|
||||||
"OpenFOAM": "[missed]"
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
versions["Salome"] = salomepl.utils.SalomeManager().version()
|
|
||||||
versions["OpenFOAM"] = openfoam.version()
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return "\n".join([ f"{ k }: { v }" for k, v in versions.items() ])
|
|
||||||
|
|
||||||
|
|
||||||
def loadFromScratch(self, configpath: str = None) -> list:
|
|
||||||
"""Loads parameters from configuration file and expands special values
|
|
||||||
|
|
||||||
:return:
|
|
||||||
List of dicts with parameters
|
|
||||||
"""
|
|
||||||
config = configpath or self.env["CONFIG"]
|
|
||||||
|
|
||||||
if not os.path.exists(config):
|
|
||||||
logger.error("Missed configuration file")
|
|
||||||
return
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.info(f"Configuration file: { config }")
|
|
||||||
|
|
||||||
buf = toml.load(config).get("structures")
|
|
||||||
paramsAll = []
|
|
||||||
|
|
||||||
|
|
||||||
for entry in buf:
|
|
||||||
# Shortcuts
|
|
||||||
_theta = entry["structure"]["theta"]
|
|
||||||
thetaMin = int(_theta[0] / _theta[2])
|
|
||||||
thetaMax = int(_theta[1] / _theta[2]) + 1
|
|
||||||
thetaList = list(
|
|
||||||
map(lambda n: n * _theta[2], range(thetaMin, thetaMax))
|
|
||||||
)
|
|
||||||
|
|
||||||
_thickness = entry["mesh"]["thickness"]
|
|
||||||
count = len(thetaList)
|
|
||||||
thicknessList = list(
|
|
||||||
map(lambda n: _thickness[0] + n * (_thickness[1] - _thickness[0]) / (count - 1), range(0, count))
|
|
||||||
)
|
|
||||||
|
|
||||||
for direction in entry["structure"]["directions"]:
|
|
||||||
for n, theta in enumerate(thetaList):
|
|
||||||
mesh = deepcopy(entry["mesh"])
|
|
||||||
mesh["thickness"] = thicknessList[n]
|
|
||||||
|
|
||||||
entryNew = {
|
|
||||||
"structure": dict(
|
|
||||||
type = entry["structure"]["type"],
|
|
||||||
theta = theta,
|
|
||||||
direction = [ float(num) for num in direction ],
|
|
||||||
filletsEnabled = entry["structure"]["filletsEnabled"]
|
|
||||||
),
|
|
||||||
"mesh": mesh,
|
|
||||||
"submesh": deepcopy(entry["submesh"]),
|
|
||||||
"meshresult": dict(),
|
|
||||||
"flow": deepcopy(entry["flow"]),
|
|
||||||
"flowapproximation": deepcopy(entry["flowapproximation"]),
|
|
||||||
"flowresult": dict(),
|
|
||||||
}
|
|
||||||
|
|
||||||
# For `type = fixedValue` only
|
|
||||||
_velocity = entryNew["flowapproximation"]["velocity"]["boundaryField"]["inlet"]["value"]
|
|
||||||
entryNew["flowapproximation"]["velocity"]["boundaryField"]["inlet"]["value"] = [
|
|
||||||
val * _velocity for val in entryNew["structure"]["direction"]
|
|
||||||
]
|
|
||||||
|
|
||||||
_velocity = entryNew["flow"]["velocity"]["boundaryField"]["inlet"]["value"]
|
|
||||||
entryNew["flow"]["velocity"]["boundaryField"]["inlet"]["value"] = [
|
|
||||||
val * _velocity for val in entryNew["structure"]["direction"]
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
paramsAll.append(entryNew)
|
|
||||||
|
|
||||||
return paramsAll
|
|
||||||
|
|
||||||
|
|
||||||
def evalParams(self):
|
|
||||||
"""Evals specific geometry(structure) parameters"""
|
|
||||||
|
|
||||||
structure = self.params.get("structure")
|
|
||||||
|
|
||||||
if not structure:
|
|
||||||
logger.error("Trying to eval empty parameters")
|
|
||||||
return
|
|
||||||
|
|
||||||
if structure["type"] == "simple":
|
|
||||||
thetaMin = 0.01
|
|
||||||
thetaMax = 0.28
|
|
||||||
|
|
||||||
r0 = 1
|
|
||||||
L = 2 * r0
|
|
||||||
radius = r0 / (1 - structure["theta"])
|
|
||||||
|
|
||||||
C1, C2 = 0.8, 0.5
|
|
||||||
Cf = C1 + (C2 - C1) / (thetaMax - thetaMin) * (structure["theta"] - thetaMin)
|
|
||||||
delta = 0.2
|
|
||||||
fillets = delta - Cf * (radius - r0)
|
|
||||||
|
|
||||||
elif structure["type"] == "faceCentered":
|
|
||||||
thetaMin = 0.01
|
|
||||||
thetaMax = 0.13
|
|
||||||
|
|
||||||
L = 1.0
|
|
||||||
r0 = L * sqrt(2) / 4
|
|
||||||
radius = r0 / (1 - structure["theta"])
|
|
||||||
|
|
||||||
C1, C2 = 0.3, 0.2
|
|
||||||
Cf = C1 + (C2 - C1) / (thetaMax - thetaMin) * (structure["theta"] - thetaMin)
|
|
||||||
delta = 0.012
|
|
||||||
fillets = delta - Cf * (radius - r0)
|
|
||||||
|
|
||||||
elif structure["type"] == "bodyCentered":
|
|
||||||
thetaMin = 0.01
|
|
||||||
thetaMax = 0.18
|
|
||||||
|
|
||||||
L = 1.0
|
|
||||||
r0 = L * sqrt(3) / 4
|
|
||||||
radius = r0 / (1 - structure["theta"])
|
|
||||||
|
|
||||||
C1, C2 = 0.3, 0.2
|
|
||||||
Cf = C1 + (C2 - C1) / (thetaMax - thetaMin) * (structure["theta"] - thetaMin)
|
|
||||||
delta = 0.02
|
|
||||||
fillets = delta - Cf * (radius - r0)
|
|
||||||
|
|
||||||
self.params["structure"].update(
|
|
||||||
L = L,
|
|
||||||
r0 = r0,
|
|
||||||
radius = radius,
|
|
||||||
fillets = fillets
|
|
||||||
)
|
|
||||||
|
|
||||||
def getCasePath(self, path: str = None) -> str:
|
|
||||||
"""Constructs case path from control parameters
|
|
||||||
|
|
||||||
:return: Absolute path to case
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
structure = self.params.get("structure")
|
|
||||||
|
|
||||||
if not structure:
|
|
||||||
logger.error("Trying to use empty parameters")
|
|
||||||
return
|
|
||||||
|
|
||||||
if path:
|
|
||||||
path = os.path.join(path, "build")
|
|
||||||
|
|
||||||
else:
|
|
||||||
path = self.env["BUILD"]
|
|
||||||
|
|
||||||
return os.path.join(
|
|
||||||
path,
|
|
||||||
structure["type"],
|
|
||||||
"direction-{}".format(str(structure['direction']).replace(" ", "")),
|
|
||||||
f"theta-{ structure['theta'] }"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def computeMesh(self, path):
|
|
||||||
"""Computes a mesh on shape via Salome
|
|
||||||
|
|
||||||
:return: Process output, error messages and returncode
|
|
||||||
:rtype: tuple(str, str, int)
|
|
||||||
"""
|
|
||||||
p = self.params["structure"]
|
|
||||||
scriptpath = os.path.join(self.env["ROOT"], "anisotropy/core/cli.py")
|
|
||||||
salomeargs = [
|
|
||||||
"computemesh",
|
|
||||||
p["type"],
|
|
||||||
p["direction"],
|
|
||||||
p["theta"],
|
|
||||||
path
|
|
||||||
]
|
|
||||||
manager = salomepl.utils.SalomeManager()
|
|
||||||
casepath = self.getCasePath(path)
|
|
||||||
|
|
||||||
self.params["meshresult"]["meshStatus"] = "Computing"
|
|
||||||
self.update()
|
|
||||||
timer = Timer()
|
|
||||||
|
|
||||||
out, err, returncode = manager.execute(
|
|
||||||
scriptpath,
|
|
||||||
*salomeargs,
|
|
||||||
timeout = self.env["salome_timeout"],
|
|
||||||
root = self.env["ROOT"],
|
|
||||||
logpath = casepath
|
|
||||||
)
|
|
||||||
self.load(p["type"], p["direction"], p["theta"])
|
|
||||||
|
|
||||||
if not returncode:
|
|
||||||
self.params["meshresult"].update(
|
|
||||||
meshStatus = "Done",
|
|
||||||
meshCalculationTime = timer.elapsed()
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.params["meshresult"].update(
|
|
||||||
meshStatus = "Failed"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
return out, err, returncode
|
|
||||||
|
|
||||||
|
|
||||||
def genmesh(self, path):
|
|
||||||
"""Computes a mesh on shape
|
|
||||||
|
|
||||||
Warning: Working only inside Salome Environment
|
|
||||||
"""
|
|
||||||
|
|
||||||
setupLogger(logger, logging.INFO, self.env["LOG"])
|
|
||||||
p = self.params
|
|
||||||
|
|
||||||
###
|
|
||||||
# Shape
|
|
||||||
##
|
|
||||||
logger.info("Constructing shape ...")
|
|
||||||
|
|
||||||
geompy = salomepl.geometry.getGeom()
|
|
||||||
structure = dict(
|
|
||||||
simple = Simple,
|
|
||||||
bodyCentered = BodyCentered,
|
|
||||||
faceCentered = FaceCentered
|
|
||||||
)[p["structure"]["type"]]
|
|
||||||
shapeGeometry = structure(**p["structure"])
|
|
||||||
shape, groups = shapeGeometry.build()
|
|
||||||
|
|
||||||
[length, surfaceArea, volume] = geompy.BasicProperties(shape, theTolerance = 1e-06)
|
|
||||||
|
|
||||||
|
|
||||||
###
|
|
||||||
# Mesh
|
|
||||||
##
|
|
||||||
logger.info("Prepairing mesh ...")
|
|
||||||
|
|
||||||
mp = p["mesh"]
|
|
||||||
|
|
||||||
lengths = [
|
|
||||||
geompy.BasicProperties(edge)[0] for edge in geompy.SubShapeAll(shape, geompy.ShapeType["EDGE"])
|
|
||||||
]
|
|
||||||
meanSize = sum(lengths) / len(lengths)
|
|
||||||
mp["maxSize"] = meanSize
|
|
||||||
mp["minSize"] = meanSize * 1e-1
|
|
||||||
mp["chordalError"] = mp["maxSize"] / 2
|
|
||||||
|
|
||||||
faces = []
|
|
||||||
for group in groups:
|
|
||||||
if group.GetName() in mp["facesToIgnore"]:
|
|
||||||
faces.append(group)
|
|
||||||
|
|
||||||
|
|
||||||
mesh = salomepl.mesh.Mesh(shape)
|
|
||||||
mesh.Tetrahedron(**mp)
|
|
||||||
|
|
||||||
if mp["viscousLayers"]:
|
|
||||||
mesh.ViscousLayers(**mp, faces = faces)
|
|
||||||
|
|
||||||
# Submesh
|
|
||||||
smp = p["submesh"]
|
|
||||||
|
|
||||||
for submesh in smp:
|
|
||||||
for group in groups:
|
|
||||||
if submesh["name"] == group.GetName():
|
|
||||||
subshape = group
|
|
||||||
|
|
||||||
submesh["maxSize"] = meanSize * 1e-1
|
|
||||||
submesh["minSize"] = meanSize * 1e-3
|
|
||||||
submesh["chordalError"] = submesh["minSize"] * 1e+1
|
|
||||||
|
|
||||||
mesh.Triangle(subshape, **submesh)
|
|
||||||
|
|
||||||
|
|
||||||
self.update()
|
|
||||||
logger.info("Computing mesh ...")
|
|
||||||
out, err, returncode = mesh.compute()
|
|
||||||
|
|
||||||
###
|
|
||||||
# Results
|
|
||||||
##
|
|
||||||
#p["meshresult"] = dict()
|
|
||||||
|
|
||||||
if not returncode:
|
|
||||||
mesh.removePyramids()
|
|
||||||
mesh.assignGroups()
|
|
||||||
|
|
||||||
casePath = self.getCasePath(path)
|
|
||||||
os.makedirs(casePath, exist_ok = True)
|
|
||||||
logger.info("Exporting mesh ...")
|
|
||||||
returncode, err = mesh.exportUNV(os.path.join(casePath, "mesh.unv"))
|
|
||||||
|
|
||||||
if returncode:
|
|
||||||
logger.error(err)
|
|
||||||
|
|
||||||
meshStats = mesh.stats()
|
|
||||||
p["meshresult"].update(
|
|
||||||
surfaceArea = surfaceArea,
|
|
||||||
volume = volume,
|
|
||||||
volumeCell = shapeGeometry.volumeCell,
|
|
||||||
**meshStats
|
|
||||||
)
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.error(err)
|
|
||||||
|
|
||||||
p["meshresult"].update(
|
|
||||||
surfaceArea = surfaceArea,
|
|
||||||
volume = volume,
|
|
||||||
volumeCell = shapeGeometry.volumeCell
|
|
||||||
)
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
|
|
||||||
def computeFlow(self, path):
|
|
||||||
"""Computes a flow on mesh via OpenFOAM
|
|
||||||
|
|
||||||
:return:
|
|
||||||
Process output, error messages and returncode
|
|
||||||
"""
|
|
||||||
###
|
|
||||||
# Case preparation
|
|
||||||
##
|
|
||||||
foamCase = [ "0", "constant", "system" ]
|
|
||||||
#self.params["flowresult"] = dict()
|
|
||||||
self.params["flowresult"]["flowStatus"] = "Computing"
|
|
||||||
self.update()
|
|
||||||
timer = Timer()
|
|
||||||
|
|
||||||
flow = self.params["flow"]
|
|
||||||
flowapproximation = self.params["flowapproximation"]
|
|
||||||
|
|
||||||
# ISSUE: ideasUnvToFoam cannot import mesh with '-case' flag so 'os.chdir' for that
|
|
||||||
casePath = self.getCasePath(path)
|
|
||||||
|
|
||||||
if not os.path.exists(casePath):
|
|
||||||
err = f"Cannot find case path { casePath }"
|
|
||||||
self.params["flowresult"]["flowStatus"] = "Failed"
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
return "", err, 1
|
|
||||||
|
|
||||||
os.chdir(casePath)
|
|
||||||
openfoam.foamClean()
|
|
||||||
|
|
||||||
for d in foamCase:
|
|
||||||
shutil.copytree(
|
|
||||||
os.path.join(self.env["openfoam_template"], d),
|
|
||||||
os.path.join(casePath, d)
|
|
||||||
)
|
|
||||||
|
|
||||||
###
|
|
||||||
# Mesh manipulations
|
|
||||||
##
|
|
||||||
if not os.path.exists("mesh.unv"):
|
|
||||||
os.chdir(path or self.env["ROOT"])
|
|
||||||
|
|
||||||
err = f"Missed 'mesh.unv'"
|
|
||||||
self.params["flowresult"]["flowStatus"] = "Failed"
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
return "", err, 1
|
|
||||||
|
|
||||||
out, err, returncode = openfoam.ideasUnvToFoam("mesh.unv")
|
|
||||||
|
|
||||||
if returncode:
|
|
||||||
os.chdir(path or self.env["ROOT"])
|
|
||||||
|
|
||||||
self.params["flowresult"]["flowStatus"] = "Failed"
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
return out, err, returncode
|
|
||||||
|
|
||||||
openfoam.createPatch(dictfile = "system/createPatchDict")
|
|
||||||
|
|
||||||
openfoam.foamDictionary(
|
|
||||||
"constant/polyMesh/boundary",
|
|
||||||
"entry0.defaultFaces.type",
|
|
||||||
"wall"
|
|
||||||
)
|
|
||||||
openfoam.foamDictionary(
|
|
||||||
"constant/polyMesh/boundary",
|
|
||||||
"entry0.defaultFaces.inGroups",
|
|
||||||
"1 (wall)"
|
|
||||||
)
|
|
||||||
|
|
||||||
out, err, returncode = openfoam.checkMesh()
|
|
||||||
|
|
||||||
if out: logger.warning(out)
|
|
||||||
|
|
||||||
openfoam.transformPoints(flow["scale"])
|
|
||||||
|
|
||||||
###
|
|
||||||
# Decomposition and initial approximation
|
|
||||||
#
|
|
||||||
# NOTE: Temporary without decomposition
|
|
||||||
##
|
|
||||||
openfoam.foamDictionary(
|
|
||||||
"constant/transportProperties",
|
|
||||||
"nu",
|
|
||||||
str(flow["transportProperties"]["nu"])
|
|
||||||
)
|
|
||||||
|
|
||||||
# openfoam.decomposePar()
|
|
||||||
|
|
||||||
openfoam.renumberMesh()
|
|
||||||
|
|
||||||
pressureBF = flowapproximation["pressure"]["boundaryField"]
|
|
||||||
velocityBF = flowapproximation["velocity"]["boundaryField"]
|
|
||||||
|
|
||||||
openfoam.foamDictionary(
|
|
||||||
"0/p",
|
|
||||||
"boundaryField.inlet.value",
|
|
||||||
openfoam.uniform(pressureBF["inlet"]["value"])
|
|
||||||
)
|
|
||||||
openfoam.foamDictionary(
|
|
||||||
"0/p",
|
|
||||||
"boundaryField.outlet.value",
|
|
||||||
openfoam.uniform(pressureBF["outlet"]["value"])
|
|
||||||
)
|
|
||||||
|
|
||||||
openfoam.foamDictionary(
|
|
||||||
"0/U",
|
|
||||||
"boundaryField.inlet.value",
|
|
||||||
openfoam.uniform(velocityBF["inlet"]["value"])
|
|
||||||
)
|
|
||||||
|
|
||||||
openfoam.potentialFoam()
|
|
||||||
|
|
||||||
###
|
|
||||||
# Main computation
|
|
||||||
##
|
|
||||||
pressureBF = flow["pressure"]["boundaryField"]
|
|
||||||
velocityBF = flow["velocity"]["boundaryField"]
|
|
||||||
|
|
||||||
openfoam.foamDictionary(
|
|
||||||
"0/U",
|
|
||||||
"boundaryField.inlet.type",
|
|
||||||
velocityBF["inlet"]["type"]
|
|
||||||
)
|
|
||||||
openfoam.foamDictionary(
|
|
||||||
"0/U",
|
|
||||||
"boundaryField.inlet.value",
|
|
||||||
openfoam.uniform(velocityBF["inlet"]["value"])
|
|
||||||
)
|
|
||||||
|
|
||||||
#for n in range(os.cpu_count()):
|
|
||||||
# openfoam.foamDictionary(
|
|
||||||
# f"processor{n}/0/U",
|
|
||||||
# "boundaryField.inlet.type",
|
|
||||||
# velocityBF.inlet.type
|
|
||||||
# )
|
|
||||||
# openfoam.foamDictionary(
|
|
||||||
# f"processor{n}/0/U",
|
|
||||||
# "boundaryField.inlet.value",
|
|
||||||
# openfoam.uniform(velocityBF.inlet.value[direction])
|
|
||||||
# )
|
|
||||||
|
|
||||||
out, err, returncode = openfoam.simpleFoam()
|
|
||||||
|
|
||||||
if not returncode:
|
|
||||||
self.params["flowresult"]["flowCalculationTime"] = timer.elapsed()
|
|
||||||
self.params["flowresult"]["flowStatus"] = "Done"
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.params["flowresult"]["flowStatus"] = "Failed"
|
|
||||||
|
|
||||||
self.update()
|
|
||||||
os.chdir(path or self.env["ROOT"])
|
|
||||||
|
|
||||||
return out, str(err, "utf-8"), returncode
|
|
||||||
|
|
||||||
|
|
||||||
def flowRate(self):
|
|
||||||
casePath = self.getCasePath()
|
|
||||||
foamPostProcessing = "postProcessing/flowRatePatch(name=outlet)/0/surfaceFieldValue.dat"
|
|
||||||
path = os.path.join(casePath, foamPostProcessing)
|
|
||||||
|
|
||||||
if not os.path.exists(path):
|
|
||||||
logger.warning(f"Unable to compute flow rate. Missed { path }")
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
with open(path, "r") as io:
|
|
||||||
lastLine = io.readlines()[-1]
|
|
||||||
flowRate = float(lastLine.replace(" ", "").replace("\n", "").split("\t")[1])
|
|
||||||
|
|
||||||
self.params["flowresult"]["flowRate"] = flowRate
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
return flowRate
|
|
||||||
|
|
||||||
|
|
||||||
def porosity(self):
|
|
||||||
mr = self.params["meshresult"]
|
|
||||||
fr = self.params["flowresult"]
|
|
||||||
|
|
||||||
fr["porosity"] = mr["volume"] / mr["volumeCell"]
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
return fr["porosity"]
|
|
@ -1,174 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
from peewee import (
|
|
||||||
SqliteDatabase, JOIN,
|
|
||||||
Model, Field,
|
|
||||||
AutoField, ForeignKeyField,
|
|
||||||
TextField, FloatField,
|
|
||||||
IntegerField, BooleanField,
|
|
||||||
TimeField
|
|
||||||
)
|
|
||||||
import json
|
|
||||||
|
|
||||||
db = SqliteDatabase(
|
|
||||||
None,
|
|
||||||
pragmas = { "foreign_keys": 1 },
|
|
||||||
field_types = { "list": "text" }
|
|
||||||
)
|
|
||||||
|
|
||||||
class BaseModel(Model):
|
|
||||||
class Meta:
|
|
||||||
database = db
|
|
||||||
|
|
||||||
|
|
||||||
class ListField(Field):
|
|
||||||
field_type = "list"
|
|
||||||
|
|
||||||
def db_value(self, value):
|
|
||||||
return str(value)
|
|
||||||
|
|
||||||
def python_value(self, value):
|
|
||||||
pval = []
|
|
||||||
|
|
||||||
for entry in value[1 : -1].split(","):
|
|
||||||
try:
|
|
||||||
pval.append(float(entry))
|
|
||||||
|
|
||||||
except:
|
|
||||||
pval.append(entry.strip().replace("'", ""))
|
|
||||||
|
|
||||||
return pval
|
|
||||||
|
|
||||||
|
|
||||||
class JSONField(TextField):
|
|
||||||
def db_value(self, value):
|
|
||||||
return json.dumps(value)
|
|
||||||
|
|
||||||
def python_value(self, value):
|
|
||||||
if value is not None:
|
|
||||||
return json.loads(value)
|
|
||||||
|
|
||||||
|
|
||||||
class Structure(BaseModel):
|
|
||||||
structure_id = AutoField()
|
|
||||||
|
|
||||||
type = TextField()
|
|
||||||
direction = ListField()
|
|
||||||
theta = FloatField()
|
|
||||||
|
|
||||||
r0 = FloatField(null = True)
|
|
||||||
L = FloatField(null = True)
|
|
||||||
radius = FloatField(null = True)
|
|
||||||
|
|
||||||
filletsEnabled = BooleanField(null = True)
|
|
||||||
fillets = FloatField(null = True)
|
|
||||||
#path = TextField()
|
|
||||||
|
|
||||||
|
|
||||||
class Mesh(BaseModel):
|
|
||||||
mesh_id = AutoField()
|
|
||||||
structure_id = ForeignKeyField(Structure, backref = "meshes")
|
|
||||||
|
|
||||||
maxSize = FloatField(null = True)
|
|
||||||
minSize = FloatField(null = True)
|
|
||||||
|
|
||||||
fineness = IntegerField(null = True)
|
|
||||||
growthRate = FloatField(null = True)
|
|
||||||
nbSegPerEdge = FloatField(null = True)
|
|
||||||
nbSegPerRadius = FloatField(null = True)
|
|
||||||
|
|
||||||
chordalErrorEnabled = BooleanField(null = True)
|
|
||||||
chordalError = FloatField(null = True)
|
|
||||||
|
|
||||||
secondOrder = BooleanField(null = True)
|
|
||||||
optimize = BooleanField(null = True)
|
|
||||||
quadAllowed = BooleanField(null = True)
|
|
||||||
useSurfaceCurvature = BooleanField(null = True)
|
|
||||||
fuseEdges = BooleanField(null = True)
|
|
||||||
checkChartBoundary = BooleanField(null = True)
|
|
||||||
|
|
||||||
viscousLayers = BooleanField(null = True)
|
|
||||||
thickness = FloatField(null = True)
|
|
||||||
numberOfLayers = IntegerField(null = True)
|
|
||||||
stretchFactor = FloatField(null = True)
|
|
||||||
isFacesToIgnore = BooleanField(null = True)
|
|
||||||
facesToIgnore = ListField(null = True)
|
|
||||||
#faces = []
|
|
||||||
extrusionMethod = TextField(null = True)
|
|
||||||
|
|
||||||
|
|
||||||
class SubMesh(BaseModel):
|
|
||||||
submesh_id = AutoField()
|
|
||||||
mesh_id = ForeignKeyField(Mesh, backref = "submeshes")
|
|
||||||
name = TextField()
|
|
||||||
|
|
||||||
maxSize = FloatField(null = True)
|
|
||||||
minSize = FloatField(null = True)
|
|
||||||
|
|
||||||
fineness = IntegerField(null = True)
|
|
||||||
growthRate = FloatField(null = True)
|
|
||||||
nbSegPerEdge = FloatField(null = True)
|
|
||||||
nbSegPerRadius = FloatField(null = True)
|
|
||||||
|
|
||||||
chordalErrorEnabled = BooleanField(null = True)
|
|
||||||
chordalError = FloatField(null = True)
|
|
||||||
|
|
||||||
secondOrder = BooleanField(null = True)
|
|
||||||
optimize = BooleanField(null = True)
|
|
||||||
quadAllowed = BooleanField(null = True)
|
|
||||||
useSurfaceCurvature = BooleanField(null = True)
|
|
||||||
fuseEdges = BooleanField(null = True)
|
|
||||||
checkChartBoundary = BooleanField(null = True)
|
|
||||||
|
|
||||||
|
|
||||||
class MeshResult(BaseModel):
|
|
||||||
meshresult_id = AutoField()
|
|
||||||
mesh_id = ForeignKeyField(Mesh, backref = "meshresults")
|
|
||||||
|
|
||||||
surfaceArea = FloatField(null = True)
|
|
||||||
volume = FloatField(null = True)
|
|
||||||
volumeCell = FloatField(null = True)
|
|
||||||
|
|
||||||
elements = IntegerField(null = True)
|
|
||||||
edges = IntegerField(null = True)
|
|
||||||
faces = IntegerField(null = True)
|
|
||||||
volumes = IntegerField(null = True)
|
|
||||||
tetrahedrons = IntegerField(null = True)
|
|
||||||
prisms = IntegerField(null = True)
|
|
||||||
pyramids = IntegerField(null = True)
|
|
||||||
|
|
||||||
meshStatus = TextField(null = True, default = "Idle")
|
|
||||||
meshCalculationTime = TimeField(null = True)
|
|
||||||
|
|
||||||
|
|
||||||
class Flow(BaseModel):
|
|
||||||
flow_id = AutoField()
|
|
||||||
structure_id = ForeignKeyField(Structure, backref = "flows")
|
|
||||||
|
|
||||||
scale = ListField(null = True)
|
|
||||||
pressure = JSONField(null = True)
|
|
||||||
velocity = JSONField(null = True)
|
|
||||||
transportProperties = JSONField(null = True)
|
|
||||||
|
|
||||||
|
|
||||||
class FlowApproximation(BaseModel):
|
|
||||||
flow_approximation_id = AutoField()
|
|
||||||
flow_id = ForeignKeyField(Flow, backref = "flowapproximations")
|
|
||||||
|
|
||||||
pressure = JSONField(null = True)
|
|
||||||
velocity = JSONField(null = True)
|
|
||||||
transportProperties = JSONField(null = True)
|
|
||||||
|
|
||||||
|
|
||||||
class FlowResult(BaseModel):
|
|
||||||
flowresult_id = AutoField()
|
|
||||||
flow_id = ForeignKeyField(Flow, backref = "flowresults")
|
|
||||||
|
|
||||||
flowRate = FloatField(null = True)
|
|
||||||
porosity = FloatField(null = True)
|
|
||||||
permeability = FloatField(null = True)
|
|
||||||
|
|
||||||
flowStatus = TextField(null = True, default = "Idle")
|
|
||||||
flowCalculationTime = TimeField(null = True)
|
|
64
anisotropy/core/parallel.py
Normal file
64
anisotropy/core/parallel.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import multiprocessing as mlp
|
||||||
|
import dill
|
||||||
|
|
||||||
|
|
||||||
|
class ParallelRunner(object):
|
||||||
|
def __init__(self, nprocs: int = 1, daemon: bool = True):
|
||||||
|
self.nprocs = nprocs
|
||||||
|
self.daemon = daemon
|
||||||
|
|
||||||
|
self.processes = []
|
||||||
|
self.queueInput = mlp.Queue(maxsize = 1)
|
||||||
|
self.queueOutput = mlp.Queue()
|
||||||
|
|
||||||
|
self.__pos = -1
|
||||||
|
self.output = []
|
||||||
|
|
||||||
|
def append(self, command, args = [], kwargs = {}):
|
||||||
|
self.__pos += 1
|
||||||
|
self.queueInput.put(dill.dumps((self.__pos, command, args, kwargs)))
|
||||||
|
|
||||||
|
def extend(self, commands: list, args: list = [], kwargs: list = []):
|
||||||
|
for command, cargs, ckwargs in zip(commands, args, kwargs):
|
||||||
|
self.append(command, cargs, ckwargs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def queueRelease(queueInput, queueOutput):
|
||||||
|
while True:
|
||||||
|
pos, command, args, kwargs = dill.loads(queueInput.get())
|
||||||
|
|
||||||
|
if pos is None or command is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
output = command(*args, **kwargs)
|
||||||
|
|
||||||
|
queueOutput.put((pos, output))
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
for n in range(self.nprocs):
|
||||||
|
self.processes.append(mlp.Process(
|
||||||
|
target = self.queueRelease,
|
||||||
|
args = (self.queueInput, self.queueOutput),
|
||||||
|
name = f"worker-{ n + 1 }"
|
||||||
|
))
|
||||||
|
|
||||||
|
for proc in self.processes:
|
||||||
|
proc.daemon = self.daemon
|
||||||
|
proc.start()
|
||||||
|
|
||||||
|
def wait(self):
|
||||||
|
for _ in range(self.nprocs):
|
||||||
|
self.append(None)
|
||||||
|
|
||||||
|
self.output = [ [] for _ in range(self.queueOutput.qsize()) ]
|
||||||
|
|
||||||
|
for _ in range(self.queueOutput.qsize()):
|
||||||
|
pos, output = self.queueOutput.get()
|
||||||
|
self.output[pos] = output
|
||||||
|
|
||||||
|
for proc in self.processes:
|
||||||
|
proc.join()
|
||||||
|
|
||||||
|
self.__pos = -1
|
97
anisotropy/core/postprocess.py
Normal file
97
anisotropy/core/postprocess.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import anisotropy.openfoam as of
|
||||||
|
|
||||||
|
|
||||||
|
def flowRate(patch: str, path: str = None):
|
||||||
|
func = f"patchFlowRate(patch={ patch })"
|
||||||
|
|
||||||
|
path = pathlib.Path(path or "").resolve()
|
||||||
|
outfile = path / "postProcessing" / func / "0/surfaceFieldValue.dat"
|
||||||
|
|
||||||
|
of.commands.postProcess(func, cwd = path, logpath = path / "patchFlowRate.log")
|
||||||
|
surfaceFieldValue = of.conversion.read_dat(outfile)["sum(phi)"][-1]
|
||||||
|
|
||||||
|
return surfaceFieldValue
|
||||||
|
|
||||||
|
|
||||||
|
def permeability(viscosity = None, flowRate = None, length = None, areaCross = None, pressureInlet = None, pressureOutlet = None, **kwargs):
|
||||||
|
viscosity = kwargs.get("viscosity", viscosity)
|
||||||
|
flowRate = kwargs.get("flowRate", flowRate)
|
||||||
|
length = kwargs.get("length", length)
|
||||||
|
areaCross = kwargs.get("areaCross", areaCross)
|
||||||
|
pressureInlet = kwargs.get("pressureInlet", pressureInlet)
|
||||||
|
pressureOutlet = kwargs.get("pressureOutlet", pressureOutlet)
|
||||||
|
|
||||||
|
return viscosity * length * flowRate / (areaCross * (pressureInlet - pressureOutlet))
|
||||||
|
|
||||||
|
|
||||||
|
def mean_nan(arr):
|
||||||
|
temp = arr.copy()
|
||||||
|
|
||||||
|
if np.isnan(temp[0]):
|
||||||
|
temp[0] = temp[1]
|
||||||
|
|
||||||
|
for n, item in enumerate(temp):
|
||||||
|
if np.all(np.isnan(item)):
|
||||||
|
|
||||||
|
vals = temp[n - 1 : n + 2]
|
||||||
|
|
||||||
|
if np.sum(~np.isnan(vals)) <= 1:
|
||||||
|
vals = temp[n - 2 : n + 3]
|
||||||
|
|
||||||
|
temp[n] = vals[~np.isnan(vals)].mean()
|
||||||
|
|
||||||
|
return temp
|
||||||
|
|
||||||
|
|
||||||
|
def filter_group(arr, nan = True, qhigh = True, quantile = 0.97):
|
||||||
|
temp = arr.copy()
|
||||||
|
check = True
|
||||||
|
quan = np.quantile(temp[~np.isnan(temp)], quantile)
|
||||||
|
limit = 1000
|
||||||
|
|
||||||
|
while check:
|
||||||
|
if nan and np.any(np.isnan(temp)):
|
||||||
|
temp = mean_nan(temp)
|
||||||
|
check = True
|
||||||
|
|
||||||
|
elif qhigh and np.any(quan < temp):
|
||||||
|
temp[quan < temp] = np.nan
|
||||||
|
check = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
check = False
|
||||||
|
|
||||||
|
if limit <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
else:
|
||||||
|
limit -= 1
|
||||||
|
|
||||||
|
return temp
|
||||||
|
|
||||||
|
|
||||||
|
def pad_nan(x, y):
|
||||||
|
return np.pad(y, (0, x.size - y.size), 'constant', constant_values = np.nan)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
def multiplot_y(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
labels = [],
|
||||||
|
lines = [],
|
||||||
|
axes_labels = ["", ""],
|
||||||
|
legend = True,
|
||||||
|
grid = True,
|
||||||
|
figsize = (12, 6)
|
||||||
|
):
|
||||||
|
fig, ax = plt.subplots(nrows = 1, ncols = 1, figsize = figsize)
|
||||||
|
|
||||||
|
for n, y_arr in enumerate(y):
|
||||||
|
ax.plot(x, y_arr, )
|
||||||
|
"""
|
388
anisotropy/core/runner.py
Normal file
388
anisotropy/core/runner.py
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from os import PathLike
|
||||||
|
|
||||||
|
from os import environ
|
||||||
|
from datetime import datetime
|
||||||
|
import pathlib
|
||||||
|
import logging
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from anisotropy.database import Database, tables
|
||||||
|
from anisotropy import shaping
|
||||||
|
from anisotropy import meshing
|
||||||
|
from anisotropy import solving
|
||||||
|
from anisotropy import openfoam
|
||||||
|
|
||||||
|
from . import config as core_config
|
||||||
|
from . import postprocess
|
||||||
|
from . import utils
|
||||||
|
from . import ParallelRunner
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class UltimateRunner(object):
|
||||||
|
def __init__(self, config = None, exec_id: int = None, _type: str = "master"):
|
||||||
|
|
||||||
|
self.type = _type
|
||||||
|
|
||||||
|
# Configuration file
|
||||||
|
self.config = config or core_config.default_config()
|
||||||
|
|
||||||
|
# Database preparation
|
||||||
|
self.database = Database(self.config["database"])
|
||||||
|
self.exec_id = None
|
||||||
|
|
||||||
|
if exec_id is not None:
|
||||||
|
if self.database.getExecution(int(exec_id)):
|
||||||
|
self.exec_id = int(exec_id)
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.warning(f"Execution id '{ exec_id }' not found. Creating new.")
|
||||||
|
|
||||||
|
if self.exec_id is None:
|
||||||
|
with self.database:
|
||||||
|
self.exec_id = tables.Execution.create(date = datetime.now()).exec_id
|
||||||
|
|
||||||
|
if self.type == "master":
|
||||||
|
logger.info(f"Current execution id: { self.exec_id }")
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
self.queue = []
|
||||||
|
|
||||||
|
def _query_database(self, tableName: str, to_dict: bool = False):
|
||||||
|
tableName = tableName.lower()
|
||||||
|
get_row = {
|
||||||
|
"shape": self.database.getShape,
|
||||||
|
"mesh": self.database.getMesh,
|
||||||
|
"flowonephase": self.database.getFlowOnephase
|
||||||
|
}[tableName]
|
||||||
|
# query
|
||||||
|
args = (
|
||||||
|
self.config.params["label"],
|
||||||
|
self.config.params["direction"],
|
||||||
|
self.config.params["alpha"],
|
||||||
|
self.exec_id,
|
||||||
|
to_dict
|
||||||
|
)
|
||||||
|
|
||||||
|
return get_row(*args)
|
||||||
|
|
||||||
|
def prepare_database(self):
|
||||||
|
# create a row in each table for the current case
|
||||||
|
# shape
|
||||||
|
with self.database:
|
||||||
|
shape = self.database.getShape(execution = self.exec_id, **self.config.params)
|
||||||
|
|
||||||
|
if not shape:
|
||||||
|
shape = tables.Shape(exec_id = self.exec_id, **self.config.params)
|
||||||
|
self.database.csave(shape)
|
||||||
|
|
||||||
|
# mesh
|
||||||
|
with self.database:
|
||||||
|
mesh = self.database.getMesh(execution = self.exec_id, **self.config.params)
|
||||||
|
|
||||||
|
if not mesh:
|
||||||
|
mesh = tables.Mesh(shape_id = shape.shape_id)
|
||||||
|
self.database.csave(mesh)
|
||||||
|
|
||||||
|
# onephase flow
|
||||||
|
with self.database:
|
||||||
|
flow = self.database.getFlowOnephase(execution = self.exec_id, **self.config.params)
|
||||||
|
|
||||||
|
if not flow:
|
||||||
|
flow = tables.FlowOnephase(mesh_id = mesh.mesh_id, **self.config.params)
|
||||||
|
self.database.csave(flow)
|
||||||
|
|
||||||
|
def prepare_queue(self):
|
||||||
|
self.config.expand()
|
||||||
|
logger.info(f"Preparing queue: { len(self.config.cases) }")
|
||||||
|
|
||||||
|
for idn, case in enumerate(self.config.cases):
|
||||||
|
config = self.config.copy()
|
||||||
|
config.chooseParams(idn)
|
||||||
|
config.minimize()
|
||||||
|
|
||||||
|
kwargs = {
|
||||||
|
"config": config,
|
||||||
|
"exec_id": self.exec_id
|
||||||
|
}
|
||||||
|
self.queue.append(kwargs)
|
||||||
|
|
||||||
|
def start(self, queue: list = None, nprocs: int = None):
|
||||||
|
nprocs = nprocs or self.config["nprocs"]
|
||||||
|
|
||||||
|
logger.info(f"Starting subprocesses: { nprocs }")
|
||||||
|
parallel = ParallelRunner(nprocs = nprocs)
|
||||||
|
parallel.start()
|
||||||
|
|
||||||
|
for kwargs in self.queue:
|
||||||
|
parallel.append(self.subrunner, kwargs = kwargs)
|
||||||
|
|
||||||
|
parallel.wait()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def casepath(self) -> PathLike:
|
||||||
|
params = self.config.params
|
||||||
|
|
||||||
|
path = pathlib.Path(environ["AP_CWD"], environ["AP_BUILD_DIR"])
|
||||||
|
path /= "execution-{}".format(self.exec_id)
|
||||||
|
path /= "{}-[{},{},{}]-{}".format(
|
||||||
|
params["label"],
|
||||||
|
*[ str(d) for d in params["direction"] ],
|
||||||
|
params["alpha"]
|
||||||
|
)
|
||||||
|
|
||||||
|
return path.resolve()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shapefile(self) -> PathLike:
|
||||||
|
return self.casepath / self.config["shapefile"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def meshfile(self) -> PathLike:
|
||||||
|
return self.casepath / self.config["meshfile"]
|
||||||
|
|
||||||
|
def compute_shape(self):
|
||||||
|
params = self.config.params
|
||||||
|
shapeParams = self._query_database("shape")
|
||||||
|
|
||||||
|
logger.info("Computing shape for {} with direction = {} and alpha = {}".format(
|
||||||
|
params["label"], params["direction"], params["alpha"]
|
||||||
|
))
|
||||||
|
timer = utils.Timer()
|
||||||
|
|
||||||
|
# check physical existence
|
||||||
|
if self.shapefile.exists() and self.config["overwrite"] is not True:
|
||||||
|
logger.info("Shape exists, skipping ...")
|
||||||
|
return
|
||||||
|
|
||||||
|
#
|
||||||
|
generate_shape = {
|
||||||
|
"simple": shaping.primitives.simple,
|
||||||
|
"bodyCentered": shaping.primitives.body_centered,
|
||||||
|
"faceCentered": shaping.primitives.face_centered
|
||||||
|
}[shapeParams.label]
|
||||||
|
|
||||||
|
try:
|
||||||
|
# generate shape
|
||||||
|
shape = generate_shape(
|
||||||
|
shapeParams.alpha,
|
||||||
|
shapeParams.direction,
|
||||||
|
r0 = shapeParams.r0,
|
||||||
|
filletsEnabled = shapeParams.filletsEnabled
|
||||||
|
)
|
||||||
|
|
||||||
|
# export
|
||||||
|
self.casepath.mkdir(parents = True, exist_ok = True)
|
||||||
|
|
||||||
|
shape.write(self.shapefile)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e, exc_info = True)
|
||||||
|
|
||||||
|
shapeParams.shapeStatus = "failed"
|
||||||
|
|
||||||
|
else:
|
||||||
|
shapeParams.shapeStatus = "done"
|
||||||
|
shapeParams.volume = shape.shape.volume * np.prod(params["scale"])
|
||||||
|
shapeParams.volumeCell = shape.cell.volume * np.prod(params["scale"])
|
||||||
|
shapeParams.radius = shape.radius
|
||||||
|
shapeParams.fillets = shape.fillets
|
||||||
|
shapeParams.porosity = shapeParams.volume / shapeParams.volumeCell
|
||||||
|
shapeParams.areaOutlet = np.sum([
|
||||||
|
face.mass for face in shape.shape.faces if face.name == "outlet"
|
||||||
|
]) * params["scale"][0] ** 2
|
||||||
|
shapeParams.length = shape.length * params["scale"][0]
|
||||||
|
shapeParams.areaCellOutlet = np.sum([
|
||||||
|
face.mass for face in shape.cell.faces if face.name == "outlet"
|
||||||
|
]) * params["scale"][0] ** 2
|
||||||
|
|
||||||
|
# commit parameters
|
||||||
|
shapeParams.shapeExecutionTime = timer.elapsed()
|
||||||
|
self.database.csave(shapeParams)
|
||||||
|
|
||||||
|
def compute_mesh(self):
|
||||||
|
params = self.config.params
|
||||||
|
meshParams = self._query_database("mesh")
|
||||||
|
|
||||||
|
logger.info("Computing mesh for {} with direction = {} and alpha = {}".format(
|
||||||
|
params["label"], params["direction"], params["alpha"]
|
||||||
|
))
|
||||||
|
timer = utils.Timer()
|
||||||
|
|
||||||
|
# check physical existence
|
||||||
|
if self.meshfile.exists() and self.config["overwrite"] is not True:
|
||||||
|
logger.info("Mesh exists, skipping ...")
|
||||||
|
return
|
||||||
|
|
||||||
|
elif not self.shapefile.exists():
|
||||||
|
logger.error("Cannot find shape file to build a mesh.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Shape
|
||||||
|
shape = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# load shape
|
||||||
|
shape = shaping.Shape().read(self.shapefile)
|
||||||
|
|
||||||
|
# generate mesh
|
||||||
|
parameters = meshing.MeshingParameters()
|
||||||
|
mesh = meshing.Mesh(shape)
|
||||||
|
mesh.generate(parameters)
|
||||||
|
|
||||||
|
# export
|
||||||
|
self.casepath.mkdir(parents = True, exist_ok = True)
|
||||||
|
|
||||||
|
mesh.write(self.meshfile)
|
||||||
|
mesh.write(self.casepath / "mesh.msh")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e, exc_info = True)
|
||||||
|
|
||||||
|
meshParams.meshStatus = "failed"
|
||||||
|
|
||||||
|
else:
|
||||||
|
meshParams.meshStatus = "done"
|
||||||
|
meshParams.faces = len(mesh.faces)
|
||||||
|
meshParams.volumes = len(mesh.volumes)
|
||||||
|
|
||||||
|
# commit parameters
|
||||||
|
meshParams.meshExecutionTime = timer.elapsed()
|
||||||
|
self.database.csave(meshParams)
|
||||||
|
|
||||||
|
def compute_flow(self):
|
||||||
|
params = self.config.params
|
||||||
|
flowParams = self._query_database("flowonephase")
|
||||||
|
|
||||||
|
logger.info("Computing flow for {} with direction = {} and alpha = {}".format(
|
||||||
|
params["label"], params["direction"], params["alpha"]
|
||||||
|
))
|
||||||
|
timer = utils.Timer()
|
||||||
|
|
||||||
|
# check physical existence
|
||||||
|
if openfoam.FoamCase(path = self.casepath).is_converged() and not self.config["overwrite"]:
|
||||||
|
logger.info("Solution exists. Skipping ...")
|
||||||
|
return
|
||||||
|
|
||||||
|
elif not self.shapefile.exists():
|
||||||
|
logger.error("Cannot find shape file to compute a solution.")
|
||||||
|
return
|
||||||
|
|
||||||
|
elif not self.meshfile.exists():
|
||||||
|
logger.error("Cannot find mesh file to compute a solution.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# precalculate some parameters
|
||||||
|
flowParams.viscosityKinematic = flowParams.viscosity / flowParams.density
|
||||||
|
self.database.csave(flowParams)
|
||||||
|
|
||||||
|
#
|
||||||
|
flowParamsDict = self._query_database("flowonephase", to_dict = True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# load shape
|
||||||
|
shape = shaping.Shape().read(self.shapefile)
|
||||||
|
|
||||||
|
#
|
||||||
|
flow = solving.FlowOnePhase(
|
||||||
|
path = self.casepath,
|
||||||
|
direction = params["direction"],
|
||||||
|
patches = shape.patches(group = True, shiftIndex = True, prefix = "patch"),
|
||||||
|
scale = params["scale"],
|
||||||
|
**flowParamsDict
|
||||||
|
)
|
||||||
|
|
||||||
|
# generate solution
|
||||||
|
flow.generate()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e, exc_info = True)
|
||||||
|
|
||||||
|
flowParams.flowStatus = "failed"
|
||||||
|
|
||||||
|
else:
|
||||||
|
flowParams.flowStatus = "done"
|
||||||
|
|
||||||
|
# commit parameters
|
||||||
|
flowParams.flowExecutionTime = timer.elapsed()
|
||||||
|
self.database.csave(flowParams)
|
||||||
|
|
||||||
|
def compute_postprocess(self):
|
||||||
|
params = self.config.params
|
||||||
|
flowParams = self._query_database("flowonephase")
|
||||||
|
|
||||||
|
logger.info("Computing post process for {} with direction = {} and alpha = {}".format(
|
||||||
|
params["label"], params["direction"], params["alpha"]
|
||||||
|
))
|
||||||
|
|
||||||
|
if flowParams.flowStatus == "done":
|
||||||
|
if flowParams.flowRate is None and self.config["overwrite"] is not True:
|
||||||
|
flowParams.flowRate = postprocess.flowRate("outlet", self.casepath)
|
||||||
|
|
||||||
|
self.database.csave(flowParams)
|
||||||
|
|
||||||
|
def pipeline(self, stage: str = None):
|
||||||
|
stage = stage or self.config["stage"]
|
||||||
|
stages = ["shape", "mesh", "flow", "postProcess", "all"]
|
||||||
|
|
||||||
|
if stage not in stages:
|
||||||
|
logger.error(f"Unknown stage '{ stage }'.")
|
||||||
|
return
|
||||||
|
|
||||||
|
for current in stages:
|
||||||
|
if current == "shape":
|
||||||
|
params = self._query_database("shape")
|
||||||
|
|
||||||
|
# check database entry
|
||||||
|
if params.shapeStatus == "done" and self.config["overwrite"] is not True:
|
||||||
|
logger.info("Successful shape entry exists, skipping ...")
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.compute_shape()
|
||||||
|
|
||||||
|
if current == "mesh":
|
||||||
|
params = self._query_database("mesh")
|
||||||
|
|
||||||
|
# check database entry
|
||||||
|
if params.meshStatus == "done" and self.config["overwrite"] is not True:
|
||||||
|
logger.info("Successful mesh entry exists, skipping ...")
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.compute_mesh()
|
||||||
|
|
||||||
|
if current == "flow":
|
||||||
|
params = self._query_database("flowonephase")
|
||||||
|
|
||||||
|
# check database entry
|
||||||
|
if params.flowStatus == "done" and self.config["overwrite"] is not True:
|
||||||
|
logger.info("Successful flow entry exists, skipping ...")
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.compute_flow()
|
||||||
|
|
||||||
|
if current == "postProcess":
|
||||||
|
params = self._query_database("flowonephase")
|
||||||
|
|
||||||
|
# check database entry
|
||||||
|
# if params.flowStatus == "done" and self.config["overwrite"] is not True:
|
||||||
|
# logger.info("Successful flow entry exists, skipping ...")
|
||||||
|
# continue
|
||||||
|
|
||||||
|
self.compute_postprocess()
|
||||||
|
|
||||||
|
if current == stage or current == "all":
|
||||||
|
break
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def subrunner(*args, **kwargs):
|
||||||
|
runner = UltimateRunner(
|
||||||
|
config = kwargs["config"],
|
||||||
|
exec_id = kwargs["exec_id"],
|
||||||
|
_type = "worker"
|
||||||
|
)
|
||||||
|
runner.prepare_database()
|
||||||
|
runner.pipeline()
|
@ -1,286 +1,113 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from multiprocessing import Queue, Process, cpu_count
|
|
||||||
import socket
|
|
||||||
import copy
|
|
||||||
import time
|
import time
|
||||||
from types import FunctionType
|
import contextlib
|
||||||
import os
|
|
||||||
|
|
||||||
class CustomFormatter(logging.Formatter):
|
class CustomFormatter(logging.Formatter):
|
||||||
def _getFormat(self, level: int):
|
def __init__(self, colors: bool = True):
|
||||||
|
self.colors = colors
|
||||||
|
|
||||||
|
def applyColors(self, level: int, info: str, msg: str):
|
||||||
grey = "\x1b[38;21m"
|
grey = "\x1b[38;21m"
|
||||||
yellow = "\x1b[33;21m"
|
yellow = "\x1b[33;21m"
|
||||||
red = "\x1b[31;21m"
|
red = "\x1b[31;21m"
|
||||||
bold_red = "\x1b[31;1m"
|
bold_red = "\x1b[31;1m"
|
||||||
reset = "\x1b[0m"
|
reset = "\x1b[0m"
|
||||||
format = "[ %(asctime)s ] [ %(processName)s ] [ %(levelname)s ] %(message)s"
|
|
||||||
|
|
||||||
formats = {
|
formats = {
|
||||||
logging.DEBUG: grey + format + reset,
|
logging.DEBUG: grey + info + reset + msg,
|
||||||
logging.INFO: grey + format + reset,
|
logging.INFO: grey + info + reset + msg,
|
||||||
logging.WARNING: yellow + format + reset,
|
logging.WARNING: yellow + info + reset + msg,
|
||||||
logging.ERROR: red + format + reset,
|
logging.ERROR: red + info + reset + msg,
|
||||||
logging.CRITICAL: bold_red + format + reset
|
logging.CRITICAL: bold_red + info + reset + msg
|
||||||
}
|
}
|
||||||
|
|
||||||
return formats.get(level)
|
return formats.get(level)
|
||||||
|
|
||||||
def format(self, record):
|
def format(self, record):
|
||||||
log_fmt = self._getFormat(record.levelno)
|
info = "[%(levelname)s %(processName)s %(asctime)s %(funcName)s]" # [ %(processName)s ]
|
||||||
time_fmt = "%H:%M:%S %d-%m-%y"
|
msg = " %(message)s"
|
||||||
|
|
||||||
|
if self.colors:
|
||||||
|
log_fmt = self.applyColors(record.levelno, info, msg)
|
||||||
|
|
||||||
|
else:
|
||||||
|
log_fmt = info + msg
|
||||||
|
|
||||||
|
time_fmt = "%d-%m-%y %H:%M:%S"
|
||||||
formatter = logging.Formatter(log_fmt, time_fmt)
|
formatter = logging.Formatter(log_fmt, time_fmt)
|
||||||
|
|
||||||
return formatter.format(record)
|
return formatter.format(record)
|
||||||
|
|
||||||
|
|
||||||
def setupLogger(logger, level: int, filepath: str = None):
|
def setupLogger(level: int, filepath: str = None):
|
||||||
"""Applies settings to logger
|
"""Applies settings to root logger
|
||||||
|
|
||||||
:param logger:
|
|
||||||
Instance of :class:`logging.Logger`
|
|
||||||
|
|
||||||
:param level:
|
:param level:
|
||||||
Logging level (logging.INFO, logging.WARNING, ..)
|
Logging level (logging.INFO, logging.WARNING, ..)
|
||||||
|
|
||||||
:param filepath:
|
:param filepath:
|
||||||
Path to directory
|
Path to directory
|
||||||
"""
|
"""
|
||||||
logger.handlers = []
|
|
||||||
logger.setLevel(level)
|
|
||||||
|
|
||||||
|
logging.addLevelName(logging.INFO, "II")
|
||||||
|
logging.addLevelName(logging.WARNING, "WW")
|
||||||
|
logging.addLevelName(logging.ERROR, "EE")
|
||||||
|
logging.addLevelName(logging.CRITICAL, "CC")
|
||||||
|
|
||||||
streamhandler = logging.StreamHandler()
|
streamhandler = logging.StreamHandler()
|
||||||
streamhandler.setLevel(level)
|
streamhandler.setLevel(level)
|
||||||
streamhandler.setFormatter(CustomFormatter())
|
streamhandler.setFormatter(CustomFormatter())
|
||||||
logger.addHandler(streamhandler)
|
logging.root.addHandler(streamhandler)
|
||||||
|
|
||||||
if filepath:
|
|
||||||
if not os.path.exists(filepath):
|
|
||||||
os.makedirs(filepath, exist_ok = True)
|
|
||||||
|
|
||||||
filehandler = logging.FileHandler(
|
|
||||||
os.path.join(filepath, "{}.log".format(logger.name))
|
|
||||||
)
|
|
||||||
filehandler.setLevel(level)
|
|
||||||
filehandler.setFormatter(CustomFormatter())
|
|
||||||
logger.addHandler(filehandler)
|
|
||||||
|
|
||||||
|
|
||||||
class struct:
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
if len(args) > 0:
|
|
||||||
if type(args[0]) == dict:
|
|
||||||
for (k, v) in args[0].items():
|
|
||||||
if type(v) == dict:
|
|
||||||
setattr(self, k, struct(v))
|
|
||||||
|
|
||||||
else:
|
|
||||||
setattr(self, k, v)
|
|
||||||
else:
|
|
||||||
self.__dict__.update(kwargs)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
for k in self.__dict__:
|
|
||||||
if type(getattr(self, k)) == struct:
|
|
||||||
yield k, dict(getattr(self, k))
|
|
||||||
|
|
||||||
else:
|
|
||||||
yield k, getattr(self, k)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
members = []
|
|
||||||
|
|
||||||
for key in self.__dict__.keys():
|
|
||||||
members.append(f"{ key } = ")
|
|
||||||
|
|
||||||
if type(self.__dict__[key]) == str:
|
|
||||||
members[len(members) - 1] += f"\"{ self.__dict__[key] }\""
|
|
||||||
|
|
||||||
else:
|
|
||||||
members[len(members) - 1] += f"{ self.__dict__[key] }"
|
|
||||||
|
|
||||||
return f"struct({', '.join(members)})"
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return str(self)
|
|
||||||
|
|
||||||
|
|
||||||
def deepupdate(target, src):
|
|
||||||
for k, v in src.items():
|
|
||||||
if isinstance(v, dict):
|
|
||||||
if not k in target:
|
|
||||||
target[k] = copy.deepcopy(v)
|
|
||||||
|
|
||||||
else:
|
|
||||||
deepupdate(target[k], v)
|
|
||||||
|
|
||||||
else:
|
|
||||||
target[k] = copy.copy(v)
|
|
||||||
|
|
||||||
def collapse(source, key = None, level = 0, sep = "_"):
|
|
||||||
if isinstance(source, dict) and source:
|
|
||||||
level = level + 1
|
|
||||||
res = {}
|
|
||||||
|
|
||||||
for k, v in source.items():
|
|
||||||
ret, lvl = collapse(v, k, level)
|
|
||||||
|
|
||||||
for kk,vv in ret.items():
|
|
||||||
if level == lvl:
|
|
||||||
newkey = k
|
|
||||||
|
|
||||||
else:
|
|
||||||
newkey = "{}{}{}".format(k, sep, kk)
|
|
||||||
|
|
||||||
res.update({ newkey: vv })
|
|
||||||
|
|
||||||
if level == 1:
|
|
||||||
return res
|
|
||||||
|
|
||||||
else:
|
|
||||||
return res, level
|
|
||||||
|
|
||||||
else:
|
|
||||||
return { key: source }, level
|
|
||||||
|
|
||||||
def expand(source, sep = "_"):
|
|
||||||
res = {}
|
|
||||||
|
|
||||||
for k, v in source.items():
|
|
||||||
if k.find(sep) == -1:
|
|
||||||
res.update({ k: v })
|
|
||||||
|
|
||||||
else:
|
|
||||||
keys = k.split(sep)
|
|
||||||
cur = res
|
|
||||||
|
|
||||||
for n, kk in enumerate(keys):
|
|
||||||
if not len(keys) == n + 1:
|
|
||||||
if not cur.get(kk):
|
|
||||||
cur.update({ kk: {} })
|
|
||||||
|
|
||||||
cur = cur[kk]
|
|
||||||
|
|
||||||
else:
|
|
||||||
cur[kk] = v
|
|
||||||
return res
|
|
||||||
|
|
||||||
#if os.path.exists(env["CONFIG"]):
|
|
||||||
# config = toml.load(env["CONFIG"])
|
|
||||||
|
|
||||||
# for restricted in ["ROOT", "BUILD", "LOG", "CONFIG"]:
|
|
||||||
# if config.get(restricted):
|
|
||||||
# config.pop(restricted)
|
|
||||||
|
|
||||||
# TODO: not working if custom config empty and etc
|
|
||||||
# for m, structure in enumerate(config["structures"]):
|
|
||||||
# for n, estructure in enumerate(env["structures"]):
|
|
||||||
# if estructure["name"] == structure["name"]:
|
|
||||||
# deepupdate(env["structures"][n], config["structures"][m])
|
|
||||||
|
|
||||||
# config.pop("structures")
|
|
||||||
# deepupdate(env, config)
|
|
||||||
|
|
||||||
def timer(func: FunctionType) -> (tuple, float):
|
|
||||||
"""(Decorator) Returns output of inner function and execution time
|
|
||||||
|
|
||||||
:param func: inner function
|
logging.root.setLevel(level)
|
||||||
:type func: FunctionType
|
|
||||||
|
if filepath:
|
||||||
|
filehandler = logging.FileHandler(filepath)
|
||||||
|
filehandler.setLevel(logging.INFO)
|
||||||
|
filehandler.setFormatter(CustomFormatter(colors = False))
|
||||||
|
|
||||||
:return: output, elapsed time
|
logging.root.addHandler(filehandler)
|
||||||
:rtype: tuple(tuple, float)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def inner(*args, **kwargs):
|
|
||||||
start = time.monotonic()
|
|
||||||
ret = func(*args, **kwargs)
|
|
||||||
elapsed = time.monotonic() - start
|
|
||||||
|
|
||||||
return ret, elapsed
|
|
||||||
|
|
||||||
return inner
|
|
||||||
|
|
||||||
|
|
||||||
class Timer(object):
|
class Timer(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.start = time.monotonic()
|
self.update()
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.start = time.monotonic()
|
||||||
|
|
||||||
def elapsed(self):
|
def elapsed(self):
|
||||||
return time.monotonic() - self.start
|
return time.monotonic() - self.start
|
||||||
|
|
||||||
|
|
||||||
def queue(cmd, qin, qout, *args):
|
class ErrorHandler(contextlib.AbstractContextManager):
|
||||||
|
def __init__(self):
|
||||||
while True:
|
self.error = ""
|
||||||
# Get item from the queue
|
self.returncode = 0
|
||||||
pos, var = qin.get()
|
self.traceback = None
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self, self.handle
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
if exc_type:
|
||||||
|
self.error = exc_value.args
|
||||||
|
self.returncode = 1
|
||||||
|
self.traceback = traceback
|
||||||
|
|
||||||
|
def handle(self, obj):
|
||||||
|
def inner(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
output = obj(*args, **kwargs)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.error = e.args
|
||||||
|
self.returncode = 1
|
||||||
|
|
||||||
|
else:
|
||||||
|
return output
|
||||||
|
|
||||||
# Exit point
|
return inner
|
||||||
if pos is None:
|
|
||||||
break
|
|
||||||
|
|
||||||
# Execute command
|
|
||||||
res = cmd(*var, *args)
|
|
||||||
|
|
||||||
# Put results to the queue
|
|
||||||
qout.put((pos, res))
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def parallel(np, var, cmd):
|
|
||||||
|
|
||||||
varcount = len(var)
|
|
||||||
|
|
||||||
processes = []
|
|
||||||
nprocs = np if np <= cpu_count() else cpu_count()
|
|
||||||
|
|
||||||
qin = Queue(1)
|
|
||||||
qout = Queue()
|
|
||||||
|
|
||||||
# Create processes
|
|
||||||
for n in range(nprocs):
|
|
||||||
pargs = [cmd, qin, qout]
|
|
||||||
|
|
||||||
p = Process(target = queue, args = tuple(pargs))
|
|
||||||
|
|
||||||
processes.append(p)
|
|
||||||
|
|
||||||
# Start processes
|
|
||||||
for p in processes:
|
|
||||||
p.daemon = True
|
|
||||||
p.start()
|
|
||||||
|
|
||||||
# Fill queue
|
|
||||||
for n in range(varcount):
|
|
||||||
qin.put((n, var[n]))
|
|
||||||
|
|
||||||
for _ in range(nprocs):
|
|
||||||
qin.put((None, None))
|
|
||||||
|
|
||||||
# Get results
|
|
||||||
results = [[] for n in range(varcount)]
|
|
||||||
|
|
||||||
for n in range(varcount):
|
|
||||||
index, res = qout.get()
|
|
||||||
|
|
||||||
results[index] = res
|
|
||||||
|
|
||||||
# Wait until each processor has finished
|
|
||||||
for p in processes:
|
|
||||||
p.join()
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
def portIsFree(address, port):
|
|
||||||
|
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
||||||
return s.connect_ex((address, port)) == 0
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
13
anisotropy/database/__init__.py
Normal file
13
anisotropy/database/__init__.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
from . import tables
|
||||||
|
|
||||||
|
from .database import Database
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"utils",
|
||||||
|
"tables",
|
||||||
|
"Database"
|
||||||
|
]
|
309
anisotropy/database/database.py
Normal file
309
anisotropy/database/database.py
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
from numpy import ndarray
|
||||||
|
|
||||||
|
import peewee as pw
|
||||||
|
import pathlib
|
||||||
|
import time
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from . import tables
|
||||||
|
|
||||||
|
|
||||||
|
class Database(pw.SqliteDatabase):
|
||||||
|
def __init__(self, filename: str = None, *args, **kwargs):
|
||||||
|
"""A Database object contains SQLite database with convient
|
||||||
|
properties and methods
|
||||||
|
"""
|
||||||
|
self.path = filename
|
||||||
|
self.pragmas_ = kwargs.get("pragmas", { "foreign_keys": 1, "journal_mode": "wal" })
|
||||||
|
self.field_types_ = kwargs.get("field_types", { "list": "text" })
|
||||||
|
self.autoconnect_ = kwargs.get("autoconnect", False)
|
||||||
|
|
||||||
|
pw.SqliteDatabase.__init__(
|
||||||
|
self,
|
||||||
|
None,
|
||||||
|
pragmas = self.pragmas_,
|
||||||
|
field_types = self.field_types_,
|
||||||
|
autoconnect = self.autoconnect_
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.path:
|
||||||
|
self.setup()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tables(self) -> list:
|
||||||
|
"""Return all tables as list.
|
||||||
|
"""
|
||||||
|
return [ tables.__dict__[table] for table in tables.__all__ ]
|
||||||
|
|
||||||
|
def setup(self, filename: str = None):
|
||||||
|
"""Initialize database and create tables.
|
||||||
|
|
||||||
|
:param filename:
|
||||||
|
Path to the file.
|
||||||
|
:return:
|
||||||
|
Self.
|
||||||
|
"""
|
||||||
|
self.path = pathlib.Path(filename or self.path).resolve()
|
||||||
|
self.init(
|
||||||
|
self.path,
|
||||||
|
pragmas = self.pragmas_
|
||||||
|
)
|
||||||
|
tables.database_proxy.initialize(self)
|
||||||
|
|
||||||
|
with self:
|
||||||
|
self.create_tables(self.tables)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def csave(self, table: pw.Model, tries: int = 5000):
|
||||||
|
"""Try to save data from model to the database ignoring
|
||||||
|
peewee.OperationalError. Usefull for concurrent processes.
|
||||||
|
|
||||||
|
:param table:
|
||||||
|
Table to save.
|
||||||
|
:param tries:
|
||||||
|
Number of tries. Falling to sleep for 1 second if database
|
||||||
|
is locked.
|
||||||
|
"""
|
||||||
|
while tries >= 0:
|
||||||
|
if self.is_closed():
|
||||||
|
self.connect()
|
||||||
|
|
||||||
|
try:
|
||||||
|
table.save()
|
||||||
|
|
||||||
|
except pw.OperationalError:
|
||||||
|
tries -= 1
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.close()
|
||||||
|
break
|
||||||
|
|
||||||
|
def getExecution(
|
||||||
|
self,
|
||||||
|
idn: int,
|
||||||
|
to_dict: bool = False,
|
||||||
|
**kwargs
|
||||||
|
) -> tables.Execution | None:
|
||||||
|
"""Get execution entry from database.
|
||||||
|
|
||||||
|
:param idn:
|
||||||
|
Index of the execution.
|
||||||
|
:return:
|
||||||
|
If entry is found returns Model instance else None.
|
||||||
|
"""
|
||||||
|
query = tables.Execution.select().where(tables.Execution.exec_id == idn)
|
||||||
|
|
||||||
|
with self:
|
||||||
|
if to_dict:
|
||||||
|
table = query.dicts().get() if query.exists() else None
|
||||||
|
|
||||||
|
else:
|
||||||
|
table = query.get() if query.exists() else None
|
||||||
|
|
||||||
|
return table
|
||||||
|
|
||||||
|
def getLatest(self) -> tables.Execution | None:
|
||||||
|
"""Get latest execution entry from database.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
If entry is found returns Model instance else None.
|
||||||
|
"""
|
||||||
|
query = tables.Execution.select()
|
||||||
|
|
||||||
|
with self:
|
||||||
|
table = query[-1] if query.exists() else None
|
||||||
|
|
||||||
|
return table
|
||||||
|
|
||||||
|
def getShape(
|
||||||
|
self,
|
||||||
|
label: str = None,
|
||||||
|
direction: list[float] | ndarray = None,
|
||||||
|
alpha: float = None,
|
||||||
|
execution: int = None,
|
||||||
|
to_dict: bool = False,
|
||||||
|
**kwargs
|
||||||
|
) -> tables.Shape | None:
|
||||||
|
"""Get shape entry from database.
|
||||||
|
|
||||||
|
:param label:
|
||||||
|
Label of the shape.
|
||||||
|
:param direction:
|
||||||
|
Array of floats represents direction vector.
|
||||||
|
:param alpha:
|
||||||
|
Spheres overlap parameter.
|
||||||
|
:param execution:
|
||||||
|
Index of the execution. If None, use latest.
|
||||||
|
:return:
|
||||||
|
If entry is found returns Model instance else None.
|
||||||
|
"""
|
||||||
|
execution = execution or self.getLatest()
|
||||||
|
query = (
|
||||||
|
tables.Shape
|
||||||
|
.select()
|
||||||
|
.join(tables.Execution, pw.JOIN.LEFT_OUTER)
|
||||||
|
.where(
|
||||||
|
tables.Execution.exec_id == execution,
|
||||||
|
tables.Shape.label == label,
|
||||||
|
tables.Shape.direction == direction,
|
||||||
|
tables.Shape.alpha == alpha
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
with self:
|
||||||
|
if to_dict:
|
||||||
|
table = query.dicts().get() if query.exists() else None
|
||||||
|
|
||||||
|
else:
|
||||||
|
table = query.get() if query.exists() else None
|
||||||
|
|
||||||
|
return table
|
||||||
|
|
||||||
|
def getMesh(
|
||||||
|
self,
|
||||||
|
label: str = None,
|
||||||
|
direction: list[float] | ndarray = None,
|
||||||
|
alpha: float = None,
|
||||||
|
execution: int = None,
|
||||||
|
to_dict: bool = False,
|
||||||
|
**kwargs
|
||||||
|
) -> tables.Mesh | None:
|
||||||
|
"""Get mesh entry from database.
|
||||||
|
|
||||||
|
:param label:
|
||||||
|
Label of the shape.
|
||||||
|
:param direction:
|
||||||
|
Array of floats represents direction vector.
|
||||||
|
:param alpha:
|
||||||
|
Spheres overlap parameter.
|
||||||
|
:param execution:
|
||||||
|
Index of the execution. If None, use latest.
|
||||||
|
:return:
|
||||||
|
If entry is found returns Model instance else None.
|
||||||
|
"""
|
||||||
|
execution = execution or self.getLatest()
|
||||||
|
query = (
|
||||||
|
tables.Mesh
|
||||||
|
.select()
|
||||||
|
.join(tables.Shape, pw.JOIN.LEFT_OUTER)
|
||||||
|
.join(tables.Execution, pw.JOIN.LEFT_OUTER)
|
||||||
|
.where(
|
||||||
|
tables.Execution.exec_id == execution,
|
||||||
|
tables.Shape.label == label,
|
||||||
|
tables.Shape.direction == direction,
|
||||||
|
tables.Shape.alpha == alpha
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
with self:
|
||||||
|
if to_dict:
|
||||||
|
table = query.dicts().get() if query.exists() else None
|
||||||
|
|
||||||
|
else:
|
||||||
|
table = query.get() if query.exists() else None
|
||||||
|
|
||||||
|
return table
|
||||||
|
|
||||||
|
def getFlowOnephase(
|
||||||
|
self,
|
||||||
|
label: str = None,
|
||||||
|
direction: list[float] | ndarray = None,
|
||||||
|
alpha: float = None,
|
||||||
|
execution: int = None,
|
||||||
|
to_dict: bool = False,
|
||||||
|
**kwargs
|
||||||
|
) -> tables.Mesh | dict | None:
|
||||||
|
"""Get one phase flow entry from database.
|
||||||
|
|
||||||
|
:param label:
|
||||||
|
Label of the shape.
|
||||||
|
:param direction:
|
||||||
|
Array of floats represents direction vector.
|
||||||
|
:param alpha:
|
||||||
|
Spheres overlap parameter.
|
||||||
|
:param execution:
|
||||||
|
Index of the execution. If None, use latest.
|
||||||
|
:param to_dict:
|
||||||
|
If True, convert result to dict.
|
||||||
|
:return:
|
||||||
|
If entry is found returns Model instance or dict else None.
|
||||||
|
"""
|
||||||
|
execution = execution or self.getLatest()
|
||||||
|
query = (
|
||||||
|
tables.FlowOnephase
|
||||||
|
.select()
|
||||||
|
.join(tables.Mesh, pw.JOIN.LEFT_OUTER)
|
||||||
|
.join(tables.Shape, pw.JOIN.LEFT_OUTER)
|
||||||
|
.join(tables.Execution, pw.JOIN.LEFT_OUTER)
|
||||||
|
.where(
|
||||||
|
tables.Execution.exec_id == execution,
|
||||||
|
tables.Shape.label == label,
|
||||||
|
tables.Shape.direction == direction,
|
||||||
|
tables.Shape.alpha == alpha
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
with self:
|
||||||
|
if to_dict:
|
||||||
|
table = query.dicts().get() if query.exists() else None
|
||||||
|
|
||||||
|
else:
|
||||||
|
table = query.get() if query.exists() else None
|
||||||
|
|
||||||
|
return table
|
||||||
|
|
||||||
|
def load(
|
||||||
|
self,
|
||||||
|
field: str,
|
||||||
|
execution: int = None,
|
||||||
|
label: str = None,
|
||||||
|
direction: list[float] | ndarray = None
|
||||||
|
):
|
||||||
|
execution = execution or self.getLatest()
|
||||||
|
|
||||||
|
for table in self.tables:
|
||||||
|
try:
|
||||||
|
column = getattr(table, field)
|
||||||
|
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
query = table.select(tables.Shape.alpha, column, tables.Shape.direction, tables.Shape.label)
|
||||||
|
|
||||||
|
for current in reversed(self.tables[ :self.tables.index(table)]):
|
||||||
|
query = query.join(table, pw.JOIN.LEFT_OUTER)
|
||||||
|
|
||||||
|
query = (
|
||||||
|
query.switch(tables.Shape)
|
||||||
|
.where(tables.Shape.exec_id == execution)
|
||||||
|
.order_by(tables.Shape.label, tables.Shape.direction, tables.Shape.alpha)
|
||||||
|
)
|
||||||
|
resp = None
|
||||||
|
|
||||||
|
with self:
|
||||||
|
if query.exists():
|
||||||
|
resp = []
|
||||||
|
for row in query.dicts():
|
||||||
|
for k in row.keys():
|
||||||
|
if type(row[k]) == list:
|
||||||
|
row[k] = str(row[k])
|
||||||
|
|
||||||
|
resp.append(row)
|
||||||
|
|
||||||
|
resp = pd.DataFrame(resp)
|
||||||
|
|
||||||
|
if label is not None:
|
||||||
|
resp = resp[resp.label == label]
|
||||||
|
|
||||||
|
if direction is not None:
|
||||||
|
resp = resp[resp.direction == direction]
|
||||||
|
|
||||||
|
return resp[field]
|
106
anisotropy/database/tables.py
Normal file
106
anisotropy/database/tables.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import peewee as pw
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
|
# proxy, assign database later
|
||||||
|
database_proxy = pw.Proxy()
|
||||||
|
|
||||||
|
|
||||||
|
class Execution(pw.Model):
|
||||||
|
exec_id = pw.AutoField()
|
||||||
|
|
||||||
|
date = pw.DateTimeField()
|
||||||
|
executionTime = pw.TimeField(null = True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
database = database_proxy
|
||||||
|
table_name = "executions"
|
||||||
|
|
||||||
|
|
||||||
|
class Shape(pw.Model):
|
||||||
|
shape_id = pw.AutoField()
|
||||||
|
exec_id = pw.ForeignKeyField(Execution, backref = "executions", on_delete = "CASCADE")
|
||||||
|
|
||||||
|
shapeStatus = pw.TextField(null = True, default = "idle")
|
||||||
|
shapeExecutionTime = pw.TimeField(null = True)
|
||||||
|
|
||||||
|
label = pw.TextField(null = True)
|
||||||
|
direction = utils.JSONField(null = True)
|
||||||
|
alpha = pw.FloatField(null = True)
|
||||||
|
|
||||||
|
r0 = pw.FloatField(null = True)
|
||||||
|
L = pw.FloatField(null = True)
|
||||||
|
radius = pw.FloatField(null = True)
|
||||||
|
|
||||||
|
filletsEnabled = pw.BooleanField(null = True)
|
||||||
|
fillets = pw.FloatField(null = True)
|
||||||
|
|
||||||
|
volumeCell = pw.FloatField(null = True)
|
||||||
|
volume = pw.FloatField(null = True)
|
||||||
|
porosity = pw.FloatField(null = True)
|
||||||
|
|
||||||
|
areaOutlet = pw.FloatField(null = True)
|
||||||
|
length = pw.FloatField(null = True)
|
||||||
|
areaCellOutlet = pw.FloatField(null = True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
database = database_proxy
|
||||||
|
table_name = "shapes"
|
||||||
|
# depends_on = Execution
|
||||||
|
|
||||||
|
|
||||||
|
class Mesh(pw.Model):
|
||||||
|
mesh_id = pw.AutoField()
|
||||||
|
shape_id = pw.ForeignKeyField(Shape, backref = "shapes", on_delete = "CASCADE")
|
||||||
|
|
||||||
|
meshStatus = pw.TextField(null = True, default = "idle")
|
||||||
|
meshExecutionTime = pw.TimeField(null = True)
|
||||||
|
|
||||||
|
elements = pw.IntegerField(null = True)
|
||||||
|
edges = pw.IntegerField(null = True)
|
||||||
|
faces = pw.IntegerField(null = True)
|
||||||
|
volumes = pw.IntegerField(null = True)
|
||||||
|
tetrahedrons = pw.IntegerField(null = True)
|
||||||
|
prisms = pw.IntegerField(null = True)
|
||||||
|
pyramids = pw.IntegerField(null = True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
database = database_proxy
|
||||||
|
table_name = "meshes"
|
||||||
|
# depends_on = Execution
|
||||||
|
|
||||||
|
|
||||||
|
class FlowOnephase(pw.Model):
|
||||||
|
flow_id = pw.AutoField()
|
||||||
|
mesh_id = pw.ForeignKeyField(Mesh, backref = "meshes", on_delete = "CASCADE")
|
||||||
|
|
||||||
|
flowStatus = pw.TextField(null = True, default = "idle")
|
||||||
|
flowExecutionTime = pw.TimeField(null = True)
|
||||||
|
|
||||||
|
pressureInlet = pw.FloatField(null = True)
|
||||||
|
pressureOutlet = pw.FloatField(null = True)
|
||||||
|
pressureInternal = pw.FloatField(null = True)
|
||||||
|
velocityInlet = utils.JSONField(null = True)
|
||||||
|
velocityOutlet = utils.JSONField(null = True)
|
||||||
|
velocityInternal = utils.JSONField(null = True)
|
||||||
|
viscosity = pw.FloatField(null = True)
|
||||||
|
viscosityKinematic = pw.FloatField(null = True)
|
||||||
|
density = pw.FloatField(null = True)
|
||||||
|
flowRate = pw.FloatField(null = True)
|
||||||
|
permeability = pw.FloatField(null = True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
database = database_proxy
|
||||||
|
table_name = "flows"
|
||||||
|
# depends_on = Execution
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"Execution",
|
||||||
|
"Shape",
|
||||||
|
"Mesh",
|
||||||
|
"FlowOnephase"
|
||||||
|
]
|
160
anisotropy/database/utils.py
Normal file
160
anisotropy/database/utils.py
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from numpy import ndarray
|
||||||
|
|
||||||
|
import peewee as pw
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class ListField(pw.TextField):
|
||||||
|
field_type = "list"
|
||||||
|
|
||||||
|
def db_value(self, value):
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
def python_value(self, value):
|
||||||
|
pval = []
|
||||||
|
|
||||||
|
for entry in value[1 : -1].split(","):
|
||||||
|
try:
|
||||||
|
pval.append(float(entry))
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
pval.append(entry.strip().replace("'", ""))
|
||||||
|
|
||||||
|
return pval
|
||||||
|
|
||||||
|
|
||||||
|
class JSONPath(pw.ColumnBase):
|
||||||
|
def __init__(self, field, path = None):
|
||||||
|
super(JSONPath, self).__init__()
|
||||||
|
|
||||||
|
self._field = field
|
||||||
|
self._path = path or ()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
return pw.Value('$%s' % ''.join(self._path))
|
||||||
|
|
||||||
|
def __getitem__(self, idx):
|
||||||
|
if isinstance(idx, int):
|
||||||
|
item = '[%s]' % idx
|
||||||
|
|
||||||
|
else:
|
||||||
|
item = '.%s' % idx
|
||||||
|
|
||||||
|
return JSONPath(self._field, self._path + (item,))
|
||||||
|
|
||||||
|
def set(self, value, as_json = None):
|
||||||
|
if as_json or isinstance(value, (list, dict)):
|
||||||
|
value = pw.fn.json(self._field._json_dumps(value))
|
||||||
|
|
||||||
|
return pw.fn.json_set(self._field, self.path, value)
|
||||||
|
|
||||||
|
def update(self, value):
|
||||||
|
return self.set(pw.fn.json_patch(self, self._field._json_dumps(value)))
|
||||||
|
|
||||||
|
def remove(self):
|
||||||
|
return pw.fn.json_remove(self._field, self.path)
|
||||||
|
|
||||||
|
def json_type(self):
|
||||||
|
return pw.fn.json_type(self._field, self.path)
|
||||||
|
|
||||||
|
def length(self):
|
||||||
|
return pw.fn.json_array_length(self._field, self.path)
|
||||||
|
|
||||||
|
def children(self):
|
||||||
|
return pw.fn.json_each(self._field, self.path)
|
||||||
|
|
||||||
|
def tree(self):
|
||||||
|
return pw.fn.json_tree(self._field, self.path)
|
||||||
|
|
||||||
|
def __sql__(self, ctx):
|
||||||
|
return ctx.sql(
|
||||||
|
pw.fn.json_extract(self._field, self.path)
|
||||||
|
if self._path else self._field
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class JSONField(pw.TextField):
|
||||||
|
field_type = 'TEXT'
|
||||||
|
unpack = False
|
||||||
|
|
||||||
|
def __init__(self, json_dumps = None, json_loads = None, **kwargs):
|
||||||
|
super(JSONField, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
self._json_dumps = json_dumps or json.dumps
|
||||||
|
self._json_loads = json_loads or json.loads
|
||||||
|
|
||||||
|
def python_value(self, value):
|
||||||
|
if value is not None:
|
||||||
|
try:
|
||||||
|
return json.loads(value)
|
||||||
|
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return value
|
||||||
|
|
||||||
|
def db_value(self, value):
|
||||||
|
if value is not None:
|
||||||
|
if isinstance(value, ndarray):
|
||||||
|
value = list(value)
|
||||||
|
|
||||||
|
if not isinstance(value, pw.Node):
|
||||||
|
value = json.dumps(value)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def _e(op):
|
||||||
|
def inner(self, rhs):
|
||||||
|
if isinstance(rhs, (list, dict)):
|
||||||
|
rhs = pw.Value(rhs, converter = self.db_value, unpack = False)
|
||||||
|
|
||||||
|
return pw.Expression(self, op, rhs)
|
||||||
|
|
||||||
|
return inner
|
||||||
|
|
||||||
|
__eq__ = _e(pw.OP.EQ)
|
||||||
|
__ne__ = _e(pw.OP.NE)
|
||||||
|
__gt__ = _e(pw.OP.GT)
|
||||||
|
__ge__ = _e(pw.OP.GTE)
|
||||||
|
__lt__ = _e(pw.OP.LT)
|
||||||
|
__le__ = _e(pw.OP.LTE)
|
||||||
|
__hash__ = pw.Field.__hash__
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
return JSONPath(self)[item]
|
||||||
|
|
||||||
|
def set(self, value, as_json = None):
|
||||||
|
return JSONPath(self).set(value, as_json)
|
||||||
|
|
||||||
|
def update(self, data):
|
||||||
|
return JSONPath(self).update(data)
|
||||||
|
|
||||||
|
def remove(self):
|
||||||
|
return JSONPath(self).remove()
|
||||||
|
|
||||||
|
def json_type(self):
|
||||||
|
return pw.fn.json_type(self)
|
||||||
|
|
||||||
|
def length(self):
|
||||||
|
return pw.fn.json_array_length(self)
|
||||||
|
|
||||||
|
def children(self):
|
||||||
|
"""
|
||||||
|
Schema of `json_each` and `json_tree`:
|
||||||
|
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
type TEXT (object, array, string, etc),
|
||||||
|
atom (value for primitive/scalar types, NULL for array and object)
|
||||||
|
id INTEGER (unique identifier for element)
|
||||||
|
parent INTEGER (unique identifier of parent element or NULL)
|
||||||
|
fullkey TEXT (full path describing element)
|
||||||
|
path TEXT (path to the container of the current element)
|
||||||
|
json JSON hidden (1st input parameter to function)
|
||||||
|
root TEXT hidden (2nd input parameter, path at which to start)
|
||||||
|
"""
|
||||||
|
return pw.fn.json_each(self)
|
||||||
|
|
||||||
|
def tree(self):
|
||||||
|
return pw.fn.json_tree(self)
|
15
anisotropy/gui/__init__.py
Normal file
15
anisotropy/gui/__init__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import layouts
|
||||||
|
from .layouts import app
|
||||||
|
|
||||||
|
|
||||||
|
app.layout = layouts.base
|
||||||
|
|
||||||
|
|
||||||
|
def run(*args, **kwargs):
|
||||||
|
app.run_server(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run(debug = True)
|
8
anisotropy/gui/app.py
Normal file
8
anisotropy/gui/app.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import dash
|
||||||
|
import dash_bootstrap_components as dbc
|
||||||
|
|
||||||
|
app = dash.Dash(__name__, external_stylesheets = [ dbc.themes.LUX ])
|
||||||
|
app.title = "Anisotropy"
|
||||||
|
app.config["update_title"] = None
|
BIN
anisotropy/gui/assets/gh-light.png
Normal file
BIN
anisotropy/gui/assets/gh-light.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
29
anisotropy/gui/layouts/__init__.py
Normal file
29
anisotropy/gui/layouts/__init__.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import (
|
||||||
|
runner,
|
||||||
|
settings,
|
||||||
|
database,
|
||||||
|
visualization,
|
||||||
|
about,
|
||||||
|
base
|
||||||
|
)
|
||||||
|
from .base import app
|
||||||
|
|
||||||
|
|
||||||
|
runner = runner.layout
|
||||||
|
settings = settings.layout
|
||||||
|
database = database.layout
|
||||||
|
visualization = visualization.layout
|
||||||
|
about = about.layout
|
||||||
|
base = base.layout
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"runner",
|
||||||
|
"settings",
|
||||||
|
"database",
|
||||||
|
"visualization",
|
||||||
|
"about",
|
||||||
|
"base",
|
||||||
|
"app"
|
||||||
|
]
|
21
anisotropy/gui/layouts/about.py
Normal file
21
anisotropy/gui/layouts/about.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from dash import html
|
||||||
|
|
||||||
|
import anisotropy
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Layout
|
||||||
|
##
|
||||||
|
layout = html.Div([
|
||||||
|
html.H1(anisotropy.__name__),
|
||||||
|
html.Hr(),
|
||||||
|
html.P([
|
||||||
|
"Author: {}".format(anisotropy.__author__),
|
||||||
|
html.Br(),
|
||||||
|
"License: {}".format(anisotropy.__license__),
|
||||||
|
html.Br(),
|
||||||
|
"Version: {}".format(anisotropy.__version__)
|
||||||
|
])
|
||||||
|
])
|
81
anisotropy/gui/layouts/base.py
Normal file
81
anisotropy/gui/layouts/base.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from dash import html
|
||||||
|
from dash import dcc
|
||||||
|
from dash.dependencies import Input, Output
|
||||||
|
import dash_bootstrap_components as dbc
|
||||||
|
|
||||||
|
from . import (
|
||||||
|
runner,
|
||||||
|
settings,
|
||||||
|
database,
|
||||||
|
visualization,
|
||||||
|
about
|
||||||
|
)
|
||||||
|
from ..app import app
|
||||||
|
from .. import styles
|
||||||
|
|
||||||
|
import anisotropy
|
||||||
|
|
||||||
|
###
|
||||||
|
# Layout
|
||||||
|
##
|
||||||
|
layout = html.Div([
|
||||||
|
# Location
|
||||||
|
dcc.Location(id = "url", refresh = False),
|
||||||
|
|
||||||
|
# Sidebar
|
||||||
|
html.Div([
|
||||||
|
# Sidebar
|
||||||
|
html.H2([html.Img(src = "/assets/simple.png", height = "150px")], style = styles.logo),
|
||||||
|
html.Hr(style = { "color": "#ffffff" }),
|
||||||
|
dbc.Nav([
|
||||||
|
dbc.NavLink("Runner", href = "/", active = "exact", style = styles.white),
|
||||||
|
dbc.NavLink("Settings", href = "/settings", active = "exact", style = styles.white),
|
||||||
|
dbc.NavLink("Database", href = "/database", active = "exact", style = styles.white),
|
||||||
|
dbc.NavLink("Visualization", href = "/visualization", active = "exact", style = styles.white),
|
||||||
|
dbc.NavLink("About", href = "/about", active = "exact", style = styles.white),
|
||||||
|
], vertical = True, pills = True),
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
html.Hr(style = styles.white),
|
||||||
|
dbc.Container([
|
||||||
|
dbc.Row([
|
||||||
|
dbc.Col("v1.2.0"),
|
||||||
|
dbc.Col(
|
||||||
|
html.A(
|
||||||
|
html.Img(src = "/assets/gh-light.png", height = "20px"),
|
||||||
|
href = anisotropy.__repository__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
])
|
||||||
|
], style = styles.misc)
|
||||||
|
], style = styles.sidebar),
|
||||||
|
|
||||||
|
# Content
|
||||||
|
html.Div(id = "page-content", style = styles.page),
|
||||||
|
], style = styles.content)
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Callbacks
|
||||||
|
##
|
||||||
|
@app.callback(
|
||||||
|
Output("page-content", "children"),
|
||||||
|
[ Input("url", "pathname") ]
|
||||||
|
)
|
||||||
|
def displayPage(pathname):
|
||||||
|
if pathname == "/settings":
|
||||||
|
return settings.layout
|
||||||
|
|
||||||
|
elif pathname == "/about":
|
||||||
|
return about.layout
|
||||||
|
|
||||||
|
elif pathname == "/database":
|
||||||
|
return database.layout
|
||||||
|
|
||||||
|
elif pathname == "/visualization":
|
||||||
|
return visualization.layout
|
||||||
|
|
||||||
|
else:
|
||||||
|
return runner.layout
|
101
anisotropy/gui/layouts/database.py
Normal file
101
anisotropy/gui/layouts/database.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from dash.dash_table import DataTable
|
||||||
|
from dash import html
|
||||||
|
from dash import dcc
|
||||||
|
import dash_bootstrap_components as dbc
|
||||||
|
from dash.dependencies import Input, Output, State
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
from os import environ
|
||||||
|
|
||||||
|
from ..app import app
|
||||||
|
from .. import styles
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Layout
|
||||||
|
##
|
||||||
|
layout = html.Div([
|
||||||
|
# Messages and timer
|
||||||
|
dbc.Alert(
|
||||||
|
id = "db_status",
|
||||||
|
duration = 10000,
|
||||||
|
dismissable = True,
|
||||||
|
is_open = False,
|
||||||
|
style = styles.message
|
||||||
|
),
|
||||||
|
# dcc.Interval(id = "interval", interval = 1000, n_intervals = 0),
|
||||||
|
|
||||||
|
# Query
|
||||||
|
html.H2("Database"),
|
||||||
|
html.Hr(),
|
||||||
|
html.P("Query"),
|
||||||
|
dcc.Textarea(id = "db_input", style = { "min-width": "100%"}),
|
||||||
|
html.Br(),
|
||||||
|
dbc.Button("Query", id = "query", style = styles.minWidth),
|
||||||
|
|
||||||
|
# Output
|
||||||
|
html.Hr(),
|
||||||
|
html.P("Output"),
|
||||||
|
DataTable(
|
||||||
|
id = "db_output",
|
||||||
|
columns = [],
|
||||||
|
data = [],
|
||||||
|
style_table = { "overflow": "scroll"},
|
||||||
|
style_cell = {
|
||||||
|
'textAlign': 'left',
|
||||||
|
'width': '150px',
|
||||||
|
'minWidth': '180px',
|
||||||
|
'maxWidth': '180px',
|
||||||
|
'whiteSpace': 'no-wrap',
|
||||||
|
'overflow': 'hidden',
|
||||||
|
'textOverflow': 'ellipsis',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Callbacks
|
||||||
|
##
|
||||||
|
@app.callback(
|
||||||
|
Output("db_output", "columns"),
|
||||||
|
Output("db_output", "data"),
|
||||||
|
Output("db_status", "children"),
|
||||||
|
Output("db_status", "is_open"),
|
||||||
|
Output("db_status", "color"),
|
||||||
|
[ Input("query", "n_clicks") ],
|
||||||
|
[ State("db_input", "value") ],
|
||||||
|
prevent_initial_call = True
|
||||||
|
)
|
||||||
|
def db_query(clicks, db_input):
|
||||||
|
from anisotropy import database
|
||||||
|
import peewee as pw
|
||||||
|
|
||||||
|
path = pathlib.Path(environ["AP_CWD"], environ["AP_DB_FILE"])
|
||||||
|
db = database.Database(path)
|
||||||
|
|
||||||
|
try:
|
||||||
|
db.connect()
|
||||||
|
cursor = db.execute_sql(db_input)
|
||||||
|
|
||||||
|
except pw.OperationalError as e:
|
||||||
|
db.close()
|
||||||
|
return None, None, str(e), True, "danger"
|
||||||
|
|
||||||
|
else:
|
||||||
|
columns = [ { "name": col[0], "id": col[0] } for col in cursor.description ]
|
||||||
|
data = []
|
||||||
|
|
||||||
|
for row in cursor.fetchall():
|
||||||
|
item = {}
|
||||||
|
|
||||||
|
for key, value in zip(columns, row):
|
||||||
|
item[key["name"]] = value
|
||||||
|
|
||||||
|
data.append(item)
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
return columns, data, "", False, "success"
|
180
anisotropy/gui/layouts/runner.py
Normal file
180
anisotropy/gui/layouts/runner.py
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from dash.dash_table import DataTable
|
||||||
|
from dash import html
|
||||||
|
from dash import dcc
|
||||||
|
import dash_bootstrap_components as dbc
|
||||||
|
from dash.dependencies import Input, Output, State
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
import os
|
||||||
|
from os import environ
|
||||||
|
|
||||||
|
from ..app import app
|
||||||
|
from .. import styles
|
||||||
|
from .. import utils
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Layout
|
||||||
|
##
|
||||||
|
layout = html.Div([
|
||||||
|
# Messages and timer
|
||||||
|
dbc.Alert(
|
||||||
|
id = "status",
|
||||||
|
duration = 10000,
|
||||||
|
dismissable = True,
|
||||||
|
is_open = False,
|
||||||
|
style = styles.message
|
||||||
|
),
|
||||||
|
dcc.Interval(id = "interval", interval = 1000, n_intervals = 0),
|
||||||
|
|
||||||
|
# Runner
|
||||||
|
html.H2("Runner"),
|
||||||
|
html.Hr(),
|
||||||
|
html.P("Execution (leave zero for the latest)"),
|
||||||
|
dcc.Input(id = "execution", type = "number", value = 0, min = 0, style = styles.minWidth),
|
||||||
|
html.Br(),
|
||||||
|
dbc.Button("Start", id = "start", color = "success", style = styles.minWidth),
|
||||||
|
dbc.Button("Stop", id = "stop", color = "danger", disabled = True, style = styles.minWidth),
|
||||||
|
|
||||||
|
# Monitor
|
||||||
|
html.H2("Monitor"),
|
||||||
|
html.Hr(),
|
||||||
|
html.P(id = "runner-status"),
|
||||||
|
DataTable(id = "monitor", columns = [], data = [], style_table = styles.table),
|
||||||
|
|
||||||
|
# Log
|
||||||
|
html.H2("Log"),
|
||||||
|
html.Hr(),
|
||||||
|
dbc.Button("Delete", id = "delete", style = styles.minWidth),
|
||||||
|
dcc.Textarea(id = "logger", disabled = True, style = styles.bigText)
|
||||||
|
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Callbacks
|
||||||
|
##
|
||||||
|
@app.callback(
|
||||||
|
Output("start", "active"),
|
||||||
|
[ Input("start", "n_clicks") ],
|
||||||
|
[ State("execution", "value") ],
|
||||||
|
prevent_initial_call = True
|
||||||
|
)
|
||||||
|
def runnerStart(clicks, execution):
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
command = [
|
||||||
|
"anisotropy",
|
||||||
|
"compute",
|
||||||
|
"-v",
|
||||||
|
"--path", environ["AP_CWD"],
|
||||||
|
"--conf", environ["AP_CONF_FILE"],
|
||||||
|
"--pid", "anisotropy.pid",
|
||||||
|
"--logfile", environ["AP_LOG_FILE"],
|
||||||
|
]
|
||||||
|
|
||||||
|
if execution > 0:
|
||||||
|
command.extend([ "--exec-id", str(execution) ])
|
||||||
|
|
||||||
|
subprocess.run(
|
||||||
|
command,
|
||||||
|
start_new_session = True,
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
Output("stop", "active"),
|
||||||
|
[ Input("stop", "n_clicks") ],
|
||||||
|
prevent_initial_call = True
|
||||||
|
)
|
||||||
|
def runnerStop(clicks):
|
||||||
|
import psutil
|
||||||
|
import signal
|
||||||
|
|
||||||
|
pidpath = pathlib.Path(environ["AP_CWD"], "anisotropy.pid")
|
||||||
|
|
||||||
|
try:
|
||||||
|
pid = int(open(pidpath, "r").read())
|
||||||
|
master = psutil.Process(pid)
|
||||||
|
|
||||||
|
except (FileNotFoundError, psutil.NoSuchProcess):
|
||||||
|
return True
|
||||||
|
|
||||||
|
else:
|
||||||
|
os.killpg(master.pid, signal.SIGTERM)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
Output("monitor", "columns"),
|
||||||
|
Output("monitor", "data"),
|
||||||
|
Output("runner-status", "children"),
|
||||||
|
Output("start", "disabled"),
|
||||||
|
Output("stop", "disabled"),
|
||||||
|
Output("delete", "disabled"),
|
||||||
|
[ Input("interval", "n_intervals") ],
|
||||||
|
)
|
||||||
|
def monitorUpdate(intervals):
|
||||||
|
import psutil
|
||||||
|
|
||||||
|
pidpath = pathlib.Path(environ["AP_CWD"], "anisotropy.pid")
|
||||||
|
processes = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
pid = int(open(pidpath, "r").read())
|
||||||
|
master = psutil.Process(pid)
|
||||||
|
|
||||||
|
except (FileNotFoundError, psutil.NoSuchProcess):
|
||||||
|
return [], [], "Status: not running", False, True, False
|
||||||
|
|
||||||
|
else:
|
||||||
|
for process in [ master, *master.children() ]:
|
||||||
|
created = psutil.time.localtime(process.create_time())
|
||||||
|
processes.append({
|
||||||
|
"name": process.name(),
|
||||||
|
"pid": process.pid,
|
||||||
|
"status": process.status(),
|
||||||
|
"memory": utils.getSize(process.memory_full_info().uss),
|
||||||
|
"threads": process.num_threads(),
|
||||||
|
"created": "{}:{}:{}".format(created.tm_hour, created.tm_min, created.tm_sec)
|
||||||
|
})
|
||||||
|
|
||||||
|
columns = [ { "name": col, "id": col } for col in processes[0].keys() ]
|
||||||
|
|
||||||
|
return columns, processes, "Status: running", True, False, True
|
||||||
|
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
Output("logger", "value"),
|
||||||
|
[ Input("interval", "n_intervals") ]
|
||||||
|
)
|
||||||
|
def logUpdate(intervals):
|
||||||
|
logpath = pathlib.Path(environ["AP_CWD"], "anisotropy.log")
|
||||||
|
|
||||||
|
if os.path.exists(logpath):
|
||||||
|
with open(logpath, "r") as io:
|
||||||
|
log = io.read()
|
||||||
|
|
||||||
|
return log
|
||||||
|
|
||||||
|
else:
|
||||||
|
return "Not found"
|
||||||
|
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
Output("delete", "active"),
|
||||||
|
[ Input("delete", "n_clicks") ],
|
||||||
|
prevent_initial_call = True
|
||||||
|
)
|
||||||
|
def logDelete(clicks):
|
||||||
|
logpath = pathlib.Path(environ["AP_CWD"], "anisotropy.log")
|
||||||
|
|
||||||
|
if os.path.exists(logpath):
|
||||||
|
os.remove(logpath)
|
||||||
|
|
||||||
|
return True
|
141
anisotropy/gui/layouts/settings.py
Normal file
141
anisotropy/gui/layouts/settings.py
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from dash import html
|
||||||
|
from dash import dcc
|
||||||
|
import dash_bootstrap_components as dbc
|
||||||
|
from dash.dependencies import Input, Output, State
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
from os import environ
|
||||||
|
|
||||||
|
from ..app import app
|
||||||
|
from .. import styles
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Layout
|
||||||
|
##
|
||||||
|
layout = html.Div([
|
||||||
|
# Messages
|
||||||
|
dbc.Alert(
|
||||||
|
id = "status",
|
||||||
|
duration = 10000,
|
||||||
|
dismissable = True,
|
||||||
|
is_open = False,
|
||||||
|
style = styles.message
|
||||||
|
),
|
||||||
|
dbc.Alert(
|
||||||
|
id = "general-status",
|
||||||
|
duration = 10000,
|
||||||
|
dismissable = True,
|
||||||
|
is_open = False,
|
||||||
|
style = styles.message
|
||||||
|
),
|
||||||
|
# General
|
||||||
|
html.H2("General"),
|
||||||
|
html.Hr(),
|
||||||
|
html.P("Path"),
|
||||||
|
dcc.Input(id = "cwd", style = { "min-width": "500px" }),
|
||||||
|
html.Br(),
|
||||||
|
dbc.Button("Save general", id = "general-save", style = styles.minWidth),
|
||||||
|
|
||||||
|
# Options
|
||||||
|
html.H2("Options"),
|
||||||
|
html.Hr(),
|
||||||
|
html.P("Nprocs"),
|
||||||
|
dcc.Input(id = "nprocs", type = "number", style = styles.minWidth),
|
||||||
|
html.P("Stage"),
|
||||||
|
dcc.Dropdown(
|
||||||
|
id = "stage",
|
||||||
|
options = [ { "label": k, "value": k } for k in ["all", "shape", "mesh", "flow", "postProcess"] ],
|
||||||
|
style = styles.minWidth
|
||||||
|
),
|
||||||
|
dbc.Button("Save", id = "submit", style = styles.minWidth),
|
||||||
|
|
||||||
|
# Cases
|
||||||
|
html.H2("Cases"),
|
||||||
|
html.Hr(),
|
||||||
|
dcc.Textarea(id = "cases", style = styles.bigText),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Callbacks
|
||||||
|
##
|
||||||
|
@app.callback(
|
||||||
|
Output("general-status", "children"),
|
||||||
|
Output("general-status", "is_open"),
|
||||||
|
Output("general-status", "color"),
|
||||||
|
[ Input("general-save", "n_clicks") ],
|
||||||
|
[
|
||||||
|
State("cwd", "value"),
|
||||||
|
],
|
||||||
|
prevent_initial_call = True
|
||||||
|
)
|
||||||
|
def generalSave(clicks, cwd):
|
||||||
|
path = pathlib.Path(cwd)
|
||||||
|
|
||||||
|
if not path.is_absolute():
|
||||||
|
return "Cwd path must be absolute", True, "danger"
|
||||||
|
|
||||||
|
environ["AP_CWD"] = str(path)
|
||||||
|
|
||||||
|
return "General settings saved", True, "success"
|
||||||
|
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
Output("cwd", "value"),
|
||||||
|
Output("nprocs", "value"),
|
||||||
|
Output("stage", "value"),
|
||||||
|
Output("cases", "value"),
|
||||||
|
[ Input("url", "pathname") ]
|
||||||
|
)
|
||||||
|
def settingsLoad(pathname):
|
||||||
|
from anisotropy.core import config as core_config
|
||||||
|
import toml
|
||||||
|
|
||||||
|
path = pathlib.Path(environ["AP_CWD"], environ["AP_CONF_FILE"])
|
||||||
|
config = core_config.default_config()
|
||||||
|
|
||||||
|
if path.exists():
|
||||||
|
config.load(path)
|
||||||
|
|
||||||
|
return environ["AP_CWD"], config["nprocs"], config["stage"], toml.dumps(config.content)
|
||||||
|
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
Output("status", "children"),
|
||||||
|
Output("status", "is_open"),
|
||||||
|
Output("status", "color"),
|
||||||
|
[ Input("submit", "n_clicks") ],
|
||||||
|
[
|
||||||
|
State("nprocs", "value"),
|
||||||
|
State("stage", "value"),
|
||||||
|
State("cases", "value")
|
||||||
|
],
|
||||||
|
prevent_initial_call = True
|
||||||
|
)
|
||||||
|
def settingsSave(nclick, nprocs, stage, cases):
|
||||||
|
from anisotropy.core import config as core_config
|
||||||
|
import toml
|
||||||
|
|
||||||
|
path = pathlib.Path(environ["AP_CWD"], environ["AP_CONF_FILE"])
|
||||||
|
config = core_config.default_config()
|
||||||
|
|
||||||
|
if path.exists():
|
||||||
|
config.load(path)
|
||||||
|
|
||||||
|
config.update(
|
||||||
|
nprocs = nprocs,
|
||||||
|
stage = stage
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
config.content = toml.loads(cases)
|
||||||
|
config.dump(path)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return str(e), True, "danger"
|
||||||
|
|
||||||
|
else:
|
||||||
|
return f"Saved to { path }", True, "success"
|
405
anisotropy/gui/layouts/visualization.py
Normal file
405
anisotropy/gui/layouts/visualization.py
Normal file
@ -0,0 +1,405 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from dash import html
|
||||||
|
from dash import dcc
|
||||||
|
import dash_bootstrap_components as dbc
|
||||||
|
from dash.dependencies import Input, Output, State
|
||||||
|
import dash_vtk
|
||||||
|
import dash_vtk.utils
|
||||||
|
import vtk
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
from os import environ
|
||||||
|
|
||||||
|
from ..app import app
|
||||||
|
from .. import styles
|
||||||
|
|
||||||
|
|
||||||
|
class MeshRepresentation(object):
|
||||||
|
def __init__(self, path):
|
||||||
|
self.mesh = vtk.vtkXMLUnstructuredGridReader()
|
||||||
|
self.mesh.SetFileName(path)
|
||||||
|
self.mesh.Update()
|
||||||
|
|
||||||
|
self.clip = False
|
||||||
|
self.crinkle = False
|
||||||
|
self.wireframe = True
|
||||||
|
self.planeNormal = [0, 0, -1]
|
||||||
|
self.planeOrigin = [0, 0, 1]
|
||||||
|
|
||||||
|
def getRepresentation(self):
|
||||||
|
if not self.clip:
|
||||||
|
dataset = self.mesh.GetOutput()
|
||||||
|
|
||||||
|
else:
|
||||||
|
plane = vtk.vtkPlane()
|
||||||
|
plane.SetNormal(self.planeNormal)
|
||||||
|
plane.SetOrigin(self.planeOrigin)
|
||||||
|
|
||||||
|
if self.crinkle:
|
||||||
|
implfunc = vtk.vtkImplicitBoolean()
|
||||||
|
implfunc.AddFunction(plane)
|
||||||
|
implfunc.Modified()
|
||||||
|
|
||||||
|
clipper = vtk.vtkExtractGeometry()
|
||||||
|
clipper.SetInputConnection(self.mesh.GetOutputPort())
|
||||||
|
clipper.SetImplicitFunction(implfunc)
|
||||||
|
clipper.SetExtractInside(1)
|
||||||
|
clipper.SetExtractOnlyBoundaryCells(0)
|
||||||
|
clipper.SetExtractBoundaryCells(1)
|
||||||
|
clipper.ExtractInsideOff()
|
||||||
|
|
||||||
|
else:
|
||||||
|
clipper = vtk.vtkTableBasedClipDataSet()
|
||||||
|
clipper.SetInputData(self.mesh.GetOutput())
|
||||||
|
clipper.SetClipFunction(plane)
|
||||||
|
clipper.SetValue(0.0)
|
||||||
|
clipper.GenerateClippedOutputOn()
|
||||||
|
|
||||||
|
clipper.Update()
|
||||||
|
dataset = clipper.GetOutput()
|
||||||
|
|
||||||
|
representation = dash_vtk.GeometryRepresentation(
|
||||||
|
id = "mesh-representation",
|
||||||
|
children = [
|
||||||
|
dash_vtk.Mesh(id = "mesh", state = dash_vtk.utils.to_mesh_state(dataset))
|
||||||
|
],
|
||||||
|
property = { "edgeVisibility": self.wireframe }
|
||||||
|
)
|
||||||
|
|
||||||
|
return representation
|
||||||
|
|
||||||
|
|
||||||
|
def databaseColumns():
|
||||||
|
from anisotropy.database import Database
|
||||||
|
import re
|
||||||
|
|
||||||
|
db = Database()
|
||||||
|
idcol = re.compile(r"\s*_id")
|
||||||
|
columns = []
|
||||||
|
|
||||||
|
for table in db.tables:
|
||||||
|
for column in table._meta.columns.keys():
|
||||||
|
if not idcol.search(column):
|
||||||
|
columns.append(column)
|
||||||
|
|
||||||
|
return columns
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Layout
|
||||||
|
##
|
||||||
|
controls = html.Div([
|
||||||
|
html.P("Execution"),
|
||||||
|
dcc.Input(id = "exec_id", type = "number", value = 0, min = 0, style = styles.minWidth),
|
||||||
|
html.Br(),
|
||||||
|
|
||||||
|
html.P("Structure"),
|
||||||
|
dcc.Dropdown(
|
||||||
|
id = "structure",
|
||||||
|
options = [ { "label": v, "value": v } for v in [ "simple", "bodyCentered", "faceCentered" ] ],
|
||||||
|
value = "simple",
|
||||||
|
style = styles.minWidth
|
||||||
|
),
|
||||||
|
html.Br(),
|
||||||
|
|
||||||
|
html.P("Direction"),
|
||||||
|
dcc.Dropdown(
|
||||||
|
id = "direction",
|
||||||
|
options = [ { "label": str(v), "value": str(v)} for v in [ [1., 0., 0.], [0., 0., 1.], [1., 1., 1.] ] ],
|
||||||
|
value = str([1., 0., 0.]),
|
||||||
|
),
|
||||||
|
html.Br(),
|
||||||
|
|
||||||
|
html.P("Alpha"),
|
||||||
|
dcc.Input(id = "alpha", type = "number", value = 0.01, min = 0.01, step = 0.01, style = styles.minWidth),
|
||||||
|
html.Br(),
|
||||||
|
|
||||||
|
html.P("Clip"),
|
||||||
|
dbc.Checkbox(id = "clip", value = False),
|
||||||
|
html.Br(),
|
||||||
|
|
||||||
|
html.P("Crinkle"),
|
||||||
|
dbc.Checkbox(id = "crinkle", value = False),
|
||||||
|
html.Br(),
|
||||||
|
|
||||||
|
html.P("Plane normal"),
|
||||||
|
dcc.Input(id = "normal-x", type = "number", value = 0, style = { "width": "100px", "margin-bottom": "20px" }),
|
||||||
|
dcc.Input(id = "normal-y", type = "number", value = 0, style = { "width": "100px", "margin-bottom": "20px" }),
|
||||||
|
dcc.Input(id = "normal-z", type = "number", value = -1, style = { "width": "100px", "margin-bottom": "20px" }),
|
||||||
|
html.Br(),
|
||||||
|
|
||||||
|
html.P("Plane origin"),
|
||||||
|
dcc.Input(id = "origin-x", type = "number", value = 0, style = { "width": "100px", "margin-bottom": "20px" }),
|
||||||
|
dcc.Input(id = "origin-y", type = "number", value = 0, style = { "width": "100px", "margin-bottom": "20px" }),
|
||||||
|
dcc.Input(id = "origin-z", type = "number", value = 1, style = { "width": "100px", "margin-bottom": "20px" }),
|
||||||
|
html.Br(),
|
||||||
|
|
||||||
|
html.P("Wireframe"),
|
||||||
|
dbc.Checkbox(id = "wireframe", value = True),
|
||||||
|
html.Br(),
|
||||||
|
|
||||||
|
dbc.Button("Draw", id = "draw"),
|
||||||
|
])
|
||||||
|
|
||||||
|
plotcontrols = html.Div([
|
||||||
|
html.P("Execution"),
|
||||||
|
dcc.Input(id = "plot-exec_id", type = "number", value = 0, min = 0, style = styles.minWidth),
|
||||||
|
html.Br(),
|
||||||
|
|
||||||
|
html.P("Structure"),
|
||||||
|
dcc.Dropdown(
|
||||||
|
id = "plot-structure",
|
||||||
|
options = [
|
||||||
|
{ "label": v, "value": v }
|
||||||
|
for v in [ "simple", "bodyCentered", "faceCentered" ]
|
||||||
|
],
|
||||||
|
value = "simple"
|
||||||
|
),
|
||||||
|
html.Br(),
|
||||||
|
|
||||||
|
html.P("Direction"),
|
||||||
|
dcc.Dropdown(
|
||||||
|
id = "plot-direction",
|
||||||
|
options = [
|
||||||
|
{ "label": str(v), "value": str(v)}
|
||||||
|
for v in [ [1., 0., 0.], [0., 0., 1.], [1., 1., 1.], "all" ]
|
||||||
|
],
|
||||||
|
value = str([1., 0., 0.]),
|
||||||
|
),
|
||||||
|
html.Br(),
|
||||||
|
|
||||||
|
html.P("Data"),
|
||||||
|
dcc.Dropdown(
|
||||||
|
id = "plot-data",
|
||||||
|
options = [ { "label": v, "value": v } for v in databaseColumns() ],
|
||||||
|
value = "porosity",
|
||||||
|
),
|
||||||
|
html.Br(),
|
||||||
|
|
||||||
|
dbc.Button("Draw", id = "plot-draw"),
|
||||||
|
])
|
||||||
|
|
||||||
|
layout = html.Div([
|
||||||
|
html.H2("Plot"),
|
||||||
|
html.Hr(),
|
||||||
|
dbc.Container(
|
||||||
|
fluid = True,
|
||||||
|
children = [
|
||||||
|
dbc.Row([
|
||||||
|
dbc.Col(width = 4, children = plotcontrols, style = { "min-width": "350px" }),
|
||||||
|
dbc.Col(
|
||||||
|
width = 8,
|
||||||
|
children = [
|
||||||
|
html.Div(id = "plot-output", style = { "width": "100%", "min-width": "800px" })
|
||||||
|
],
|
||||||
|
style = { "min-width": "800px" }
|
||||||
|
),
|
||||||
|
], style = { "height": "100%"}),
|
||||||
|
]),
|
||||||
|
html.Br(),
|
||||||
|
|
||||||
|
html.H2("Mesh"),
|
||||||
|
html.Hr(),
|
||||||
|
dbc.Container(
|
||||||
|
fluid = True,
|
||||||
|
children = [
|
||||||
|
dbc.Row([
|
||||||
|
dbc.Col(width = 4, children = controls, style = { "min-width": "350px" }),
|
||||||
|
dbc.Col(
|
||||||
|
width = 8,
|
||||||
|
children = [
|
||||||
|
html.Div(
|
||||||
|
id = "vtk-output",
|
||||||
|
style = { "height": "800px", "width": "100%", "min-width": "800px" }
|
||||||
|
)
|
||||||
|
],
|
||||||
|
style = { "min-width": "800px" }),
|
||||||
|
], style = { "height": "100%"}),
|
||||||
|
])
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
[ Output("plot-output", "children") ],
|
||||||
|
[ Input("plot-draw", "n_clicks") ],
|
||||||
|
[
|
||||||
|
State("plot-exec_id", "value"),
|
||||||
|
State("plot-structure", "value"),
|
||||||
|
State("plot-direction", "value"),
|
||||||
|
State("plot-data", "value"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def plotDraw(clicks, execution, structure, direction, data):
|
||||||
|
import peewee as pw
|
||||||
|
from anisotropy.database import Database, tables
|
||||||
|
import json
|
||||||
|
from pandas import DataFrame
|
||||||
|
import plotly.express as px
|
||||||
|
|
||||||
|
path = pathlib.Path(environ["AP_CWD"], environ["AP_DB_FILE"])
|
||||||
|
|
||||||
|
if not path.is_file():
|
||||||
|
return [ "Database not found" ]
|
||||||
|
|
||||||
|
db = Database(path)
|
||||||
|
|
||||||
|
if not db.getExecution(execution):
|
||||||
|
return [ "Execution not found" ]
|
||||||
|
|
||||||
|
for model in db.tables:
|
||||||
|
try:
|
||||||
|
column = getattr(model, data)
|
||||||
|
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
if direction == "all":
|
||||||
|
select = (tables.Shape.alpha, column, tables.Shape.direction)
|
||||||
|
|
||||||
|
else:
|
||||||
|
select = (tables.Shape.alpha, column)
|
||||||
|
|
||||||
|
"""query = (
|
||||||
|
tables.Shape
|
||||||
|
.select(*select)
|
||||||
|
.join(tables.Execution, JOIN.LEFT_OUTER)
|
||||||
|
.switch(tables.Shape)
|
||||||
|
.join(tables.Mesh, JOIN.LEFT_OUTER)
|
||||||
|
#.switch(tables.Shape)
|
||||||
|
.join(tables.FlowOnephase, JOIN.LEFT_OUTER)
|
||||||
|
.switch(tables.Shape)
|
||||||
|
.where(
|
||||||
|
tables.Shape.exec_id == execution,
|
||||||
|
tables.Shape.label == structure,
|
||||||
|
)
|
||||||
|
)"""
|
||||||
|
query = model.select(*select)
|
||||||
|
idn = db.tables.index(model)
|
||||||
|
|
||||||
|
for table in reversed(db.tables[ :idn]):
|
||||||
|
query = query.join(table, pw.JOIN.LEFT_OUTER)
|
||||||
|
|
||||||
|
query = query.switch(tables.Shape)
|
||||||
|
query = query.where(
|
||||||
|
tables.Shape.exec_id == execution,
|
||||||
|
tables.Shape.label == structure,
|
||||||
|
)
|
||||||
|
query = query.order_by(tables.Shape.alpha)
|
||||||
|
|
||||||
|
if not direction == "all":
|
||||||
|
query = query.where(tables.Shape.direction == json.loads(direction))
|
||||||
|
|
||||||
|
with db:
|
||||||
|
if query.exists():
|
||||||
|
table = []
|
||||||
|
for row in query.dicts():
|
||||||
|
for k in row.keys():
|
||||||
|
if type(row[k]) == list:
|
||||||
|
row[k] = str(row[k])
|
||||||
|
|
||||||
|
table.append(row)
|
||||||
|
|
||||||
|
else:
|
||||||
|
table = None
|
||||||
|
|
||||||
|
if not table:
|
||||||
|
return [ "Results not found" ]
|
||||||
|
|
||||||
|
import plotly.io as plt_io
|
||||||
|
|
||||||
|
plt_io.templates["custom_dark"] = plt_io.templates["plotly_dark"]
|
||||||
|
|
||||||
|
plt_io.templates["custom_dark"]['layout']['paper_bgcolor'] = '#30404D'
|
||||||
|
plt_io.templates["custom_dark"]['layout']['plot_bgcolor'] = '#30404D'
|
||||||
|
|
||||||
|
plt_io.templates['custom_dark']['layout']['yaxis']['gridcolor'] = '#4f687d'
|
||||||
|
plt_io.templates['custom_dark']['layout']['xaxis']['gridcolor'] = '#4f687d'
|
||||||
|
|
||||||
|
fig = px.line(
|
||||||
|
DataFrame(table), x = "alpha", y = data, title = structure, markers = True
|
||||||
|
)
|
||||||
|
|
||||||
|
if direction == "all":
|
||||||
|
fig = px.line(
|
||||||
|
DataFrame(table), x = "alpha", y = data, title = structure, markers = True, color = "direction"
|
||||||
|
)
|
||||||
|
|
||||||
|
fig.layout.template = "custom_dark"
|
||||||
|
fig.update_xaxes(showline = True, linewidth = 1, linecolor = '#4f687d', mirror = True)
|
||||||
|
fig.update_yaxes(showline = True, linewidth = 1, linecolor = '#4f687d', mirror = True)
|
||||||
|
|
||||||
|
plot = dcc.Graph(
|
||||||
|
figure = fig
|
||||||
|
)
|
||||||
|
|
||||||
|
return [ plot ]
|
||||||
|
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
[ Output("alpha", "min"), Output("alpha", "max") ],
|
||||||
|
[ Input("structure", "value") ]
|
||||||
|
)
|
||||||
|
def alphaLimits(label):
|
||||||
|
if label == "simple":
|
||||||
|
return 0.01, 0.29
|
||||||
|
|
||||||
|
elif label == "bodyCentered":
|
||||||
|
return 0.01, 0.18
|
||||||
|
|
||||||
|
elif label == "faceCentered":
|
||||||
|
return 0.01, 0.13
|
||||||
|
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
[ Output("vtk-output", "children") ],
|
||||||
|
[ Input("draw", "n_clicks") ],
|
||||||
|
[
|
||||||
|
State("exec_id", "value"),
|
||||||
|
State("structure", "value"),
|
||||||
|
State("direction", "value"),
|
||||||
|
State("alpha", "value"),
|
||||||
|
State("clip", "value"),
|
||||||
|
State("crinkle", "value"),
|
||||||
|
State("wireframe", "value"),
|
||||||
|
State("normal-x", "value"),
|
||||||
|
State("normal-y", "value"),
|
||||||
|
State("normal-z", "value"),
|
||||||
|
State("origin-x", "value"),
|
||||||
|
State("origin-y", "value"),
|
||||||
|
State("origin-z", "value"),
|
||||||
|
],
|
||||||
|
prevent_initial_call = True
|
||||||
|
)
|
||||||
|
def meshDraw(clicks, execution, structure, direction, alpha, clip, crinkle, wireframe, normal_x, normal_y, normal_z, origin_x, origin_y, origin_z):
|
||||||
|
import meshio
|
||||||
|
|
||||||
|
path = pathlib.Path(environ["AP_CWD"], environ["AP_BUILD_DIR"])
|
||||||
|
path /= "execution-{}".format(execution)
|
||||||
|
path /= "{}-{}-{}".format(
|
||||||
|
structure,
|
||||||
|
direction.replace(" ", ""),
|
||||||
|
alpha
|
||||||
|
)
|
||||||
|
basemeshpath = path / "mesh.msh"
|
||||||
|
meshpath = path / "mesh.vtu"
|
||||||
|
|
||||||
|
if not basemeshpath.exists():
|
||||||
|
return [ "Mesh not found" ]
|
||||||
|
|
||||||
|
if not meshpath.exists() or not meshpath.is_file():
|
||||||
|
meshold = meshio.read(basemeshpath)
|
||||||
|
meshold.write(meshpath)
|
||||||
|
|
||||||
|
meshrepr = MeshRepresentation(meshpath)
|
||||||
|
meshrepr.clip = clip
|
||||||
|
meshrepr.crinkle = crinkle
|
||||||
|
meshrepr.wireframe = wireframe
|
||||||
|
meshrepr.planeNormal = [ normal_x, normal_y, normal_z ]
|
||||||
|
meshrepr.planeOrigin = [ origin_x, origin_y, origin_z ]
|
||||||
|
view = dash_vtk.View([ meshrepr.getRepresentation() ])
|
||||||
|
|
||||||
|
return [ view ]
|
57
anisotropy/gui/styles.py
Normal file
57
anisotropy/gui/styles.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
minWidth = {
|
||||||
|
"min-width": "200px",
|
||||||
|
"max-width": "200px",
|
||||||
|
"margin-bottom": "15px"
|
||||||
|
}
|
||||||
|
|
||||||
|
bigText = {
|
||||||
|
"min-width": "100%",
|
||||||
|
"min-height": "500px"
|
||||||
|
}
|
||||||
|
|
||||||
|
message = {
|
||||||
|
"position": "fixed",
|
||||||
|
"top": "30px",
|
||||||
|
"right": "30px",
|
||||||
|
}
|
||||||
|
|
||||||
|
page = {
|
||||||
|
"padding": "30px 50px 0px 50px"
|
||||||
|
}
|
||||||
|
|
||||||
|
table = {
|
||||||
|
"margin-bottom": "15px",
|
||||||
|
"min-width": "100%"
|
||||||
|
}
|
||||||
|
|
||||||
|
sidebar = {
|
||||||
|
"position": "fixed",
|
||||||
|
"top": 0,
|
||||||
|
"left": 0,
|
||||||
|
"bottom": 0,
|
||||||
|
"width": "16rem",
|
||||||
|
"padding": "2rem 1rem",
|
||||||
|
"background-color": "#363636"
|
||||||
|
}
|
||||||
|
|
||||||
|
content = {
|
||||||
|
"margin-left": "18rem",
|
||||||
|
"margin-right": "2rem",
|
||||||
|
"padding": "2rem 1rem",
|
||||||
|
}
|
||||||
|
|
||||||
|
misc = {
|
||||||
|
"color": "#757575",
|
||||||
|
"text-align": "center"
|
||||||
|
}
|
||||||
|
|
||||||
|
logo = {
|
||||||
|
"color": "#ffffff",
|
||||||
|
"text-align": "center"
|
||||||
|
}
|
||||||
|
|
||||||
|
white = {
|
||||||
|
"color": "#ffffff"
|
||||||
|
}
|
8
anisotropy/gui/utils.py
Normal file
8
anisotropy/gui/utils.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
def getSize(bytes):
|
||||||
|
for unit in ["", "K", "M", "G", "T", "P"]:
|
||||||
|
if bytes < 1024:
|
||||||
|
return f"{bytes:.2f} {unit}iB"
|
||||||
|
|
||||||
|
bytes /= 1024
|
16
anisotropy/meshing/__init__.py
Normal file
16
anisotropy/meshing/__init__.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
from . import conversion
|
||||||
|
from . import metrics
|
||||||
|
|
||||||
|
from .mesh import Mesh, MeshingParameters
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"utils",
|
||||||
|
"conversion",
|
||||||
|
"metrics",
|
||||||
|
"Mesh",
|
||||||
|
"MeshingParameters"
|
||||||
|
]
|
145
anisotropy/meshing/conversion.py
Normal file
145
anisotropy/meshing/conversion.py
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
|
||||||
|
from numpy import ndarray
|
||||||
|
from os import PathLike
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
|
def write_neutral(
|
||||||
|
points: ndarray,
|
||||||
|
cells: list[utils.CellBlock],
|
||||||
|
dim: int,
|
||||||
|
filename: PathLike
|
||||||
|
):
|
||||||
|
"""Write mesh to the netgen file (Neutral format [.mesh]).
|
||||||
|
|
||||||
|
:param points:
|
||||||
|
Array of points.
|
||||||
|
:param cells:
|
||||||
|
List of cell blocks.
|
||||||
|
:param dim:
|
||||||
|
Dimension of the mesh.
|
||||||
|
:param filename:
|
||||||
|
Path of the file.
|
||||||
|
"""
|
||||||
|
precision = "6f"
|
||||||
|
floatValue = "{value:>{width}." + precision + "}"
|
||||||
|
intValue = "{value:>{width}}"
|
||||||
|
outfile = open(Path(filename).resolve(), "w")
|
||||||
|
|
||||||
|
# write points
|
||||||
|
outfile.write(str(len(points)) + "\n")
|
||||||
|
|
||||||
|
for n in range(len(points)):
|
||||||
|
point = points[n]
|
||||||
|
|
||||||
|
outfile.write(floatValue.format(value = point[0], width = 10) + " ")
|
||||||
|
outfile.write(floatValue.format(value = point[1], width = 9) + " ")
|
||||||
|
|
||||||
|
if dim == 3:
|
||||||
|
outfile.write(floatValue.format(value = point[2], width = 9) + " ")
|
||||||
|
|
||||||
|
outfile.write("\n")
|
||||||
|
|
||||||
|
# write volume cells
|
||||||
|
if dim == 3:
|
||||||
|
count = sum([ len(cell.data) for cell in cells if cell.dim == 3])
|
||||||
|
outfile.write(str(count) + "\n")
|
||||||
|
|
||||||
|
for cellBlock in cells:
|
||||||
|
if cellBlock.dim == 3:
|
||||||
|
for cell in cellBlock.data:
|
||||||
|
outfile.write(intValue.format(value = 1, width = 4))
|
||||||
|
|
||||||
|
for pointId in cell:
|
||||||
|
outfile.write(" ")
|
||||||
|
# shift id up, in netgen it starts from one
|
||||||
|
outfile.write(intValue.format(value = pointId + 1, width = 8))
|
||||||
|
|
||||||
|
outfile.write("\n")
|
||||||
|
|
||||||
|
# write faces
|
||||||
|
count = sum([ len(cell.data) for cell in cells if cell.dim == 2])
|
||||||
|
outfile.write(str(count) + "\n")
|
||||||
|
|
||||||
|
for cellBlock in cells:
|
||||||
|
if cellBlock.dim == 2:
|
||||||
|
for index, cell in zip(cellBlock.indices, cellBlock.data):
|
||||||
|
outfile.write(intValue.format(value = index, width = 4) + " ")
|
||||||
|
|
||||||
|
for pointId in cell:
|
||||||
|
outfile.write(" ")
|
||||||
|
# shift id up, in netgen it starts from one
|
||||||
|
outfile.write(intValue.format(value = pointId + 1, width = 8))
|
||||||
|
|
||||||
|
outfile.write("\n")
|
||||||
|
|
||||||
|
# write segments
|
||||||
|
# important?
|
||||||
|
|
||||||
|
|
||||||
|
def read_neutral(filename: PathLike) -> tuple[list, list]:
|
||||||
|
"""Read mesh from netgen file (Neutral format [.mesh]).
|
||||||
|
|
||||||
|
:param filename:
|
||||||
|
Path of the file.
|
||||||
|
:return:
|
||||||
|
List of points and list of cell blocks.
|
||||||
|
"""
|
||||||
|
infile = open(Path(filename).resolve(), "r")
|
||||||
|
|
||||||
|
# read points from file, starts with single integer
|
||||||
|
npoints = int(infile.readline())
|
||||||
|
points = []
|
||||||
|
|
||||||
|
for n in range(npoints):
|
||||||
|
points.append(np.fromstring(infile.readline(), dtype = float, sep = " "))
|
||||||
|
|
||||||
|
# dimension
|
||||||
|
dim = None if len(points) == 0 else points[0].size
|
||||||
|
|
||||||
|
# read volume cells
|
||||||
|
nvolumes = int(infile.readline())
|
||||||
|
volume_indices = []
|
||||||
|
volumes = []
|
||||||
|
|
||||||
|
for n in range(nvolumes):
|
||||||
|
data = np.fromstring(infile.readline(), dtype = int, sep = " ")
|
||||||
|
volume_indices.append(data[0])
|
||||||
|
# shift node indices down, in netgen it starts from one, need from zero
|
||||||
|
volumes.append(data[1: ] - 1)
|
||||||
|
|
||||||
|
# read surface cells
|
||||||
|
nsurfaces = int(infile.readline())
|
||||||
|
surface_indices = []
|
||||||
|
surfaces = []
|
||||||
|
|
||||||
|
for n in range(nsurfaces):
|
||||||
|
data = np.fromstring(infile.readline(), dtype = int, sep = " ")
|
||||||
|
surface_indices.append(data[0])
|
||||||
|
# shift node indices down, in netgen it starts from one, need from zero
|
||||||
|
surfaces.append(data[1: ] - 1)
|
||||||
|
|
||||||
|
# read segment cells
|
||||||
|
# important?
|
||||||
|
|
||||||
|
# write data to object
|
||||||
|
points = np.asarray(points)
|
||||||
|
cells = []
|
||||||
|
|
||||||
|
if len(volumes) > 0:
|
||||||
|
cells += utils.collect_cells(volumes, 3, volume_indices)
|
||||||
|
|
||||||
|
if len(surfaces) > 0:
|
||||||
|
cellBlocks = utils.collect_cells(surfaces, 2, surface_indices)
|
||||||
|
|
||||||
|
if dim == 3:
|
||||||
|
for cellBlock in cellBlocks:
|
||||||
|
cellBlock.is_boundary = True
|
||||||
|
|
||||||
|
cells += cellBlocks
|
||||||
|
|
||||||
|
return points, cells
|
242
anisotropy/meshing/mesh.py
Normal file
242
anisotropy/meshing/mesh.py
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# This file is part of anisotropy.
|
||||||
|
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
from numpy import ndarray
|
||||||
|
from os import PathLike
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import netgen.occ as ng_occ
|
||||||
|
import netgen.meshing as ng_meshing
|
||||||
|
import meshio
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
from . import metrics
|
||||||
|
from . import conversion
|
||||||
|
|
||||||
|
|
||||||
|
class NoGeometrySpecified(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NotSupportedMeshFormat(Exception):
|
||||||
|
"""Exception for not supported mesh format IO operations.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MeshingParameters:
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
"""A MeshingParameters object contains parameters for netgen mesher.
|
||||||
|
"""
|
||||||
|
self.preset(2, **kwargs)
|
||||||
|
|
||||||
|
def preset(self, key: int, **kwargs):
|
||||||
|
"""Apply predefined parameters.
|
||||||
|
|
||||||
|
:param key:
|
||||||
|
0: very_coarse
|
||||||
|
1: coarse
|
||||||
|
2: moderate
|
||||||
|
3: fine
|
||||||
|
4: very_fine
|
||||||
|
"""
|
||||||
|
self.__dict__.update(kwargs)
|
||||||
|
|
||||||
|
self.maxh = kwargs.get("maxh", 0.1)
|
||||||
|
self.curvaturesafety = kwargs.get("curvaturesafety", [1, 1.5, 2, 3, 5][key])
|
||||||
|
self.segmentsperedge = kwargs.get("segmentsperedge", [0.3, 0.5, 1, 2, 3][key])
|
||||||
|
self.grading = kwargs.get("grading", [0.7, 0.5, 0.3, 0.2, 0.1][key])
|
||||||
|
self.chartdistfac = kwargs.get("chartdistfac", [0.8, 1, 1.5, 2, 5][key])
|
||||||
|
self.linelengthfac = kwargs.get("linelengthfac", [0.2, 0.35, 0.5, 1.5, 3][key])
|
||||||
|
self.closeedgefac = kwargs.get("closeedgefac", [0.5, 1, 2, 3.5, 5][key])
|
||||||
|
self.minedgelen = kwargs.get("minedgelen", [0.002, 0.02, 0.2, 1.0, 2.0][key])
|
||||||
|
self.surfmeshcurvfac = kwargs.get("surfmeshcurvfac", [1, 1.5, 2, 3, 5.0][key])
|
||||||
|
self.optsteps2d = kwargs.get("optsteps2d", 5)
|
||||||
|
self.optsteps3d = kwargs.get("optsetps3d", 5)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get(self) -> ng_meshing.MeshingParameters:
|
||||||
|
return ng_meshing.MeshingParameters(**self.__dict__)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
items = [ f"{ key }: { val }" for key, val in self.__dict__.items() ]
|
||||||
|
|
||||||
|
return "<MeshingParameters, " + ", ".join(items) + ">"
|
||||||
|
|
||||||
|
|
||||||
|
class Mesh:
|
||||||
|
def __init__(self, shape = None):
|
||||||
|
"""A Mesh object contains a mesh
|
||||||
|
|
||||||
|
:param shape:
|
||||||
|
OCC shape.
|
||||||
|
"""
|
||||||
|
self.shape = shape
|
||||||
|
self.mesh = None
|
||||||
|
|
||||||
|
self.points = []
|
||||||
|
self.cells = []
|
||||||
|
|
||||||
|
self._dim = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dim(self) -> float | None:
|
||||||
|
"""Mesh dimension.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
float or None if mesh contains no points.
|
||||||
|
"""
|
||||||
|
return None if len(self.points) == 0 else self.points[0].size
|
||||||
|
|
||||||
|
@property
|
||||||
|
def geometry(self) -> ng_occ.OCCGeometry | None:
|
||||||
|
"""Netgen OCCGeometry object.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
OCCGeometry object that can be used for meshing
|
||||||
|
or None if shape is not defined.
|
||||||
|
"""
|
||||||
|
if self.shape is not None:
|
||||||
|
if hasattr(self.shape, "geometry"):
|
||||||
|
return self.shape.geometry
|
||||||
|
|
||||||
|
else:
|
||||||
|
return ng_occ.OCCGeometry(self.shape)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def generate(
|
||||||
|
self,
|
||||||
|
parameters: ng_meshing.MeshingParameters = None,
|
||||||
|
refineSteps: int = 0,
|
||||||
|
refineOptimize: bool = True,
|
||||||
|
scale: float = 0
|
||||||
|
):
|
||||||
|
"""Generate mesh using netgen mesher on occ geometry.
|
||||||
|
|
||||||
|
:param parameters:
|
||||||
|
Meshing parameters object.
|
||||||
|
:param refineSteps:
|
||||||
|
Refine mesh after main meshing.
|
||||||
|
:param refineOptimize:
|
||||||
|
If True, optimize surface and volume mesh after each refine step.
|
||||||
|
:param scale:
|
||||||
|
Scale mesh after all operations.
|
||||||
|
"""
|
||||||
|
if not self.geometry:
|
||||||
|
raise NoGeometrySpecified("Cannot build mesh without geometry")
|
||||||
|
|
||||||
|
parameters = parameters or MeshingParameters()
|
||||||
|
# generate volume mesh
|
||||||
|
mesh = self.geometry.GenerateMesh(parameters.get())
|
||||||
|
|
||||||
|
if refineSteps > 0:
|
||||||
|
for n in range(refineSteps):
|
||||||
|
mesh.Refine()
|
||||||
|
|
||||||
|
# optimize surface and volume mesh
|
||||||
|
if refineOptimize:
|
||||||
|
mesh.OptimizeMesh2d(parameters.get())
|
||||||
|
mesh.OptimizeVolumeMesh(parameters.get())
|
||||||
|
|
||||||
|
if scale > 0:
|
||||||
|
mesh.Scale(scale)
|
||||||
|
|
||||||
|
self.points = utils.extract_netgen_points(mesh)
|
||||||
|
self.cells = []
|
||||||
|
|
||||||
|
if mesh.dim == 3:
|
||||||
|
volume_cells, volume_indices = utils.extract_netgen_cells(mesh, 3)
|
||||||
|
self.cells += utils.collect_cells(volume_cells, 3, volume_indices)
|
||||||
|
|
||||||
|
surface_cells, surface_indices = utils.extract_netgen_cells(mesh, 2)
|
||||||
|
cell_blocks = utils.collect_cells(surface_cells, 2, surface_indices)
|
||||||
|
|
||||||
|
for cellBlock in cell_blocks:
|
||||||
|
cellBlock.is_boundary = True
|
||||||
|
|
||||||
|
self.cells += cell_blocks
|
||||||
|
|
||||||
|
# TODO: dim == 2
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def read(self, filename: str):
|
||||||
|
"""Import a mesh from the file.
|
||||||
|
|
||||||
|
:param filename:
|
||||||
|
Path of the file.
|
||||||
|
"""
|
||||||
|
path = pathlib.Path(filename).resolve()
|
||||||
|
ext = path.suffix[1: ]
|
||||||
|
|
||||||
|
# Force netgen neutral format
|
||||||
|
if ext == "mesh":
|
||||||
|
self.points, self.cells = conversion.read_neutral(path)
|
||||||
|
|
||||||
|
else:
|
||||||
|
mesh = meshio.read(path)
|
||||||
|
self.points = mesh.points
|
||||||
|
self.cells = mesh.cells
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def write(self, filename: PathLike):
|
||||||
|
"""Export mesh to the file.
|
||||||
|
|
||||||
|
:param filename:
|
||||||
|
Path of the file to store the given mesh in.
|
||||||
|
"""
|
||||||
|
path = pathlib.Path(filename).resolve()
|
||||||
|
ext = path.suffix[1: ]
|
||||||
|
|
||||||
|
# Force netgen neutral format
|
||||||
|
if ext == "mesh":
|
||||||
|
conversion.write_neutral(self.points, self.cells, self.dim, path)
|
||||||
|
|
||||||
|
else:
|
||||||
|
mesh = meshio.Mesh(self.points, self.cells)
|
||||||
|
mesh.write(path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def volumes(self) -> list[ndarray]:
|
||||||
|
"""Volumes.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
List of points.
|
||||||
|
"""
|
||||||
|
points = []
|
||||||
|
|
||||||
|
for cellBlock in self.cells:
|
||||||
|
if cellBlock.dim == 3:
|
||||||
|
points += [ *self.points[cellBlock.data] ]
|
||||||
|
|
||||||
|
return points
|
||||||
|
|
||||||
|
@property
|
||||||
|
def volume(self) -> float:
|
||||||
|
"""Volume of whole mesh.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Volume.
|
||||||
|
"""
|
||||||
|
return np.sum([ metrics.volume(cell) for cell in self.volumes ])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def faces(self) -> list[ndarray]:
|
||||||
|
"""Boundary faces.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
List of boundary faces.
|
||||||
|
"""
|
||||||
|
points = []
|
||||||
|
|
||||||
|
for cellBlock in self.cells:
|
||||||
|
if cellBlock.dim == 2 and cellBlock.is_boundary:
|
||||||
|
points += [ *self.points[cellBlock.data] ]
|
||||||
|
|
||||||
|
return points
|
43
anisotropy/meshing/metrics.py
Normal file
43
anisotropy/meshing/metrics.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# This file is part of anisotropy.
|
||||||
|
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from numpy import ndarray
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
|
def volume(points: ndarray) -> ndarray:
|
||||||
|
"""Volume of the cell.
|
||||||
|
|
||||||
|
:param points:
|
||||||
|
Array of points that represents volumetric cell.
|
||||||
|
:return:
|
||||||
|
Volume.
|
||||||
|
"""
|
||||||
|
cellType = utils.cell_type(3, len(points))
|
||||||
|
|
||||||
|
if cellType == "tetra":
|
||||||
|
return 1 / 6 * np.linalg.det(np.append(points.transpose(), np.array([[1, 1, 1, 1]]), axis = 0))
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception(f"Not supported cell type '{ cellType }'")
|
||||||
|
|
||||||
|
|
||||||
|
def area(points: ndarray) -> ndarray:
|
||||||
|
"""Area of the cell.
|
||||||
|
|
||||||
|
:param points:
|
||||||
|
Array of points that represents surface cell.
|
||||||
|
:return:
|
||||||
|
Area.
|
||||||
|
"""
|
||||||
|
cellType = utils.cell_type(2, len(points))
|
||||||
|
vecs = np.roll(points, shift = -1, axis = 0) - points
|
||||||
|
|
||||||
|
if cellType == "triangle":
|
||||||
|
return 0.5 * np.abs(np.cross(*vecs[ :-1]))
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception(f"Not supported cell type '{ cellType }'")
|
178
anisotropy/meshing/utils.py
Normal file
178
anisotropy/meshing/utils.py
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from numpy import ndarray
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import netgen.meshing as ng_meshing
|
||||||
|
import meshio
|
||||||
|
|
||||||
|
|
||||||
|
topo_dim = meshio._mesh.topological_dimension
|
||||||
|
cell_nodes = meshio._common.num_nodes_per_cell
|
||||||
|
|
||||||
|
|
||||||
|
def cell_type(dimension: int, num_nodes: int) -> str | None:
|
||||||
|
"""Detect a cell topology type.
|
||||||
|
|
||||||
|
:param dimension:
|
||||||
|
Cell dimension.
|
||||||
|
:param num_nodes:
|
||||||
|
Number of nodes in the cell.
|
||||||
|
:return:
|
||||||
|
Cell type or None if cell type if not supported.
|
||||||
|
"""
|
||||||
|
for dim in topo_dim.keys():
|
||||||
|
for num in cell_nodes.keys():
|
||||||
|
if (
|
||||||
|
topo_dim[dim] == dimension and
|
||||||
|
cell_nodes[num] == num_nodes and
|
||||||
|
dim == num
|
||||||
|
):
|
||||||
|
return dim
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class CellBlock:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
cellType: str,
|
||||||
|
data: list | ndarray,
|
||||||
|
tags: list[str] = [],
|
||||||
|
indices: list | ndarray = [],
|
||||||
|
is_boundary: bool = False,
|
||||||
|
names: list[str] = []
|
||||||
|
):
|
||||||
|
"""A CellBlock object contains cells information of the same type.
|
||||||
|
|
||||||
|
:param cellType:
|
||||||
|
Type of the cell.
|
||||||
|
:param data:
|
||||||
|
Array of cells.
|
||||||
|
:param tags:
|
||||||
|
Some cell tags.
|
||||||
|
:param indices:
|
||||||
|
Array of indices of cells. Must be the same length as data length.
|
||||||
|
Usefull for detecting boundary faces, etc.
|
||||||
|
:param is_boundary:
|
||||||
|
Flag that cells are not internal.
|
||||||
|
:param names:
|
||||||
|
Array of names of cells. Must be the same length as data length.
|
||||||
|
Usefull for detecting boundary faces, etc.
|
||||||
|
"""
|
||||||
|
self.type = cellType
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
if cellType.startswith("polyhedron"):
|
||||||
|
self.dim = 3
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.data = np.asarray(self.data)
|
||||||
|
self.dim = topo_dim[cellType]
|
||||||
|
|
||||||
|
self.tags = tags
|
||||||
|
self.indices = np.asarray(indices)
|
||||||
|
self.is_boundary = is_boundary
|
||||||
|
self.names = names
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
items = [
|
||||||
|
"CellBlock",
|
||||||
|
f"type: { self.type }",
|
||||||
|
f"num cells: { len(self.data) }",
|
||||||
|
f"tags: { self.tags }",
|
||||||
|
]
|
||||||
|
return "<" + ", ".join(items) + ">"
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.data)
|
||||||
|
|
||||||
|
|
||||||
|
def collect_cells(cells: list, dim: int, indices: list | ndarray = None, cellType: str = None) -> list[CellBlock]:
|
||||||
|
"""Collect cell blocks from a list of cells.
|
||||||
|
|
||||||
|
:param cells:
|
||||||
|
List of cells.
|
||||||
|
:param dim:
|
||||||
|
Cell dimension.
|
||||||
|
:param indices:
|
||||||
|
List of cell indices. Must be the same length as list of cells.
|
||||||
|
:return:
|
||||||
|
List of cell blocks.
|
||||||
|
"""
|
||||||
|
cellBlocks = []
|
||||||
|
|
||||||
|
if indices is not None:
|
||||||
|
assert len(cells) == len(indices), "cells and indices arrays must be the same length"
|
||||||
|
|
||||||
|
for n, cell in enumerate(cells):
|
||||||
|
cellType = cellType or cell_type(dim, len(cell))
|
||||||
|
index = indices[n] if indices else None
|
||||||
|
is_added = False
|
||||||
|
|
||||||
|
# update cell block
|
||||||
|
for cellBlock in cellBlocks:
|
||||||
|
if cellBlock.type == cellType:
|
||||||
|
cellBlock.data += [ cell ]
|
||||||
|
|
||||||
|
if index:
|
||||||
|
cellBlock.indices.append(index)
|
||||||
|
|
||||||
|
is_added = True
|
||||||
|
|
||||||
|
# or create new
|
||||||
|
if not is_added:
|
||||||
|
cellBlock = CellBlock(cellType, [])
|
||||||
|
cellBlock.data = [ cell ]
|
||||||
|
|
||||||
|
if index:
|
||||||
|
cellBlock.indices = [ index ]
|
||||||
|
|
||||||
|
cellBlocks.append(cellBlock)
|
||||||
|
|
||||||
|
# convert arrays to numpy
|
||||||
|
for cellBlock in cellBlocks:
|
||||||
|
cellBlock.data = np.asarray(cellBlock.data)
|
||||||
|
cellBlock.indices = np.asarray(cellBlock.indices)
|
||||||
|
|
||||||
|
return cellBlocks
|
||||||
|
|
||||||
|
|
||||||
|
def extract_netgen_points(mesh: ng_meshing.Mesh) -> ndarray:
|
||||||
|
"""Extract points from netgen mesh object.
|
||||||
|
|
||||||
|
:param mesh:
|
||||||
|
Netgen mesh object.
|
||||||
|
:return:
|
||||||
|
Array of points.
|
||||||
|
"""
|
||||||
|
return np.array([ point.p for point in mesh.Points() ], dtype = float)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_netgen_cells(mesh: ng_meshing.Mesh, dim: int) -> tuple[list, list]:
|
||||||
|
"""Extract cells from netgen mesh according to cell dimension.
|
||||||
|
|
||||||
|
:param mesh:
|
||||||
|
Netgen mesh object.
|
||||||
|
:param dim:
|
||||||
|
Cell dimension.
|
||||||
|
:return:
|
||||||
|
List of cells and list of indices.
|
||||||
|
"""
|
||||||
|
cells = []
|
||||||
|
indices = []
|
||||||
|
elements = {
|
||||||
|
0: mesh.Elements0D(),
|
||||||
|
1: mesh.Elements1D(),
|
||||||
|
2: mesh.Elements2D(),
|
||||||
|
3: mesh.Elements3D()
|
||||||
|
}[dim]
|
||||||
|
|
||||||
|
if len(elements) > 0:
|
||||||
|
for cell in elements:
|
||||||
|
# shift nodes values, they should start from zero
|
||||||
|
nodes = np.array([ node.nr for node in cell.points ], dtype = int) - 1
|
||||||
|
cells.append(nodes)
|
||||||
|
indices.append(cell.index)
|
||||||
|
|
||||||
|
return cells, indices
|
||||||
|
|
Binary file not shown.
137
anisotropy/newgui/geometry.py
Normal file
137
anisotropy/newgui/geometry.py
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import dearpygui.dearpygui as dpg
|
||||||
|
|
||||||
|
def create_geometry(parent):
|
||||||
|
with dpg.child_window(
|
||||||
|
tag = "geometry_container",
|
||||||
|
parent = parent
|
||||||
|
) as container:
|
||||||
|
dpg.add_button(label = "Add case", callback = Case.create(container))
|
||||||
|
with dpg.group(horizontal = True):
|
||||||
|
dpg.add_text("Cases")
|
||||||
|
dpg.add_separator()
|
||||||
|
|
||||||
|
return container
|
||||||
|
|
||||||
|
class Case:
|
||||||
|
case_list = []
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
self.parent = parent
|
||||||
|
self.uuid = dpg.generate_uuid()
|
||||||
|
self.values = {}
|
||||||
|
Case.case_list.append(self)
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"<Case: { self.uuid }, type: geometry>"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create(parent):
|
||||||
|
def create_callback():
|
||||||
|
case = Case(parent)
|
||||||
|
|
||||||
|
with dpg.collapsing_header(label = f"g-case-{ case.uuid }", parent = case.parent) as case._case:
|
||||||
|
with dpg.child_window(height = 300) as case.child_window:
|
||||||
|
with dpg.group(horizontal = True):
|
||||||
|
case._source = dpg.add_combo(["", "from file", "periodic"], label = "Source")
|
||||||
|
|
||||||
|
dpg.add_button(label = "Remove", callback = case.remove_case)
|
||||||
|
dpg.add_button(label = "Edit", callback = case.edit_case)
|
||||||
|
|
||||||
|
case.create_stats()
|
||||||
|
|
||||||
|
case.create_params_file()
|
||||||
|
case.create_params_periodic()
|
||||||
|
|
||||||
|
return create_callback
|
||||||
|
|
||||||
|
def remove_case(self):
|
||||||
|
dpg.delete_item(self._case)
|
||||||
|
Case.case_list.remove(self)
|
||||||
|
|
||||||
|
def edit_case(self):
|
||||||
|
source_val = dpg.get_value(self._source)
|
||||||
|
|
||||||
|
if source_val == "from file":
|
||||||
|
dpg.show_item(self._file_params)
|
||||||
|
|
||||||
|
elif source_val == "periodic":
|
||||||
|
dpg.show_item(self._periodic_params)
|
||||||
|
|
||||||
|
def create_stats(self):
|
||||||
|
with dpg.table(header_row = True, parent = self.child_window) as self._case_stats:
|
||||||
|
dpg.add_table_column(label = "Parameters")
|
||||||
|
dpg.add_table_column(label = "")
|
||||||
|
|
||||||
|
for k, v in self.values.items():
|
||||||
|
with dpg.table_row():
|
||||||
|
dpg.add_text(k)
|
||||||
|
dpg.add_text(v)
|
||||||
|
|
||||||
|
def update_stats(self):
|
||||||
|
dpg.delete_item(self._case_stats)
|
||||||
|
self.create_stats()
|
||||||
|
|
||||||
|
def create_params_file(self):
|
||||||
|
with dpg.window(show = False, modal = True, height = 600, width = 400) as self._file_params:
|
||||||
|
with dpg.group(horizontal = True):
|
||||||
|
self._file_input = dpg.add_input_text(label = "File")
|
||||||
|
dpg.add_button(label = "..", callback = lambda: dpg.show_item(self._file_dialog))
|
||||||
|
|
||||||
|
with dpg.file_dialog(
|
||||||
|
directory_selector = False,
|
||||||
|
show = False,
|
||||||
|
modal = True,
|
||||||
|
callback = lambda s, d: dpg.set_value(self._file_input, d['file_path_name']),
|
||||||
|
height = 400,
|
||||||
|
width = 600
|
||||||
|
) as self._file_dialog:
|
||||||
|
dpg.add_file_extension(".geo")
|
||||||
|
|
||||||
|
dpg.add_button(label = "Save", width = -1)
|
||||||
|
|
||||||
|
def create_params_periodic(self):
|
||||||
|
def alpha_type_cb(s, d):
|
||||||
|
type_val = dpg.get_value(self._alpha_type)
|
||||||
|
|
||||||
|
if type_val == "float":
|
||||||
|
dpg.show_item(self._alpha_float)
|
||||||
|
dpg.hide_item(self._alpha_range)
|
||||||
|
|
||||||
|
elif type_val == "range":
|
||||||
|
dpg.hide_item(self._alpha_float)
|
||||||
|
dpg.show_item(self._alpha_range)
|
||||||
|
|
||||||
|
with dpg.window(show = False, modal = True, height = 600, width = 400) as self._periodic_params:
|
||||||
|
self._label_str = dpg.add_input_text(label = "label")
|
||||||
|
|
||||||
|
with dpg.group(horizontal = True):
|
||||||
|
self._alpha_type = dpg.add_combo(["float", "range"], default_value = "float", width = 100, callback = alpha_type_cb)
|
||||||
|
self._alpha_float = dpg.add_input_float(label = "alpha")
|
||||||
|
self._alpha_range = dpg.add_input_floatx(label = "alpha", size = 3, show = False)
|
||||||
|
|
||||||
|
self._r0_float = dpg.add_input_float(label = "r_0")
|
||||||
|
|
||||||
|
dpg.add_separator()
|
||||||
|
dpg.add_button(label = "Save", width = -1, callback = self.save_params)
|
||||||
|
|
||||||
|
def save_params(self):
|
||||||
|
source_val = dpg.get_value(self._source)
|
||||||
|
|
||||||
|
if source_val == "from file":
|
||||||
|
self.values.update(
|
||||||
|
filepath = dpg.get_value(self._file_input)
|
||||||
|
)
|
||||||
|
|
||||||
|
elif source_val == "periodic":
|
||||||
|
self.values.update(
|
||||||
|
label = dpg.get_value(self._label_str),
|
||||||
|
alpha = dpg.get_value(self._alpha_range) if dpg.get_value(self._alpha_type) == "range" else dpg.get_value(self._alpha_float),
|
||||||
|
r0 = dpg.get_value(self._r0_float),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.update_stats()
|
||||||
|
dpg.hide_item(self._periodic_params)
|
88
anisotropy/newgui/newgui.py
Normal file
88
anisotropy/newgui/newgui.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import dearpygui.dearpygui as dpg
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
import geometry
|
||||||
|
|
||||||
|
|
||||||
|
gui_path = pathlib.Path(__file__).resolve().parent
|
||||||
|
dpg.create_context()
|
||||||
|
|
||||||
|
|
||||||
|
with dpg.font_registry():
|
||||||
|
print(gui_path)
|
||||||
|
default_font = dpg.add_font(gui_path / "fonts/NotoSansMono-Regular-Nerd-Font-Complete.ttf", 20)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
with dpg.window(tag = "Primary") as primary:
|
||||||
|
dpg.bind_font(default_font)
|
||||||
|
|
||||||
|
with dpg.menu_bar(tag = "m1"):
|
||||||
|
with dpg.menu(label = "File"):
|
||||||
|
dpg.add_menu_item(label = "Preferences", callback = lambda: dpg.configure_item("preferences", show = True))
|
||||||
|
dpg.add_menu_item(label = "Exit", callback = lambda: dpg.destroy_context())
|
||||||
|
|
||||||
|
with dpg.menu(label = "Tools"):
|
||||||
|
dpg.add_menu_item(label = "Geometry", callback = lambda: dpg.show_item("geometry"))
|
||||||
|
dpg.add_menu_item(label = "Mesh", callback = lambda: dpg.show_item("mesh"))
|
||||||
|
dpg.add_menu_item(label = "Solver", callback = lambda: dpg.show_item("solver"))
|
||||||
|
dpg.add_menu_item(label = "Runner", callback = lambda: dpg.show_item("runner"))
|
||||||
|
dpg.add_separator()
|
||||||
|
dpg.add_menu_item(label = "Database", callback = lambda: dpg.show_item("database"))
|
||||||
|
dpg.add_menu_item(label = "Post-processing", callback = lambda: dpg.show_item("post-processing"))
|
||||||
|
|
||||||
|
with dpg.child_window(tag = "Test22", border = False, height = -40):
|
||||||
|
|
||||||
|
with dpg.child_window(tag = "Test", border = False):
|
||||||
|
with dpg.tab_bar(label = "tabbar"):
|
||||||
|
with dpg.tab(label = "Geometry", tag = "geometry", closable = True, show = False):
|
||||||
|
geometry.create_geometry("geometry")
|
||||||
|
|
||||||
|
with dpg.item_handler_registry(tag="widget handler"):
|
||||||
|
dpg.add_item_edited_handler(callback = lambda: dpg.delete_item("mesh") if not dpg.is_item_shown("mesh") else None)
|
||||||
|
|
||||||
|
with dpg.tab(label = "Mesh", tag = "mesh", closable = True, show = False):
|
||||||
|
with dpg.child_window(tag = "mesh_container", border = True):
|
||||||
|
dpg.add_text("")
|
||||||
|
|
||||||
|
# bind item handler registry to item
|
||||||
|
dpg.bind_item_handler_registry("mesh", "widget handler")
|
||||||
|
|
||||||
|
with dpg.tab(label = "Solver", tag = "solver", closable = True, show = False):
|
||||||
|
with dpg.child_window(tag = "solver_container", border = True):
|
||||||
|
dpg.add_text("")
|
||||||
|
|
||||||
|
with dpg.tab(label = "Runner", tag = "runner", closable = True, show = False):
|
||||||
|
with dpg.child_window(tag = "runner_container", border = True):
|
||||||
|
dpg.add_text("")
|
||||||
|
|
||||||
|
with dpg.tab(label = "Database", tag = "database", closable = True, show = False):
|
||||||
|
with dpg.child_window(tag = "database_container", border = True):
|
||||||
|
dpg.add_text("")
|
||||||
|
|
||||||
|
with dpg.tab(label = "Post-processing", tag = "post-processing", closable = True, show = False):
|
||||||
|
with dpg.child_window(tag = "post-processing_container", border = True):
|
||||||
|
dpg.add_text("")
|
||||||
|
|
||||||
|
dpg.add_separator()
|
||||||
|
with dpg.table(tag = "statusbar", header_row = False):
|
||||||
|
dpg.add_table_column(label = "")
|
||||||
|
dpg.add_table_column(label = "")
|
||||||
|
|
||||||
|
with dpg.table_row():
|
||||||
|
dpg.add_button(label = "Debug", callback = lambda: print(dpg.get_value("mesh")))
|
||||||
|
dpg.add_text("status")
|
||||||
|
|
||||||
|
with dpg.window(label = "Preferences", tag = "preferences", modal = True, show = False, width = 400, height = 600):
|
||||||
|
with dpg.collapsing_header(label = "General"):
|
||||||
|
with dpg.group(horizontal = True):
|
||||||
|
dpg.add_text("Font")
|
||||||
|
dpg.add_text(dpg.get_item_font("Primary"))
|
||||||
|
|
||||||
|
dpg.create_viewport(title = "New GUI", width = 1000, height = 800)
|
||||||
|
dpg.setup_dearpygui()
|
||||||
|
dpg.show_viewport()
|
||||||
|
dpg.set_primary_window("Primary", True)
|
||||||
|
dpg.start_dearpygui()
|
||||||
|
|
||||||
|
dpg.destroy_context()
|
@ -1,37 +1,22 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
from . import conversion
|
||||||
|
|
||||||
|
from .file import FoamFile
|
||||||
|
from .runner import FoamRunner
|
||||||
|
from .case import FoamCase
|
||||||
|
|
||||||
|
from . import presets
|
||||||
|
from . import commands
|
||||||
|
|
||||||
from .meshConversion import ideasUnvToFoam
|
|
||||||
from .meshManipulation import createPatch, transformPoints, checkMesh, renumberMesh
|
|
||||||
from .miscellaneous import foamDictionary
|
|
||||||
from .parallelProcessing import decomposePar
|
|
||||||
from .solvers import potentialFoam, simpleFoam
|
|
||||||
from .utils import version, foamClean, uniform
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# meshConversion
|
"utils",
|
||||||
"ideasUnvToFoam",
|
"conversion",
|
||||||
|
"FoamFile",
|
||||||
# meshManipulation
|
"FoamRunner",
|
||||||
"createPatch",
|
"FoamCase",
|
||||||
"transformPoints",
|
"presets",
|
||||||
"checkMesh",
|
"commands"
|
||||||
"renumberMesh",
|
|
||||||
|
|
||||||
# miscellaneous
|
|
||||||
"foamDictionary",
|
|
||||||
|
|
||||||
# parallelProcessing
|
|
||||||
"decomposePar",
|
|
||||||
|
|
||||||
# solvers
|
|
||||||
"potentialFoam",
|
|
||||||
"simpleFoam",
|
|
||||||
|
|
||||||
# utils
|
|
||||||
"version",
|
|
||||||
"foamClean",
|
|
||||||
"uniform"
|
|
||||||
]
|
]
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
import os, sys
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
import logging
|
|
||||||
logger = logging.getLogger()
|
|
||||||
|
|
||||||
def application(name: str, *args: str, case: str = None, stderr: bool = True, useMPI: bool = False) -> int:
|
|
||||||
|
|
||||||
cmd = []
|
|
||||||
|
|
||||||
if useMPI:
|
|
||||||
nprocs = os.cpu_count()
|
|
||||||
cmd.extend(["mpirun", "-np", str(nprocs), "--oversubscribe"])
|
|
||||||
|
|
||||||
cmd.append(name)
|
|
||||||
|
|
||||||
if case:
|
|
||||||
cmd.extend(["-case", case])
|
|
||||||
|
|
||||||
if args:
|
|
||||||
cmd.extend([*args])
|
|
||||||
|
|
||||||
logger.info("{}: {}".format(name, [*args]))
|
|
||||||
logpath = os.path.join(case if case else "", "{}.log".format(name))
|
|
||||||
|
|
||||||
with subprocess.Popen(cmd,
|
|
||||||
stdout = subprocess.PIPE,
|
|
||||||
stderr = subprocess.PIPE) as p, \
|
|
||||||
open(logpath, "wb") as logfile:
|
|
||||||
|
|
||||||
for line in p.stdout:
|
|
||||||
#sys.stdout.buffer.write(line)
|
|
||||||
logfile.write(line)
|
|
||||||
|
|
||||||
#for line in p.stderr:
|
|
||||||
# logfile.write(line)
|
|
||||||
|
|
||||||
out, err = p.communicate()
|
|
||||||
logfile.write(err)
|
|
||||||
|
|
||||||
#if err and stderr:
|
|
||||||
# logger.error("""{}:
|
|
||||||
# {}""".format(name, str(err, "utf-8")))
|
|
||||||
|
|
||||||
return out, err, p.returncode
|
|
||||||
|
|
||||||
|
|
174
anisotropy/openfoam/case.py
Normal file
174
anisotropy/openfoam/case.py
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import re
|
||||||
|
import pathlib
|
||||||
|
import io
|
||||||
|
from lz.reversal import reverse as lz_reverse
|
||||||
|
|
||||||
|
from . import FoamFile
|
||||||
|
|
||||||
|
|
||||||
|
class FoamCase(object):
|
||||||
|
def __init__(self, files: FoamFile | list[FoamFile] = None, path: str = None):
|
||||||
|
|
||||||
|
self.path = path
|
||||||
|
self._backpath = None
|
||||||
|
self._files = []
|
||||||
|
|
||||||
|
if files is not None:
|
||||||
|
self.add(files)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
content = [ file.object for file in self._files ]
|
||||||
|
|
||||||
|
return "<FoamCase: {}>".format(", ".join(content) or None)
|
||||||
|
|
||||||
|
def add(self, files: FoamFile | list[FoamFile]):
|
||||||
|
if type(files) is not list:
|
||||||
|
assert type(files) is FoamFile, "passed object is not a FoamFile"
|
||||||
|
|
||||||
|
files = [ files ]
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
assert type(file) is FoamFile, "passed object is not a FoamFile"
|
||||||
|
assert file.object is not None, "FoamFile object attribute is None"
|
||||||
|
|
||||||
|
idn = self.contains(file.object)
|
||||||
|
|
||||||
|
if idn is not None:
|
||||||
|
self._files.pop(idn)
|
||||||
|
self._files.append(file)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
self._files.append(file)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __add__(self, files: FoamFile | list[FoamFile]):
|
||||||
|
return self.add(files)
|
||||||
|
|
||||||
|
def contains(self, name: str) -> int | None:
|
||||||
|
for n, file in enumerate(self._files):
|
||||||
|
if file.object == name:
|
||||||
|
return n
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def write(self, path: str = None):
|
||||||
|
path = pathlib.Path(path or self.path or "")
|
||||||
|
|
||||||
|
for file in self._files:
|
||||||
|
path_ = path / (
|
||||||
|
file.location + "/" + file.object
|
||||||
|
if file.location else file.object
|
||||||
|
)
|
||||||
|
|
||||||
|
file.write(path_.resolve())
|
||||||
|
|
||||||
|
def read(self, path: str = None):
|
||||||
|
path = pathlib.Path(path or self.path or "")
|
||||||
|
|
||||||
|
for file in self._files:
|
||||||
|
path_ = path / (
|
||||||
|
file.location + "/" + file.object
|
||||||
|
if file.location else file.object
|
||||||
|
)
|
||||||
|
|
||||||
|
file.read(path_.resolve())
|
||||||
|
|
||||||
|
def remove(self, path: str = None):
|
||||||
|
path = pathlib.Path(path or self.path or "")
|
||||||
|
|
||||||
|
for file in self._files:
|
||||||
|
path_ = path / (
|
||||||
|
file.location + "/" + file.object
|
||||||
|
if file.location else file.object
|
||||||
|
)
|
||||||
|
|
||||||
|
file.remove(path_.resolve())
|
||||||
|
|
||||||
|
def clean(self, included: list = ["0", "constant", "system"]):
|
||||||
|
regxs = [
|
||||||
|
r"^\d+.\d+$",
|
||||||
|
r"^\d+$",
|
||||||
|
r"^processor\d+$",
|
||||||
|
r"^log..+$",
|
||||||
|
r"^.+.log$",
|
||||||
|
r"^logs$",
|
||||||
|
r"^postProcessing$",
|
||||||
|
r"^polyMesh$"
|
||||||
|
]
|
||||||
|
excluded = []
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(os.path.abspath("")):
|
||||||
|
for _dir in dirs:
|
||||||
|
excluded += [
|
||||||
|
os.path.join(root, _dir)
|
||||||
|
for regx in regxs if re.match(regx, _dir)
|
||||||
|
]
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
excluded += [
|
||||||
|
os.path.join(root, file)
|
||||||
|
for regx in regxs if re.match(regx, file)
|
||||||
|
]
|
||||||
|
|
||||||
|
for file in excluded:
|
||||||
|
if os.path.split(file)[1] not in included and os.path.exists(file):
|
||||||
|
if os.path.isdir(file):
|
||||||
|
shutil.rmtree(file)
|
||||||
|
|
||||||
|
if os.path.isfile(file):
|
||||||
|
os.remove(file)
|
||||||
|
|
||||||
|
def chdir(self, path: str = None):
|
||||||
|
path = pathlib.Path(path or self.path or "").resolve()
|
||||||
|
self._backpath = os.getcwd()
|
||||||
|
|
||||||
|
os.chdir(path)
|
||||||
|
|
||||||
|
def chback(self):
|
||||||
|
path = pathlib.Path(self._backpath or "").resolve()
|
||||||
|
|
||||||
|
os.chdir(path)
|
||||||
|
|
||||||
|
def is_converged(self, path: str = None) -> None | bool:
|
||||||
|
path = pathlib.Path(path or self.path or "").resolve()
|
||||||
|
controlDict = FoamFile()
|
||||||
|
|
||||||
|
if controlDict.exists(path / "system" / "controlDict"):
|
||||||
|
controlDict.read(path / "system" / "controlDict")
|
||||||
|
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
application = controlDict.get("application")
|
||||||
|
|
||||||
|
if application is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
logfile = (
|
||||||
|
path / f"{ application }.log"
|
||||||
|
if (path / f"{ application }.log").exists() else (path / f"log.{ application }")
|
||||||
|
)
|
||||||
|
status = False
|
||||||
|
|
||||||
|
if logfile.exists():
|
||||||
|
with open(logfile, "r") as infile:
|
||||||
|
limit = 30
|
||||||
|
|
||||||
|
for line in lz_reverse(infile, batch_size = io.DEFAULT_BUFFER_SIZE):
|
||||||
|
if not line.find("End") < 0:
|
||||||
|
status = True
|
||||||
|
|
||||||
|
if limit <= 0:
|
||||||
|
status = False
|
||||||
|
|
||||||
|
limit -= 1
|
||||||
|
|
||||||
|
return status
|
211
anisotropy/openfoam/commands.py
Normal file
211
anisotropy/openfoam/commands.py
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import FoamRunner
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# meshConversion
|
||||||
|
##
|
||||||
|
|
||||||
|
def netgenNeutralToFoam(meshfile: str, run: bool = True, **kwargs) -> FoamRunner:
|
||||||
|
command = "netgenNeutralToFoam"
|
||||||
|
kwargs.update(logpath = kwargs.get("logpath", f"{ command }.log"))
|
||||||
|
kwargs.update(exit = True)
|
||||||
|
args = [ meshfile ]
|
||||||
|
|
||||||
|
runner = FoamRunner(command, args = args, **kwargs)
|
||||||
|
|
||||||
|
if run:
|
||||||
|
runner.run()
|
||||||
|
|
||||||
|
return runner
|
||||||
|
|
||||||
|
|
||||||
|
def ideasUnvToFoam(meshfile: str, run: bool = True, **kwargs) -> FoamRunner:
|
||||||
|
command = "ideasUnvToFoam"
|
||||||
|
kwargs.update(logpath = kwargs.get("logpath", f"{ command }.log"))
|
||||||
|
kwargs.update(exit = True)
|
||||||
|
args = [ meshfile ]
|
||||||
|
|
||||||
|
runner = FoamRunner(command, args = args, **kwargs)
|
||||||
|
|
||||||
|
if run:
|
||||||
|
runner.run()
|
||||||
|
|
||||||
|
return runner
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# meshManipulation
|
||||||
|
##
|
||||||
|
|
||||||
|
def createPatch(dictfile: str = None, overwrite: bool = True, run: bool = True, **kwargs) -> FoamRunner:
|
||||||
|
command = "createPatch"
|
||||||
|
kwargs.update(logpath = kwargs.get("logpath", f"{ command }.log"))
|
||||||
|
kwargs.update(exit = True)
|
||||||
|
args = []
|
||||||
|
|
||||||
|
if dictfile:
|
||||||
|
args.extend(["-dict", dictfile])
|
||||||
|
|
||||||
|
if overwrite:
|
||||||
|
args.append("-overwrite")
|
||||||
|
|
||||||
|
runner = FoamRunner(command, args = args, **kwargs)
|
||||||
|
|
||||||
|
if run:
|
||||||
|
runner.run()
|
||||||
|
|
||||||
|
return runner
|
||||||
|
|
||||||
|
|
||||||
|
def transformPoints(transformations: dict, run: bool = True, **kwargs) -> FoamRunner:
|
||||||
|
command = "transformPoints"
|
||||||
|
kwargs.update(logpath = kwargs.get("logpath", f"{ command }.log"))
|
||||||
|
kwargs.update(exit = True)
|
||||||
|
args = []
|
||||||
|
arg = []
|
||||||
|
|
||||||
|
for k, v in transformations.items():
|
||||||
|
if type(v) == int or type(v) == float:
|
||||||
|
value = str(v)
|
||||||
|
|
||||||
|
elif type(v) == tuple or type(v) == list:
|
||||||
|
value = "({} {} {})".format(*v)
|
||||||
|
|
||||||
|
arg.append("{}={}".format(k, value))
|
||||||
|
|
||||||
|
args.append(", ".join(arg))
|
||||||
|
|
||||||
|
runner = FoamRunner(command, args = args, **kwargs)
|
||||||
|
|
||||||
|
if run:
|
||||||
|
runner.run()
|
||||||
|
|
||||||
|
return runner
|
||||||
|
|
||||||
|
|
||||||
|
def checkMesh(allGeometry: bool = True, allTopology: bool = True, run: bool = True, **kwargs) -> FoamRunner:
|
||||||
|
command = "checkMesh"
|
||||||
|
kwargs.update(logpath = kwargs.get("logpath", f"{ command }.log"))
|
||||||
|
kwargs.update(exit = True)
|
||||||
|
args = []
|
||||||
|
|
||||||
|
if allGeometry:
|
||||||
|
args.append("-allGeometry")
|
||||||
|
|
||||||
|
if allTopology:
|
||||||
|
args.append("-allTopology")
|
||||||
|
|
||||||
|
runner = FoamRunner(command, args = args, **kwargs)
|
||||||
|
|
||||||
|
if run:
|
||||||
|
runner.run()
|
||||||
|
|
||||||
|
return runner
|
||||||
|
|
||||||
|
|
||||||
|
def renumberMesh(overwrite: bool = True, run: bool = True, **kwargs) -> FoamRunner:
|
||||||
|
command = "renumberMesh"
|
||||||
|
kwargs.update(logpath = kwargs.get("logpath", f"{ command }.log"))
|
||||||
|
kwargs.update(exit = True)
|
||||||
|
args = []
|
||||||
|
|
||||||
|
if overwrite:
|
||||||
|
args.append("-overwrite")
|
||||||
|
|
||||||
|
runner = FoamRunner(command, args = args, **kwargs)
|
||||||
|
|
||||||
|
if run:
|
||||||
|
runner.run()
|
||||||
|
|
||||||
|
return runner
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# miscellaneous
|
||||||
|
##
|
||||||
|
|
||||||
|
# def foamDictionary()
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# parallelProcessing
|
||||||
|
##
|
||||||
|
|
||||||
|
def decomposePar(run: bool = True, **kwargs) -> FoamRunner:
|
||||||
|
command = "decomposePar"
|
||||||
|
kwargs.update(logpath = kwargs.get("logpath", f"{command}.log"))
|
||||||
|
kwargs.update(exit = True)
|
||||||
|
args = []
|
||||||
|
|
||||||
|
runner = FoamRunner(command, args = args, **kwargs)
|
||||||
|
|
||||||
|
if run:
|
||||||
|
runner.run()
|
||||||
|
|
||||||
|
return runner
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# solvers
|
||||||
|
##
|
||||||
|
|
||||||
|
def potentialFoam(parallel: bool = False, run: bool = True, **kwargs) -> FoamRunner:
|
||||||
|
command = "potentialFoam"
|
||||||
|
kwargs.update(logpath = kwargs.get("logpath", f"{command}.log"))
|
||||||
|
kwargs.update(exit = True)
|
||||||
|
args = []
|
||||||
|
|
||||||
|
if parallel:
|
||||||
|
args.append("-parallel")
|
||||||
|
kwargs.update(mpi = True)
|
||||||
|
|
||||||
|
runner = FoamRunner(command, args = args, **kwargs)
|
||||||
|
|
||||||
|
if run:
|
||||||
|
runner.run()
|
||||||
|
|
||||||
|
return runner
|
||||||
|
|
||||||
|
|
||||||
|
def simpleFoam(parallel: bool = False, run: bool = True, **kwargs) -> FoamRunner:
|
||||||
|
command = "simpleFoam"
|
||||||
|
kwargs.update(logpath = kwargs.get("logpath", f"{command}.log"))
|
||||||
|
kwargs.update(exit = True)
|
||||||
|
args = []
|
||||||
|
|
||||||
|
if parallel:
|
||||||
|
args.append("-parallel")
|
||||||
|
kwargs.update(mpi = True)
|
||||||
|
|
||||||
|
runner = FoamRunner(command, args = args, **kwargs)
|
||||||
|
|
||||||
|
if run:
|
||||||
|
runner.run()
|
||||||
|
|
||||||
|
return runner
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# postProcessing
|
||||||
|
##
|
||||||
|
|
||||||
|
def postProcess(func: str = None, latestTime: bool = False, run: bool = True, **kwargs) -> FoamRunner:
|
||||||
|
command = "postProcess"
|
||||||
|
kwargs.update(logpath=kwargs.get("logpath", f"{command}.log"))
|
||||||
|
kwargs.update(exit = True)
|
||||||
|
args = []
|
||||||
|
|
||||||
|
if func:
|
||||||
|
args.extend(["-func", func])
|
||||||
|
|
||||||
|
if latestTime:
|
||||||
|
args.append("-latestTime")
|
||||||
|
|
||||||
|
runner = FoamRunner(command, args = args, **kwargs)
|
||||||
|
|
||||||
|
if run:
|
||||||
|
runner.run()
|
||||||
|
|
||||||
|
return runner
|
120
anisotropy/openfoam/conversion.py
Normal file
120
anisotropy/openfoam/conversion.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
from PyFoam.RunDictionary.ParsedParameterFile import ParsedParameterFile
|
||||||
|
from PyFoam.Basics.FoamFileGenerator import FoamFileGenerator
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
|
def read_foamfile(filename: str) -> tuple[dict, dict]:
|
||||||
|
"""Read a FoamFile.
|
||||||
|
|
||||||
|
:param filename:
|
||||||
|
Path to the file.
|
||||||
|
:return:
|
||||||
|
Two dictionaries that contains header and content information
|
||||||
|
of the FoamFile.
|
||||||
|
"""
|
||||||
|
path = pathlib.Path(filename).resolve()
|
||||||
|
ppf = ParsedParameterFile(path)
|
||||||
|
header = ppf.header or {}
|
||||||
|
content = ppf.content or {}
|
||||||
|
|
||||||
|
if type(content) == dict:
|
||||||
|
if content.get("internalField"):
|
||||||
|
content["internalField"] = np.asarray(content["internalField"])
|
||||||
|
|
||||||
|
return header, content
|
||||||
|
|
||||||
|
|
||||||
|
def write_foamfile(header: dict, content: dict, filename: str):
|
||||||
|
"""Write a FoamFile to the file.
|
||||||
|
|
||||||
|
:param header:
|
||||||
|
Header block with the FoamFile metadata.
|
||||||
|
:param content:
|
||||||
|
Content block of the FoamFile.
|
||||||
|
:param filename:
|
||||||
|
Path to the file.
|
||||||
|
"""
|
||||||
|
path = pathlib.Path(filename).resolve()
|
||||||
|
|
||||||
|
# preformat
|
||||||
|
header = (
|
||||||
|
FoamFileGenerator({}, header = header)
|
||||||
|
.makeString()[ :-2]
|
||||||
|
.replace("\n ", "\n" + 4 * " ")
|
||||||
|
)
|
||||||
|
content = (
|
||||||
|
FoamFileGenerator(content)
|
||||||
|
.makeString()[ :-1]
|
||||||
|
.replace("\n ", "\n" + 4 * " ")
|
||||||
|
.replace(" \t// " + 73 * "*" + " //", "")
|
||||||
|
.replace(" /* empty */ ", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(path, "w") as outfile:
|
||||||
|
outfile.write(utils.template(header, content) + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
def read_dat(filename: str):
|
||||||
|
"""Read dat file.
|
||||||
|
|
||||||
|
:param filename:
|
||||||
|
Path to the file.
|
||||||
|
:return:
|
||||||
|
Dictionary with arrays. Keys are created according file header
|
||||||
|
block or numerated with string numbers if header is not found.
|
||||||
|
"""
|
||||||
|
path = pathlib.Path(filename).resolve()
|
||||||
|
header = []
|
||||||
|
content = []
|
||||||
|
|
||||||
|
with open(path, "r") as infile:
|
||||||
|
for line in infile.readlines():
|
||||||
|
if line.startswith("#"):
|
||||||
|
header.append(line)
|
||||||
|
|
||||||
|
else:
|
||||||
|
content.append(line)
|
||||||
|
|
||||||
|
columns = []
|
||||||
|
|
||||||
|
if header[-1].find(":") < 0:
|
||||||
|
for column in header[-1].replace("#", "").split("\t"):
|
||||||
|
columns.append(column.strip())
|
||||||
|
|
||||||
|
header.pop(-1)
|
||||||
|
|
||||||
|
else:
|
||||||
|
for column in range(len(content[0].split("\t"))):
|
||||||
|
columns.append(str(column))
|
||||||
|
|
||||||
|
output = {}
|
||||||
|
|
||||||
|
for row in header:
|
||||||
|
key, value = row.replace("#", "").split(":")
|
||||||
|
|
||||||
|
try:
|
||||||
|
value = float(value.strip())
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
value = value.strip()
|
||||||
|
|
||||||
|
output[key.strip()] = value
|
||||||
|
|
||||||
|
for column in columns:
|
||||||
|
output[column] = []
|
||||||
|
|
||||||
|
for row in content:
|
||||||
|
values = row.split("\t")
|
||||||
|
|
||||||
|
for column, value in zip(columns, values):
|
||||||
|
output[column].append(float(value))
|
||||||
|
|
||||||
|
for key in output.keys():
|
||||||
|
output[key] = np.asarray(output[key])
|
||||||
|
|
||||||
|
return output
|
171
anisotropy/openfoam/file.py
Normal file
171
anisotropy/openfoam/file.py
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
from . import conversion
|
||||||
|
|
||||||
|
|
||||||
|
class FoamFile(object):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
filename: str = None,
|
||||||
|
_version: float = 2.0,
|
||||||
|
_format: str = "ascii",
|
||||||
|
_class: str = "dictionary",
|
||||||
|
_object: str = None,
|
||||||
|
_location: str = None
|
||||||
|
):
|
||||||
|
"""A FoamFile object.
|
||||||
|
|
||||||
|
:param filename:
|
||||||
|
Can be used as shortcut to set _location and _object,
|
||||||
|
_location and _object parameters will be ignored.
|
||||||
|
:param _version:
|
||||||
|
Version of the file format, current is 2.0.
|
||||||
|
:param _format:
|
||||||
|
ASCII or binary representation, currently ascii only
|
||||||
|
supported.
|
||||||
|
:param _class:
|
||||||
|
Class of the file.
|
||||||
|
:param _object:
|
||||||
|
Usually contains name of the file.
|
||||||
|
:param _location:
|
||||||
|
Path to the parent directory of the file according
|
||||||
|
to the case root.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if filename:
|
||||||
|
splitted = filename.split("/")
|
||||||
|
_object = splitted[-1]
|
||||||
|
_location = "/".join(splitted[ :-1])
|
||||||
|
|
||||||
|
self.header = {
|
||||||
|
"version": _version,
|
||||||
|
"format": _format,
|
||||||
|
"class": _class,
|
||||||
|
"object": _object
|
||||||
|
}
|
||||||
|
self.content = {}
|
||||||
|
|
||||||
|
if _location:
|
||||||
|
self.header["location"] = f'"{ _location }"'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version(self) -> str:
|
||||||
|
return self.header.get("version")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def format(self) -> str:
|
||||||
|
return self.header.get("format")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def class_(self) -> str:
|
||||||
|
return self.header.get("class")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def object(self) -> str:
|
||||||
|
return self.header.get("object")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def location(self) -> str:
|
||||||
|
return (
|
||||||
|
self.header.get("location").replace('"', "")
|
||||||
|
if self.header.get("location") else None
|
||||||
|
)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.content[key]
|
||||||
|
|
||||||
|
def get(self, key, default = None):
|
||||||
|
return self.content.get(key, default)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self.content[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
del self.content[key]
|
||||||
|
|
||||||
|
def update(self, **kwargs):
|
||||||
|
self.content.update(**kwargs)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.content)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
for key in self.content:
|
||||||
|
yield key
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "<FoamFile: {}>".format(self.header["object"] or None)
|
||||||
|
|
||||||
|
def __add__(self, file):
|
||||||
|
from . import FoamCase
|
||||||
|
|
||||||
|
assert type(file) is FoamFile
|
||||||
|
|
||||||
|
return FoamCase([ self, file ])
|
||||||
|
|
||||||
|
def exists(self, filename: str = None) -> bool:
|
||||||
|
filename = filename or (
|
||||||
|
self.location + "/" + self.object
|
||||||
|
if self.location else self.object
|
||||||
|
)
|
||||||
|
path = pathlib.Path(filename).resolve()
|
||||||
|
|
||||||
|
return path.exists() and path.is_file()
|
||||||
|
|
||||||
|
def read(self, filename: str = None):
|
||||||
|
"""Read a FoamFile.
|
||||||
|
|
||||||
|
:param filename:
|
||||||
|
Path to the file. If None, use location and object from header with
|
||||||
|
the current working directory.
|
||||||
|
:return:
|
||||||
|
Self.
|
||||||
|
"""
|
||||||
|
filename = filename or (
|
||||||
|
self.location + "/" + self.object
|
||||||
|
if self.location else self.object
|
||||||
|
)
|
||||||
|
path = pathlib.Path(filename).resolve()
|
||||||
|
header, content = conversion.read_foamfile(path)
|
||||||
|
|
||||||
|
self.header = header
|
||||||
|
|
||||||
|
if not header.get("object"):
|
||||||
|
self.header["object"] = path.parts[-1]
|
||||||
|
|
||||||
|
self.content = content
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def write(self, filename: str = None):
|
||||||
|
"""Write a FoamFile to the file.
|
||||||
|
|
||||||
|
:param filename:
|
||||||
|
Path to the file. If None, use location and object from header with
|
||||||
|
the current working directory.
|
||||||
|
"""
|
||||||
|
filename = filename or (
|
||||||
|
self.location + "/" + self.object
|
||||||
|
if self.location else self.object
|
||||||
|
)
|
||||||
|
path = pathlib.Path(filename).resolve()
|
||||||
|
path.parent.mkdir(parents = True, exist_ok = True)
|
||||||
|
conversion.write_foamfile(self.header, self.content, path)
|
||||||
|
|
||||||
|
def remove(self, filename: str = None):
|
||||||
|
"""Remove a FoamFile.
|
||||||
|
|
||||||
|
:param filename:
|
||||||
|
Path to the file. If None, use location and object from header with
|
||||||
|
the current working directory.
|
||||||
|
"""
|
||||||
|
filename = filename or (
|
||||||
|
self.location + "/" + self.object
|
||||||
|
if self.location else self.object
|
||||||
|
)
|
||||||
|
path = pathlib.Path(filename).resolve()
|
||||||
|
|
||||||
|
if path.exists():
|
||||||
|
pathlib.os.remove(filename)
|
@ -1,9 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
from .application import application
|
|
||||||
|
|
||||||
def ideasUnvToFoam(mesh: str, case: str = None) -> (str, int):
|
|
||||||
return application("ideasUnvToFoam", mesh, case = case, stderr = True)
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
from .application import application
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
def createPatch(dictfile: str = None, case: str = None):
|
|
||||||
args = ["-overwrite"]
|
|
||||||
|
|
||||||
if dictfile:
|
|
||||||
args.extend(["-dict", dictfile])
|
|
||||||
|
|
||||||
application("createPatch", *args, case = case, stderr = True)
|
|
||||||
|
|
||||||
|
|
||||||
def transformPoints(scale, case: str = None):
|
|
||||||
_scale = f"({ scale[0] } { scale[1] } { scale[2] })"
|
|
||||||
|
|
||||||
application("transformPoints", "-scale", _scale, case = case, stderr = True)
|
|
||||||
|
|
||||||
|
|
||||||
def checkMesh(case: str = None) -> str:
|
|
||||||
_, err, returncode = application("checkMesh", "-allGeometry", "-allTopology", case = case, stderr = True)
|
|
||||||
out = ""
|
|
||||||
|
|
||||||
with open("checkMesh.log", "r") as io:
|
|
||||||
warnings = []
|
|
||||||
for line in io:
|
|
||||||
if re.search("\*\*\*", line):
|
|
||||||
warnings.append(line.replace("***", "").strip())
|
|
||||||
|
|
||||||
if warnings:
|
|
||||||
out = "checkMesh:\n\t{}".format("\n\t".join(warnings))
|
|
||||||
|
|
||||||
return out, err, returncode
|
|
||||||
|
|
||||||
|
|
||||||
def renumberMesh(case: str = None):
|
|
||||||
application("renumberMesh", "-overwrite", useMPI = False, case = case, stderr = True)
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
from .application import application
|
|
||||||
|
|
||||||
def foamDictionary(filepath: str, entry: str, value: str = None, case: str = None):
|
|
||||||
args = [filepath, "-entry", entry]
|
|
||||||
|
|
||||||
if value:
|
|
||||||
args.extend(["-set", value])
|
|
||||||
|
|
||||||
application("foamDictionary", *args, case = case, stderr = False)
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
from .application import application
|
|
||||||
|
|
||||||
def decomposePar(case: str = None):
|
|
||||||
application("decomposePar", case = case, stderr = True)
|
|
||||||
|
|
223
anisotropy/openfoam/presets.py
Normal file
223
anisotropy/openfoam/presets.py
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import FoamFile
|
||||||
|
|
||||||
|
|
||||||
|
def controlDict() -> FoamFile:
|
||||||
|
ff = FoamFile(filename = "system/controlDict")
|
||||||
|
|
||||||
|
ff.content = {
|
||||||
|
"application": "simpleFoam",
|
||||||
|
"startFrom": "startTime",
|
||||||
|
"startTime": 0,
|
||||||
|
"stopAt": "endTime",
|
||||||
|
"endTime": 2000,
|
||||||
|
"deltaT": 1,
|
||||||
|
"writeControl": "timeStep",
|
||||||
|
"writeInterval": 100,
|
||||||
|
"purgeWrite": 0,
|
||||||
|
"writeFormat": "ascii",
|
||||||
|
"writePrecision": 6,
|
||||||
|
"writeCompression": "off",
|
||||||
|
"timeFormat": "general",
|
||||||
|
"timePrecision": 6,
|
||||||
|
"runTimeModifiable": "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ff
|
||||||
|
|
||||||
|
|
||||||
|
def fvSolution() -> FoamFile:
|
||||||
|
ff = FoamFile(filename = "system/fvSolution")
|
||||||
|
|
||||||
|
ff.content = {
|
||||||
|
"solvers": {
|
||||||
|
"p": {
|
||||||
|
"solver": "GAMG",
|
||||||
|
"tolerance": 1e-06,
|
||||||
|
"relTol": 0.1,
|
||||||
|
"smoother": "GaussSeidel"
|
||||||
|
},
|
||||||
|
"U": {
|
||||||
|
"solver": "smoothSolver",
|
||||||
|
"smoother": "symGaussSeidel",
|
||||||
|
"tolerance": 1e-05,
|
||||||
|
"relTol": 0.1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SIMPLE": {
|
||||||
|
"nNonOrthogonalCorrectors": 0,
|
||||||
|
"consistent": "yes",
|
||||||
|
"residualControl": {
|
||||||
|
"p": 1e-02,
|
||||||
|
"U": 1e-03
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relaxationFactors": {
|
||||||
|
"fields": {
|
||||||
|
"p": 0.3
|
||||||
|
},
|
||||||
|
"equations": {
|
||||||
|
"U": 0.7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ff
|
||||||
|
|
||||||
|
|
||||||
|
def fvSchemes() -> FoamFile:
|
||||||
|
ff = FoamFile(filename = "system/fvSchemes")
|
||||||
|
|
||||||
|
ff.content = {
|
||||||
|
"ddtSchemes": {
|
||||||
|
"default": "steadyState"
|
||||||
|
},
|
||||||
|
"gradSchemes": {
|
||||||
|
"default": ("Gauss", "linear")
|
||||||
|
},
|
||||||
|
"divSchemes": {
|
||||||
|
"default": "none",
|
||||||
|
"div(phi,U)": ("bounded", "Gauss", "linearUpwind", "grad(U)"),
|
||||||
|
"div((nuEff*dev2(T(grad(U)))))": ("Gauss", "linear"),
|
||||||
|
"div(nonlinearStress)": ("Gauss", "linear")
|
||||||
|
},
|
||||||
|
"laplacianSchemes": {
|
||||||
|
"default": ("Gauss", "linear", "corrected")
|
||||||
|
},
|
||||||
|
"interpolationSchemes": {
|
||||||
|
"default": "linear"
|
||||||
|
},
|
||||||
|
"snGradSchemes": {
|
||||||
|
"default": "corrected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ff
|
||||||
|
|
||||||
|
|
||||||
|
def transportProperties() -> FoamFile:
|
||||||
|
ff = FoamFile(filename = "constant/transportProperties")
|
||||||
|
|
||||||
|
ff.content = {
|
||||||
|
"transportModel": "Newtonian",
|
||||||
|
"nu": 1e-05
|
||||||
|
}
|
||||||
|
|
||||||
|
return ff
|
||||||
|
|
||||||
|
|
||||||
|
def turbulenceProperties() -> FoamFile:
|
||||||
|
ff = FoamFile(filename = "constant/turbulenceProperties")
|
||||||
|
|
||||||
|
ff.content = {
|
||||||
|
"simulationType": "RAS",
|
||||||
|
"RAS": {
|
||||||
|
"RASModel": "kEpsilon",
|
||||||
|
"turbulence": "on",
|
||||||
|
"printCoeffs": "on"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ff
|
||||||
|
|
||||||
|
|
||||||
|
def p() -> FoamFile:
|
||||||
|
ff = FoamFile("0/p", _class = "volScalarField")
|
||||||
|
|
||||||
|
ff.content = {
|
||||||
|
"dimensions": "[0 2 -2 0 0 0 0]",
|
||||||
|
"internalField": "uniform 0",
|
||||||
|
"boundaryField": {
|
||||||
|
"inlet": {
|
||||||
|
"type": "fixedValue",
|
||||||
|
"value": "uniform 0.001"
|
||||||
|
},
|
||||||
|
"outlet": {
|
||||||
|
"type": "fixedValue",
|
||||||
|
"value": "uniform 0"
|
||||||
|
},
|
||||||
|
"wall": {
|
||||||
|
"type": "zeroGradient"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ff
|
||||||
|
|
||||||
|
|
||||||
|
def U() -> FoamFile:
|
||||||
|
ff = FoamFile("0/U", _class = "volVectorField")
|
||||||
|
|
||||||
|
ff.content = {
|
||||||
|
"dimensions": "[0 1 -1 0 0 0 0]",
|
||||||
|
"internalField": "uniform (0 0 0)",
|
||||||
|
"boundaryField": {
|
||||||
|
"inlet": {
|
||||||
|
"type": "fixedValue",
|
||||||
|
"value": "uniform (0 0 -6e-5)"
|
||||||
|
},
|
||||||
|
"outlet": {
|
||||||
|
"type": "zeroGradient",
|
||||||
|
},
|
||||||
|
"wall": {
|
||||||
|
"type": "fixedValue",
|
||||||
|
"value": "uniform (0 0 0)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ff
|
||||||
|
|
||||||
|
|
||||||
|
def createPatchDict() -> FoamFile:
|
||||||
|
ff = FoamFile("system/createPatchDict")
|
||||||
|
|
||||||
|
ff.content = {
|
||||||
|
"pointSync": False,
|
||||||
|
"patches": [
|
||||||
|
{
|
||||||
|
"name": "inlet",
|
||||||
|
"patchInfo": {
|
||||||
|
"type": "patch",
|
||||||
|
"inGroups": ["inlet"]
|
||||||
|
},
|
||||||
|
"constructFrom": "patches",
|
||||||
|
"patches": ["some_inlet"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "output",
|
||||||
|
"patchInfo": {
|
||||||
|
"type": "patch",
|
||||||
|
"inGroups": ["outlet"]
|
||||||
|
},
|
||||||
|
"constructFrom": "patches",
|
||||||
|
"patches": ["some_outlet"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "wall",
|
||||||
|
"patchInfo": {
|
||||||
|
"type": "wall",
|
||||||
|
"inGroups": ["wall"]
|
||||||
|
},
|
||||||
|
"constructFrom": "patches",
|
||||||
|
"patches": ["some_wall"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ff
|
||||||
|
|
||||||
|
|
||||||
|
def decomposeParDict() -> FoamFile:
|
||||||
|
ff = FoamFile("system/decomposeParDict")
|
||||||
|
|
||||||
|
ff.content = {
|
||||||
|
"numberOfSubdomains": 4,
|
||||||
|
"method": "simple",
|
||||||
|
"coeffs": {
|
||||||
|
"n": [2, 2, 2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ff
|
80
anisotropy/openfoam/runner.py
Normal file
80
anisotropy/openfoam/runner.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class FoamRunner(object):
|
||||||
|
def __init__(self, command: str, args: list[str] = None, mpi: bool = False, cwd: str = None, logpath: str = None, exit: bool = False):
|
||||||
|
self.command = command
|
||||||
|
self.args = args
|
||||||
|
self.mpi = mpi
|
||||||
|
self.cwd = cwd or os.getcwd()
|
||||||
|
self.logpath = logpath
|
||||||
|
self.exit = exit
|
||||||
|
self.output = ""
|
||||||
|
self.error = ""
|
||||||
|
self.returncode = 0
|
||||||
|
|
||||||
|
def fullcommand(self) -> list[str]:
|
||||||
|
command = []
|
||||||
|
|
||||||
|
if self.mpi:
|
||||||
|
nprocs = os.cpu_count()
|
||||||
|
command.extend(["mpirun", "-np", str(nprocs), "--oversubscribe"])
|
||||||
|
|
||||||
|
command.append(self.command)
|
||||||
|
|
||||||
|
if self.args:
|
||||||
|
command.extend([ str(arg) for arg in self.args ])
|
||||||
|
|
||||||
|
return command
|
||||||
|
|
||||||
|
def run(self) -> tuple[str, str, int]:
|
||||||
|
try:
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
self.fullcommand(),
|
||||||
|
stdout = subprocess.PIPE,
|
||||||
|
stderr = subprocess.PIPE,
|
||||||
|
encoding = "utf-8",
|
||||||
|
cwd = self.cwd
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug(f"Starting subprocess: { proc.args }")
|
||||||
|
|
||||||
|
if self.logpath:
|
||||||
|
with proc, open(self.logpath, "w") as io:
|
||||||
|
while True:
|
||||||
|
output = proc.stdout.read(1)
|
||||||
|
|
||||||
|
if output == "" and proc.poll() is not None:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not output == "":
|
||||||
|
io.write(output)
|
||||||
|
|
||||||
|
error = proc.stderr.read()
|
||||||
|
|
||||||
|
if not error == "":
|
||||||
|
self.error = error
|
||||||
|
io.write(error)
|
||||||
|
|
||||||
|
self.returncode = proc.returncode
|
||||||
|
|
||||||
|
if self.logpath and self.error:
|
||||||
|
with open(self.logpath, "a") as io:
|
||||||
|
io.write(self.error)
|
||||||
|
|
||||||
|
except FileNotFoundError as err:
|
||||||
|
self.error = err.args[1]
|
||||||
|
self.returncode = 2
|
||||||
|
|
||||||
|
logger.error(self.error, exc_info = True)
|
||||||
|
|
||||||
|
if not self.returncode == 0 and self.exit:
|
||||||
|
raise Exception(f"Subprocess failed: { self.error }")
|
||||||
|
|
||||||
|
return self.output, self.error, self.returncode
|
@ -1,34 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
from .application import application
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
def potentialFoam(case: str = None, useMPI: bool = False):
|
|
||||||
if useMPI:
|
|
||||||
out, err, returncode = application("potentialFoam", "-parallel", useMPI = True, case = case, stderr = True)
|
|
||||||
|
|
||||||
else:
|
|
||||||
out, err, returncode = application("potentialFoam", case = case, stderr = True)
|
|
||||||
|
|
||||||
return out, err, returncode
|
|
||||||
|
|
||||||
|
|
||||||
def simpleFoam(case: str = None, useMPI: bool = False):
|
|
||||||
if useMPI:
|
|
||||||
out, err, returncode = application("simpleFoam", "-parallel", useMPI = True, case = case, stderr = True)
|
|
||||||
|
|
||||||
else:
|
|
||||||
out, err, returncode = application("simpleFoam", case = case, stderr = True)
|
|
||||||
|
|
||||||
out = ""
|
|
||||||
|
|
||||||
with open("simpleFoam.log", "r") as io:
|
|
||||||
for line in io:
|
|
||||||
if re.search("solution converged", line):
|
|
||||||
out = "simpleFoam:\n\t{}".format(line.strip())
|
|
||||||
|
|
||||||
return out, err, returncode
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
|||||||
/*--------------------------------*- C++ -*----------------------------------*\
|
|
||||||
| ========= | |
|
|
||||||
| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
|
|
||||||
| \\ / O peration | Version: v2012 |
|
|
||||||
| \\ / A nd | Website: www.openfoam.com |
|
|
||||||
| \\/ M anipulation | |
|
|
||||||
\*---------------------------------------------------------------------------*/
|
|
||||||
FoamFile
|
|
||||||
{
|
|
||||||
version 2.0;
|
|
||||||
format ascii;
|
|
||||||
class volVectorField;
|
|
||||||
location "0";
|
|
||||||
object U;
|
|
||||||
}
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
|
|
||||||
|
|
||||||
dimensions [0 1 -1 0 0 0 0];
|
|
||||||
|
|
||||||
|
|
||||||
internalField uniform (0 0 0);
|
|
||||||
|
|
||||||
boundaryField
|
|
||||||
{
|
|
||||||
inlet
|
|
||||||
{
|
|
||||||
type fixedValue;
|
|
||||||
value uniform (0 0 -6e-5);
|
|
||||||
}
|
|
||||||
outlet
|
|
||||||
{
|
|
||||||
type zeroGradient;
|
|
||||||
}
|
|
||||||
symetryPlane
|
|
||||||
{
|
|
||||||
type fixedValue;
|
|
||||||
value uniform (0 0 0);
|
|
||||||
}
|
|
||||||
wall
|
|
||||||
{
|
|
||||||
type fixedValue;
|
|
||||||
value uniform (0 0 0);
|
|
||||||
}
|
|
||||||
strips
|
|
||||||
{
|
|
||||||
type fixedValue;
|
|
||||||
value uniform (0 0 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultFaces
|
|
||||||
{
|
|
||||||
type fixedValue;
|
|
||||||
value uniform (0 0 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ************************************************************************* //
|
|
@ -1,55 +0,0 @@
|
|||||||
/*--------------------------------*- C++ -*----------------------------------*\
|
|
||||||
| ========= | |
|
|
||||||
| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
|
|
||||||
| \\ / O peration | Version: v2012 |
|
|
||||||
| \\ / A nd | Website: www.openfoam.com |
|
|
||||||
| \\/ M anipulation | |
|
|
||||||
\*---------------------------------------------------------------------------*/
|
|
||||||
FoamFile
|
|
||||||
{
|
|
||||||
version 2.0;
|
|
||||||
format ascii;
|
|
||||||
class volScalarField;
|
|
||||||
location "0";
|
|
||||||
object p;
|
|
||||||
}
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
|
|
||||||
|
|
||||||
dimensions [0 2 -2 0 0 0 0];
|
|
||||||
|
|
||||||
|
|
||||||
internalField uniform 0;
|
|
||||||
|
|
||||||
boundaryField
|
|
||||||
{
|
|
||||||
inlet
|
|
||||||
{
|
|
||||||
type fixedValue;
|
|
||||||
value uniform 0.001;
|
|
||||||
}
|
|
||||||
outlet
|
|
||||||
{
|
|
||||||
type fixedValue;
|
|
||||||
value uniform 0;
|
|
||||||
}
|
|
||||||
symetryPlane
|
|
||||||
{
|
|
||||||
type zeroGradient;
|
|
||||||
}
|
|
||||||
wall
|
|
||||||
{
|
|
||||||
type zeroGradient;
|
|
||||||
}
|
|
||||||
strips
|
|
||||||
{
|
|
||||||
type zeroGradient;
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultFaces
|
|
||||||
{
|
|
||||||
type zeroGradient;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ************************************************************************* //
|
|
@ -1,22 +0,0 @@
|
|||||||
/*--------------------------------*- C++ -*----------------------------------*\
|
|
||||||
| ========= | |
|
|
||||||
| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
|
|
||||||
| \\ / O peration | Version: v2012 |
|
|
||||||
| \\ / A nd | Website: www.openfoam.com |
|
|
||||||
| \\/ M anipulation | |
|
|
||||||
\*---------------------------------------------------------------------------*/
|
|
||||||
FoamFile
|
|
||||||
{
|
|
||||||
version 2.0;
|
|
||||||
format ascii;
|
|
||||||
class dictionary;
|
|
||||||
location "constant";
|
|
||||||
object transportProperties;
|
|
||||||
}
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
|
|
||||||
|
|
||||||
transportModel Newtonian;
|
|
||||||
|
|
||||||
nu 1e-06;
|
|
||||||
|
|
||||||
// ************************************************************************* //
|
|
@ -1,23 +0,0 @@
|
|||||||
/*--------------------------------*- C++ -*----------------------------------*\
|
|
||||||
| ========= | |
|
|
||||||
| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
|
|
||||||
| \\ / O peration | Version: v2012 |
|
|
||||||
| \\ / A nd | Website: www.openfoam.com |
|
|
||||||
| \\/ M anipulation | |
|
|
||||||
\*---------------------------------------------------------------------------*/
|
|
||||||
FoamFile
|
|
||||||
{
|
|
||||||
version 2.0;
|
|
||||||
format ascii;
|
|
||||||
class dictionary;
|
|
||||||
location "constant";
|
|
||||||
object turbulenceProperties;
|
|
||||||
}
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
|
|
||||||
|
|
||||||
simulationType laminar;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ************************************************************************* //
|
|
@ -1,28 +0,0 @@
|
|||||||
/*--------------------------------*- C++ -*----------------------------------*\
|
|
||||||
| ========= | |
|
|
||||||
| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
|
|
||||||
| \\ / O peration | Version: v2012 |
|
|
||||||
| \\ / A nd | Website: www.openfoam.com |
|
|
||||||
| \\/ M anipulation | |
|
|
||||||
\*---------------------------------------------------------------------------*/
|
|
||||||
FoamFile
|
|
||||||
{
|
|
||||||
version 2.0;
|
|
||||||
format ascii;
|
|
||||||
class dictionary;
|
|
||||||
object collapseDict;
|
|
||||||
}
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
|
|
||||||
|
|
||||||
collapseEdgesCoeffs
|
|
||||||
{
|
|
||||||
// Edges shorter than this absolute value will be merged
|
|
||||||
minimumEdgeLength 2e-7;
|
|
||||||
|
|
||||||
// The maximum angle between two edges that share a point attached to
|
|
||||||
// no other edges
|
|
||||||
maximumMergeAngle 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ************************************************************************* //
|
|
@ -1,80 +0,0 @@
|
|||||||
/*--------------------------------*- C++ -*----------------------------------*\
|
|
||||||
| ========= | |
|
|
||||||
| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
|
|
||||||
| \\ / O peration | Version: v2012 |
|
|
||||||
| \\ / A nd | Website: www.openfoam.com |
|
|
||||||
| \\/ M anipulation | |
|
|
||||||
\*---------------------------------------------------------------------------*/
|
|
||||||
FoamFile
|
|
||||||
{
|
|
||||||
version 2.0;
|
|
||||||
format ascii;
|
|
||||||
class dictionary;
|
|
||||||
location "system";
|
|
||||||
object controlDict;
|
|
||||||
}
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
|
|
||||||
|
|
||||||
application simpleFoam;
|
|
||||||
|
|
||||||
startFrom latestTime; //startTime;
|
|
||||||
|
|
||||||
startTime 0;
|
|
||||||
|
|
||||||
stopAt endTime;
|
|
||||||
|
|
||||||
endTime 5000;
|
|
||||||
|
|
||||||
deltaT 1;
|
|
||||||
|
|
||||||
writeControl timeStep;
|
|
||||||
|
|
||||||
writeInterval 50;
|
|
||||||
|
|
||||||
purgeWrite 0;
|
|
||||||
|
|
||||||
writeFormat ascii;
|
|
||||||
|
|
||||||
writePrecision 6;
|
|
||||||
|
|
||||||
writeCompression off;
|
|
||||||
|
|
||||||
timeFormat general;
|
|
||||||
|
|
||||||
timePrecision 6;
|
|
||||||
|
|
||||||
runTimeModifiable true;
|
|
||||||
|
|
||||||
functions
|
|
||||||
{
|
|
||||||
flowRatePatch(name=inlet)
|
|
||||||
{
|
|
||||||
name inlet;
|
|
||||||
type surfaceFieldValue;
|
|
||||||
libs ( "libfieldFunctionObjects.so" );
|
|
||||||
writeControl timeStep;
|
|
||||||
writeInterval 1;
|
|
||||||
writeFields false;
|
|
||||||
log false;
|
|
||||||
regionType patch;
|
|
||||||
fields ( phi );
|
|
||||||
operation sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
flowRatePatch(name=outlet)
|
|
||||||
{
|
|
||||||
name outlet;
|
|
||||||
type surfaceFieldValue;
|
|
||||||
libs ( "libfieldFunctionObjects.so" );
|
|
||||||
writeControl timeStep;
|
|
||||||
writeInterval 1;
|
|
||||||
writeFields false;
|
|
||||||
log false;
|
|
||||||
regionType patch;
|
|
||||||
fields ( phi );
|
|
||||||
operation sum;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ************************************************************************* //
|
|
@ -1,168 +0,0 @@
|
|||||||
/*--------------------------------*- C++ -*----------------------------------*\
|
|
||||||
| ========= | |
|
|
||||||
| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
|
|
||||||
| \\ / O peration | Version: v2012 |
|
|
||||||
| \\ / A nd | Website: www.openfoam.com |
|
|
||||||
| \\/ M anipulation | |
|
|
||||||
\*---------------------------------------------------------------------------*/
|
|
||||||
FoamFile
|
|
||||||
{
|
|
||||||
version 2.0;
|
|
||||||
format ascii;
|
|
||||||
class dictionary;
|
|
||||||
object createPatchDict;
|
|
||||||
}
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
|
|
||||||
|
|
||||||
pointSync false;
|
|
||||||
|
|
||||||
// Patches to create.
|
|
||||||
patches
|
|
||||||
(
|
|
||||||
{
|
|
||||||
name defaultFaces;
|
|
||||||
|
|
||||||
patchInfo
|
|
||||||
{
|
|
||||||
type empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructFrom patches;
|
|
||||||
patches (defaultFaces);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
name inlet;
|
|
||||||
|
|
||||||
patchInfo
|
|
||||||
{
|
|
||||||
type patch;
|
|
||||||
inGroups (inlet);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructFrom patches;
|
|
||||||
patches (smesh_inlet);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
name outlet;
|
|
||||||
|
|
||||||
|
|
||||||
patchInfo
|
|
||||||
{
|
|
||||||
type patch;
|
|
||||||
inGroups (outlet);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructFrom patches;
|
|
||||||
patches (smesh_outlet);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
name wall;
|
|
||||||
|
|
||||||
patchInfo
|
|
||||||
{
|
|
||||||
type wall;
|
|
||||||
inGroups (wall);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructFrom patches;
|
|
||||||
patches (smesh_wall);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
name symetry0;
|
|
||||||
|
|
||||||
patchInfo
|
|
||||||
{
|
|
||||||
type symetryPlane;
|
|
||||||
inGroups (symetryPlane);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructFrom patches;
|
|
||||||
patches (smesh_symetry0);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
name symetry1;
|
|
||||||
|
|
||||||
patchInfo
|
|
||||||
{
|
|
||||||
type symetryPlane;
|
|
||||||
inGroups (symetryPlane);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructFrom patches;
|
|
||||||
patches (smesh_symetry1);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
name symetry2;
|
|
||||||
|
|
||||||
patchInfo
|
|
||||||
{
|
|
||||||
type symetryPlane;
|
|
||||||
inGroups (symetryPlane);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructFrom patches;
|
|
||||||
patches (smesh_symetry2);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
name symetry3;
|
|
||||||
|
|
||||||
patchInfo
|
|
||||||
{
|
|
||||||
type symetryPlane;
|
|
||||||
inGroups (symetryPlane);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructFrom patches;
|
|
||||||
patches (smesh_symetry3);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
name symetry4;
|
|
||||||
|
|
||||||
patchInfo
|
|
||||||
{
|
|
||||||
type symetryPlane;
|
|
||||||
inGroups (symetryPlane);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructFrom patches;
|
|
||||||
patches (smesh_symetry4);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
name symetry5;
|
|
||||||
|
|
||||||
patchInfo
|
|
||||||
{
|
|
||||||
type symetryPlane;
|
|
||||||
inGroups (symetryPlane);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructFrom patches;
|
|
||||||
patches (smesh_symetry5);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
name strips;
|
|
||||||
|
|
||||||
patchInfo
|
|
||||||
{
|
|
||||||
type wall;
|
|
||||||
inGroups (wall);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructFrom patches;
|
|
||||||
patches (smesh_strips);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
// ************************************************************************* //
|
|
@ -1,26 +0,0 @@
|
|||||||
/*--------------------------------*- C++ -*----------------------------------*\
|
|
||||||
| ========= | |
|
|
||||||
| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
|
|
||||||
| \\ / O peration | Version: v2012 |
|
|
||||||
| \\ / A nd | Website: www.openfoam.com |
|
|
||||||
| \\/ M anipulation | |
|
|
||||||
\*---------------------------------------------------------------------------*/
|
|
||||||
FoamFile
|
|
||||||
{
|
|
||||||
version 2.0;
|
|
||||||
format ascii;
|
|
||||||
class dictionary;
|
|
||||||
object decomposeParDict;
|
|
||||||
}
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
|
|
||||||
|
|
||||||
numberOfSubdomains 4;
|
|
||||||
|
|
||||||
method simple;
|
|
||||||
|
|
||||||
coeffs
|
|
||||||
{
|
|
||||||
n (2 2 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ************************************************************************* //
|
|
@ -1,53 +0,0 @@
|
|||||||
/*--------------------------------*- C++ -*----------------------------------*\
|
|
||||||
| ========= | |
|
|
||||||
| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
|
|
||||||
| \\ / O peration | Version: v2012 |
|
|
||||||
| \\ / A nd | Website: www.openfoam.com |
|
|
||||||
| \\/ M anipulation | |
|
|
||||||
\*---------------------------------------------------------------------------*/
|
|
||||||
FoamFile
|
|
||||||
{
|
|
||||||
version 2.0;
|
|
||||||
format ascii;
|
|
||||||
class dictionary;
|
|
||||||
location "system";
|
|
||||||
object fvSchemes;
|
|
||||||
}
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
|
|
||||||
|
|
||||||
ddtSchemes
|
|
||||||
{
|
|
||||||
default steadyState;
|
|
||||||
}
|
|
||||||
|
|
||||||
gradSchemes
|
|
||||||
{
|
|
||||||
default Gauss linear;
|
|
||||||
// grad(Phi) Gauss linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
divSchemes
|
|
||||||
{
|
|
||||||
default none;
|
|
||||||
div(phi,U) bounded Gauss linearUpwind grad(U);
|
|
||||||
div((nuEff*dev2(T(grad(U))))) Gauss linear;
|
|
||||||
div(nonlinearStress) Gauss linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
laplacianSchemes
|
|
||||||
{
|
|
||||||
default Gauss linear corrected;
|
|
||||||
// laplacian(1, Phi) Gauss linear corrected;
|
|
||||||
}
|
|
||||||
|
|
||||||
interpolationSchemes
|
|
||||||
{
|
|
||||||
default linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
snGradSchemes
|
|
||||||
{
|
|
||||||
default corrected;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ************************************************************************* //
|
|
@ -1,96 +0,0 @@
|
|||||||
/*--------------------------------*- C++ -*----------------------------------*\
|
|
||||||
| ========= | |
|
|
||||||
| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
|
|
||||||
| \\ / O peration | Version: v2012 |
|
|
||||||
| \\ / A nd | Website: www.openfoam.com |
|
|
||||||
| \\/ M anipulation | |
|
|
||||||
\*---------------------------------------------------------------------------*/
|
|
||||||
FoamFile
|
|
||||||
{
|
|
||||||
version 2.0;
|
|
||||||
format ascii;
|
|
||||||
class dictionary;
|
|
||||||
location "system";
|
|
||||||
object fvSolution;
|
|
||||||
}
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
|
|
||||||
|
|
||||||
solvers
|
|
||||||
{
|
|
||||||
p
|
|
||||||
{
|
|
||||||
solver GAMG;
|
|
||||||
tolerance 1e-06;
|
|
||||||
relTol 0.1;
|
|
||||||
smoother GaussSeidel;
|
|
||||||
}
|
|
||||||
|
|
||||||
U
|
|
||||||
{
|
|
||||||
solver smoothSolver;
|
|
||||||
smoother GaussSeidel;
|
|
||||||
nSweeps 2;
|
|
||||||
tolerance 1e-08;
|
|
||||||
relTol 0.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Phi
|
|
||||||
{
|
|
||||||
solver GAMG;
|
|
||||||
smoother GaussSeidel;
|
|
||||||
tolerance 1e-08;
|
|
||||||
relTol 0.01;
|
|
||||||
}*/
|
|
||||||
Phi
|
|
||||||
{
|
|
||||||
solver GAMG;
|
|
||||||
smoother DIC;
|
|
||||||
cacheAgglomeration on;
|
|
||||||
agglomerator faceAreaPair;
|
|
||||||
nCellsInCoarsestLevel 10;
|
|
||||||
mergeLevels 1;
|
|
||||||
|
|
||||||
tolerance 1e-06;
|
|
||||||
relTol 0.01;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
potentialFlow
|
|
||||||
{
|
|
||||||
nNonOrthogonalCorrectors 20;
|
|
||||||
PhiRefCell 0;
|
|
||||||
PhiRefPoint 0;
|
|
||||||
PhiRefValue 0;
|
|
||||||
Phi 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
cache
|
|
||||||
{
|
|
||||||
grad(U);
|
|
||||||
}
|
|
||||||
|
|
||||||
SIMPLE
|
|
||||||
{
|
|
||||||
nNonOrthogonalCorrectors 10;
|
|
||||||
|
|
||||||
residualControl
|
|
||||||
{
|
|
||||||
p 1e-5;
|
|
||||||
U 1e-5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
relaxationFactors
|
|
||||||
{
|
|
||||||
fields
|
|
||||||
{
|
|
||||||
p 0.3;
|
|
||||||
}
|
|
||||||
equations
|
|
||||||
{
|
|
||||||
U 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ************************************************************************* //
|
|
@ -1,39 +1,61 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
from __future__ import annotations
|
||||||
|
from numpy.typing import ArrayLike
|
||||||
|
from numpy import ndarray
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
from .application import application
|
|
||||||
|
|
||||||
def version() -> str:
|
|
||||||
return os.environ["WM_PROJECT_VERSION"]
|
|
||||||
|
|
||||||
|
|
||||||
def foamCleanCustom(case: str = None):
|
def version() -> str | None:
|
||||||
rmDirs = ["0", "constant", "system", "postProcessing", "logs"]
|
"""Version of the current OpenFOAM installation.
|
||||||
rmDirs.extend([ "processor{}".format(n) for n in range(os.cpu_count()) ])
|
|
||||||
path = case if case else ""
|
|
||||||
|
|
||||||
for d in rmDirs:
|
:return:
|
||||||
if os.path.exists(os.path.join(path, d)):
|
Version string or None if installation is not found.
|
||||||
shutil.rmtree(os.path.join(path, d))
|
"""
|
||||||
|
return os.environ.get("WM_PROJECT_VERSION")
|
||||||
|
|
||||||
def foamClean(case: str = None):
|
|
||||||
rmDirs = ["0", "constant", "system"]
|
|
||||||
path = case if case else ""
|
|
||||||
|
|
||||||
for d in rmDirs:
|
def template(header: dict, content: dict) -> str:
|
||||||
if os.path.exists(os.path.join(path, d)):
|
"""Render FoamFile with current template.
|
||||||
shutil.rmtree(os.path.join(path, d))
|
|
||||||
|
|
||||||
application("foamCleanTutorials", useMPI = False, case = case, stderr = True)
|
:param header:
|
||||||
|
Header block with the FoamFile metadata.
|
||||||
|
:param content:
|
||||||
|
Content block of the FoamFile.
|
||||||
|
:return:
|
||||||
|
Generated string of the whole FoamFile.
|
||||||
|
"""
|
||||||
|
limit = 78
|
||||||
|
desc = [
|
||||||
|
"/*--------------------------------*- C++ -*----------------------------------*\\",
|
||||||
|
"| ========= | |",
|
||||||
|
"| \\\\ / F ield | OpenFOAM: The Open Source CFD Toolbox |",
|
||||||
|
"| \\\\ / O peration |",
|
||||||
|
"| \\\\ / A nd | |",
|
||||||
|
"| \\\\/ M anipulation | |",
|
||||||
|
"\\*---------------------------------------------------------------------------*/"
|
||||||
|
]
|
||||||
|
desc[3] += " Version: {}".format(version() or "missed")
|
||||||
|
desc[3] += " " * (limit - len(desc[3])) + "|"
|
||||||
|
afterheader = "// " + 37 * "* " + "//"
|
||||||
|
endfile = "// " + 73 * "*" + " //"
|
||||||
|
|
||||||
def uniform(value) -> str:
|
return "\n".join([*desc, header, afterheader, content, endfile])
|
||||||
if type(value) == list or type(value) == tuple:
|
|
||||||
|
|
||||||
|
def uniform(value: ArrayLike | float | int) -> str:
|
||||||
|
"""Convert value to the OpenFOAM uniform representation.
|
||||||
|
|
||||||
|
:param value:
|
||||||
|
Vector or scalar value.
|
||||||
|
:return:
|
||||||
|
Uniform string representation.
|
||||||
|
"""
|
||||||
|
if type(value) in [list, tuple, ndarray]:
|
||||||
return f"uniform ({ value[0] } { value[1] } { value[2] })"
|
return f"uniform ({ value[0] } { value[1] } { value[2] })"
|
||||||
|
|
||||||
elif type(value) == int or type(value) == float:
|
elif type(value) in [int, float]:
|
||||||
return f"uniform { value }"
|
return f"uniform { value }"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger("anisotropy")
|
|
||||||
|
|
||||||
try:
|
|
||||||
import GEOM
|
|
||||||
from salome.geom import geomBuilder
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
logger.debug("Trying to get SALOME geometry modules outside SALOME environment. Modules won't be imported.")
|
|
||||||
|
|
||||||
if globals().get("geomBuilder"):
|
|
||||||
geompy = geomBuilder.New()
|
|
||||||
|
|
||||||
else:
|
|
||||||
geompy = None
|
|
||||||
|
|
||||||
def getGeom():
|
|
||||||
return geompy
|
|
||||||
|
|
||||||
|
|
@ -1,173 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger("anisotropy")
|
|
||||||
|
|
||||||
try:
|
|
||||||
import SMESH
|
|
||||||
from salome.smesh import smeshBuilder
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
logger.debug("Trying to get SALOME mesh modules outside SALOME environment. Modules won't be imported.")
|
|
||||||
|
|
||||||
if globals().get("smeshBuilder"):
|
|
||||||
smesh = smeshBuilder.New()
|
|
||||||
|
|
||||||
else:
|
|
||||||
smesh = None
|
|
||||||
|
|
||||||
import enum
|
|
||||||
|
|
||||||
class Fineness(enum.Enum):
|
|
||||||
VeryCoarse = 0
|
|
||||||
Coarse = 1
|
|
||||||
Moderate = 2
|
|
||||||
Fine = 3
|
|
||||||
VeryFine = 4
|
|
||||||
Custom = 5
|
|
||||||
|
|
||||||
|
|
||||||
def getSmesh():
|
|
||||||
return smesh
|
|
||||||
|
|
||||||
|
|
||||||
def updateParams(old, new: dict):
|
|
||||||
old.SetMaxSize(new.get("maxSize", old.GetMaxSize()))
|
|
||||||
old.SetMinSize(new.get("minSize", old.GetMinSize()))
|
|
||||||
|
|
||||||
old.SetFineness(new.get("fineness", old.GetFineness()))
|
|
||||||
old.SetGrowthRate(new.get("growthRate", old.GetGrowthRate()))
|
|
||||||
old.SetNbSegPerEdge(new.get("nbSegPerEdge", old.GetNbSegPerEdge()))
|
|
||||||
old.SetNbSegPerRadius(new.get("nbSegPerRadius", old.GetNbSegPerRadius()))
|
|
||||||
|
|
||||||
old.SetChordalErrorEnabled(new.get("chordalErrorEnabled", old.GetChordalErrorEnabled()))
|
|
||||||
old.SetChordalError(new.get("chordalError", old.GetChordalError()))
|
|
||||||
|
|
||||||
old.SetSecondOrder(new.get("secondOrder", old.GetSecondOrder()))
|
|
||||||
old.SetOptimize(new.get("optimize", old.GetOptimize()))
|
|
||||||
old.SetQuadAllowed(new.get("quadAllowed", old.GetQuadAllowed()))
|
|
||||||
old.SetUseSurfaceCurvature(new.get("useSurfaceCurvature", old.GetUseSurfaceCurvature()))
|
|
||||||
old.SetFuseEdges(new.get("fuseEdges", old.GetFuseEdges()))
|
|
||||||
old.SetCheckChartBoundary(new.get("checkChartBoundary", old.GetCheckChartBoundary()))
|
|
||||||
|
|
||||||
|
|
||||||
class Mesh(object):
|
|
||||||
def __init__(self, shape, name = ""):
|
|
||||||
self.name = name if name else shape.GetName()
|
|
||||||
self.mesh = smesh.Mesh(shape, self.name)
|
|
||||||
self.geom = shape
|
|
||||||
self.algo = None
|
|
||||||
self.params = None
|
|
||||||
self.viscousLayers = None
|
|
||||||
|
|
||||||
self.submeshes = []
|
|
||||||
|
|
||||||
def Tetrahedron(self, **kwargs):
|
|
||||||
self.algo = self.mesh.Tetrahedron(algo = smeshBuilder.NETGEN_1D2D3D)
|
|
||||||
self.params = self.algo.Parameters()
|
|
||||||
|
|
||||||
self.params = updateParams(self.params, kwargs)
|
|
||||||
|
|
||||||
def _extrusionMethod(self, key: str):
|
|
||||||
return dict(
|
|
||||||
SURF_OFFSET_SMOOTH = smeshBuilder.SURF_OFFSET_SMOOTH,
|
|
||||||
FACE_OFFSET = smeshBuilder.FACE_OFFSET,
|
|
||||||
NODE_OFFSET = smeshBuilder.NODE_OFFSET,
|
|
||||||
).get(key, smeshBuilder.SURF_OFFSET_SMOOTH)
|
|
||||||
|
|
||||||
def ViscousLayers(self,
|
|
||||||
thickness = 1,
|
|
||||||
numberOfLayers = 1,
|
|
||||||
stretchFactor = 0,
|
|
||||||
faces = [],
|
|
||||||
isFacesToIgnore = True,
|
|
||||||
extrMethod = "SURF_OFFSET_SMOOTH",
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
|
|
||||||
self.viscousLayers = self.algo.ViscousLayers(
|
|
||||||
thickness,
|
|
||||||
numberOfLayers,
|
|
||||||
stretchFactor,
|
|
||||||
faces,
|
|
||||||
isFacesToIgnore,
|
|
||||||
self._extrusionMethod(extrMethod)
|
|
||||||
)
|
|
||||||
|
|
||||||
def Triangle(self, subshape, **kwargs):
|
|
||||||
submesh = Submesh(self.mesh, subshape)
|
|
||||||
submesh.algo = self.mesh.Triangle(algo = smeshBuilder.NETGEN_1D2D, geom = subshape)
|
|
||||||
submesh.mesh = submesh.algo.subm
|
|
||||||
submesh.params = submesh.algo.Parameters()
|
|
||||||
|
|
||||||
submesh.params = updateParams(submesh.params, kwargs)
|
|
||||||
|
|
||||||
self.submeshes.append(submesh)
|
|
||||||
|
|
||||||
def assignGroups(self, withPrefix = True):
|
|
||||||
prefix = "smesh_" if withPrefix else ""
|
|
||||||
|
|
||||||
for group in self.mesh.geompyD.GetGroups(self.geom):
|
|
||||||
if group.GetName():
|
|
||||||
self.mesh.GroupOnGeom(group, f"{ prefix }{ group.GetName() }", SMESH.FACE)
|
|
||||||
|
|
||||||
def compute(self):
|
|
||||||
isDone = self.mesh.Compute()
|
|
||||||
returncode = int(not isDone)
|
|
||||||
err = self.mesh.GetComputeErrors()
|
|
||||||
|
|
||||||
return "", err, returncode
|
|
||||||
|
|
||||||
def stats(self):
|
|
||||||
return {
|
|
||||||
"elements": self.mesh.NbElements(),
|
|
||||||
"edges": self.mesh.NbEdges(),
|
|
||||||
"faces": self.mesh.NbFaces(),
|
|
||||||
"volumes": self.mesh.NbVolumes(),
|
|
||||||
"tetrahedrons": self.mesh.NbTetras(),
|
|
||||||
"prisms": self.mesh.NbPrisms(),
|
|
||||||
"pyramids": self.mesh.NbPyramids()
|
|
||||||
}
|
|
||||||
|
|
||||||
def exportUNV(self, path):
|
|
||||||
returncode = 0
|
|
||||||
error = ""
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.mesh.ExportUNV(path)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
error = e.details.text
|
|
||||||
returncode = 1
|
|
||||||
|
|
||||||
return returncode, error
|
|
||||||
|
|
||||||
def removePyramids(self):
|
|
||||||
if self.mesh.NbPyramids() > 0:
|
|
||||||
pyramidCriterion = smesh.GetCriterion(
|
|
||||||
SMESH.VOLUME,
|
|
||||||
SMESH.FT_ElemGeomType,
|
|
||||||
SMESH.FT_Undefined,
|
|
||||||
SMESH.Geom_PYRAMID
|
|
||||||
)
|
|
||||||
pyramidGroup = self.mesh.MakeGroupByCriterion("pyramids", pyramidCriterion)
|
|
||||||
pyramidVolumes = self.mesh.GetIDSource(pyramidGroup.GetIDs(), SMESH.VOLUME)
|
|
||||||
|
|
||||||
self.mesh.SplitVolumesIntoTetra(pyramidVolumes, smesh.Hex_5Tet)
|
|
||||||
|
|
||||||
self.mesh.RemoveGroup(pyramidGroup)
|
|
||||||
self.mesh.RenumberElements()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Submesh(object):
|
|
||||||
def __init__(self, father, subshape, name = ""):
|
|
||||||
self.name = name if name else subshape.GetName()
|
|
||||||
self.mesh = None
|
|
||||||
self.geom = subshape
|
|
||||||
self.algo = None
|
|
||||||
self.params = None
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import logging
|
|
||||||
import sys, os
|
|
||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
class SalomeNotFound(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SalomeManager(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.__port = None
|
|
||||||
self.__lastproc = None
|
|
||||||
|
|
||||||
|
|
||||||
def runner(self, cmdargs: list, **kwargs):
|
|
||||||
timeout = kwargs.pop("timeout") if kwargs.get("timeout") else None
|
|
||||||
|
|
||||||
try:
|
|
||||||
with subprocess.Popen(
|
|
||||||
cmdargs,
|
|
||||||
stdout = kwargs.pop("stdout") if kwargs.get("stdout") else subprocess.PIPE,
|
|
||||||
stderr = kwargs.pop("stdout") if kwargs.get("stderr") else subprocess.PIPE,
|
|
||||||
**kwargs
|
|
||||||
) as proc:
|
|
||||||
self.__lastproc = proc
|
|
||||||
out, err = proc.communicate(timeout = timeout)
|
|
||||||
|
|
||||||
except FileNotFoundError:
|
|
||||||
raise SalomeNotFound()
|
|
||||||
|
|
||||||
return out, err, proc.returncode
|
|
||||||
|
|
||||||
|
|
||||||
def port(self) -> int:
|
|
||||||
out, err, returncode = self.runner(["salome", "start", "--print-port"], text = True)
|
|
||||||
|
|
||||||
if returncode == 0:
|
|
||||||
reg = re.search("(?!port:)([0-9]+)", out)
|
|
||||||
|
|
||||||
if reg:
|
|
||||||
return int(reg.group())
|
|
||||||
|
|
||||||
return 2810
|
|
||||||
|
|
||||||
|
|
||||||
def version(self) -> int:
|
|
||||||
out, err, returncode = self.runner(["salome", "--version"], text = True)
|
|
||||||
|
|
||||||
return out.strip().split(" ")[-1]
|
|
||||||
|
|
||||||
|
|
||||||
def kill(self, port: int = None):
|
|
||||||
return self.runner(["salome", "kill", str(self.__port or port)])
|
|
||||||
|
|
||||||
|
|
||||||
def killall(self):
|
|
||||||
return self.runner(["salome", "killall"])
|
|
||||||
|
|
||||||
|
|
||||||
def execute(self, scriptpath: str, *args, root: str = None, logpath: str = None, timeout: int = None, **kwargs):
|
|
||||||
|
|
||||||
if not root:
|
|
||||||
root = os.environ["HOME"]
|
|
||||||
|
|
||||||
# ISSUE: salome removes commas from string list
|
|
||||||
args = list(args)
|
|
||||||
args.insert(1, root)
|
|
||||||
|
|
||||||
salomeargs = "args:"
|
|
||||||
salomeargs += ",".join([ '"{}"'.format(str(arg)) for arg in args ])
|
|
||||||
|
|
||||||
if kwargs:
|
|
||||||
salomeargs += "," + ",".join([ '{}="{}"'.format(k, v) for k, v in kwargs.items() ])
|
|
||||||
|
|
||||||
###
|
|
||||||
self.__port = self.port()
|
|
||||||
cmd = [
|
|
||||||
"salome",
|
|
||||||
"start",
|
|
||||||
"-t",
|
|
||||||
"--shutdown-servers=1",
|
|
||||||
"--port", str(self.__port),
|
|
||||||
scriptpath,
|
|
||||||
salomeargs
|
|
||||||
]
|
|
||||||
|
|
||||||
try:
|
|
||||||
out, err, returncode = self.runner(cmd, timeout = timeout)
|
|
||||||
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
lastproc = self.__lastproc
|
|
||||||
self.kill()
|
|
||||||
|
|
||||||
out, err = lastproc.communicate()
|
|
||||||
returncode = lastproc.returncode
|
|
||||||
|
|
||||||
if logpath:
|
|
||||||
os.makedirs(logpath, exist_ok = True)
|
|
||||||
|
|
||||||
with open(os.path.join(logpath, "salome.log"), "wb") as io:
|
|
||||||
io.write(out)
|
|
||||||
io.write(err)
|
|
||||||
|
|
||||||
return str(out, "utf-8"), str(err, "utf-8"), returncode
|
|
||||||
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
from anisotropy.samples.simple import Simple
|
|
||||||
from anisotropy.samples.bodyCentered import BodyCentered
|
|
||||||
from anisotropy.samples.faceCentered import FaceCentered
|
|
@ -1,230 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
from math import pi, sqrt
|
|
||||||
from anisotropy.salomepl import geometry
|
|
||||||
|
|
||||||
class BodyCentered(object):
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
|
|
||||||
self.direction = kwargs.get("direction", [1, 0, 0])
|
|
||||||
self.theta = kwargs.get("theta", 0.01)
|
|
||||||
self.L = kwargs.get("L", 1)
|
|
||||||
self.r0 = kwargs.get("r0", self.L * sqrt(3) / 4)
|
|
||||||
self.radius = kwargs.get("radius", self.r0 / (1 - self.theta))
|
|
||||||
self.filletsEnabled = kwargs.get("filletsEnabled", False)
|
|
||||||
self.fillets = kwargs.get("fillets", 0)
|
|
||||||
self.volumeCell = None
|
|
||||||
|
|
||||||
|
|
||||||
def build(self):
|
|
||||||
|
|
||||||
geompy = geometry.getGeom()
|
|
||||||
|
|
||||||
###
|
|
||||||
# Pore Cell
|
|
||||||
##
|
|
||||||
if self.direction in [[1, 0, 0], [0, 0, 1]]:
|
|
||||||
###
|
|
||||||
# Parameters
|
|
||||||
##
|
|
||||||
xn, yn, zn = 3, 3, 3
|
|
||||||
|
|
||||||
length = 2 * self.r0
|
|
||||||
width = self.L / 2
|
|
||||||
diag = self.L * sqrt(2)
|
|
||||||
height = self.L
|
|
||||||
|
|
||||||
point = []
|
|
||||||
xl = sqrt(diag ** 2 + diag ** 2) * 0.5
|
|
||||||
yw = xl
|
|
||||||
zh = height
|
|
||||||
|
|
||||||
scale = 100
|
|
||||||
oo = geompy.MakeVertex(0, 0, 0)
|
|
||||||
spos1 = (0, 0, 0)
|
|
||||||
spos2 = (0, 0, 0)
|
|
||||||
|
|
||||||
###
|
|
||||||
# Bounding box
|
|
||||||
##
|
|
||||||
if self.direction == [1, 0, 0]:
|
|
||||||
sk = geompy.Sketcher3D()
|
|
||||||
sk.addPointsAbsolute(xl, 0, 0)
|
|
||||||
sk.addPointsAbsolute(0, yw, 0)
|
|
||||||
sk.addPointsAbsolute(0, yw, zh)
|
|
||||||
sk.addPointsAbsolute(xl, 0, zh)
|
|
||||||
sk.addPointsAbsolute(xl, 0, 0)
|
|
||||||
|
|
||||||
inletface = geompy.MakeFaceWires([sk.wire()], True)
|
|
||||||
vecflow = geompy.GetNormal(inletface)
|
|
||||||
poreCell = geompy.MakePrismVecH(inletface, vecflow, diag)
|
|
||||||
|
|
||||||
elif self.direction == [0, 0, 1]:
|
|
||||||
sk = geompy.Sketcher3D()
|
|
||||||
sk.addPointsAbsolute(0, yw, 0)
|
|
||||||
sk.addPointsAbsolute(xl, 0, 0)
|
|
||||||
sk.addPointsAbsolute(2 * xl, yw, 0)
|
|
||||||
sk.addPointsAbsolute(xl, 2 * yw, 0)
|
|
||||||
sk.addPointsAbsolute(0, yw, 0)
|
|
||||||
|
|
||||||
inletface = geompy.MakeFaceWires([sk.wire()], True)
|
|
||||||
vecflow = geompy.GetNormal(inletface)
|
|
||||||
poreCell = geompy.MakePrismVecH(inletface, vecflow, zh)
|
|
||||||
|
|
||||||
[_, _, self.volumeCell] = geompy.BasicProperties(poreCell, theTolerance = 1e-06)
|
|
||||||
|
|
||||||
inletface = geompy.MakeScaleTransform(inletface, oo, scale)
|
|
||||||
poreCell = geompy.MakeScaleTransform(poreCell, oo, scale)
|
|
||||||
|
|
||||||
faces = geompy.ExtractShapes(poreCell, geompy.ShapeType["FACE"], False)
|
|
||||||
symetryface = []
|
|
||||||
|
|
||||||
for face in faces:
|
|
||||||
norm = geompy.GetNormal(face)
|
|
||||||
angle = round(geompy.GetAngle(norm, vecflow), 0)
|
|
||||||
|
|
||||||
if (angle == 0 or angle == 180) and not face == inletface:
|
|
||||||
outletface = face
|
|
||||||
|
|
||||||
else:
|
|
||||||
symetryface.append(face)
|
|
||||||
|
|
||||||
elif self.direction == [1, 1, 1]:
|
|
||||||
###
|
|
||||||
# Parameters
|
|
||||||
##
|
|
||||||
xn, yn, zn = 3, 3, 3
|
|
||||||
|
|
||||||
length = 2 * self.r0
|
|
||||||
width = self.L / 2
|
|
||||||
diag = self.L * sqrt(2)
|
|
||||||
height = diag / 3
|
|
||||||
|
|
||||||
point = []
|
|
||||||
xl, yw, zh = -self.L / 4, -self.L / 4, -self.L / 4
|
|
||||||
point.append((self.L / 3 + xl, self.L / 3 + yw, 4 * self.L / 3 + zh))
|
|
||||||
point.append((self.L + xl, 0 + yw, self.L + zh))
|
|
||||||
point.append((4 * self.L / 3 + xl, self.L / 3 + yw, self.L / 3 + zh))
|
|
||||||
point.append((self.L + xl, self.L + yw, 0 + zh))
|
|
||||||
point.append((self.L / 3 + xl, 4 * self.L / 3 + yw, self.L / 3 + zh))
|
|
||||||
point.append((0 + xl, self.L + yw, self.L + zh))
|
|
||||||
point.append((self.L / 3 + xl, self.L / 3 + yw, 4 * self.L / 3 + zh))
|
|
||||||
|
|
||||||
scale = 100
|
|
||||||
oo = geompy.MakeVertex(0, 0, 0)
|
|
||||||
spos1 = (0, 0, 0)
|
|
||||||
spos2 = (0, 0, 0)
|
|
||||||
|
|
||||||
###
|
|
||||||
# Bounding box
|
|
||||||
##
|
|
||||||
sk = geompy.Sketcher3D()
|
|
||||||
|
|
||||||
for p in point:
|
|
||||||
sk.addPointsAbsolute(*p)
|
|
||||||
|
|
||||||
inletface = geompy.MakeFaceWires([sk.wire()], False)
|
|
||||||
vecflow = geompy.GetNormal(inletface)
|
|
||||||
poreCell = geompy.MakePrismVecH(inletface, vecflow, self.L * sqrt(3))
|
|
||||||
|
|
||||||
[_, _, self.volumeCell] = geompy.BasicProperties(poreCell, theTolerance = 1e-06)
|
|
||||||
|
|
||||||
inletface = geompy.MakeScaleTransform(inletface, oo, scale)
|
|
||||||
poreCell = geompy.MakeScaleTransform(poreCell, oo, scale)
|
|
||||||
|
|
||||||
faces = geompy.ExtractShapes(poreCell, geompy.ShapeType["FACE"], False)
|
|
||||||
symetryface = []
|
|
||||||
|
|
||||||
for face in faces:
|
|
||||||
norm = geompy.GetNormal(face)
|
|
||||||
angle = round(geompy.GetAngle(norm, vecflow), 0)
|
|
||||||
|
|
||||||
if (angle == 0 or angle == 180) and not face == inletface:
|
|
||||||
outletface = face
|
|
||||||
|
|
||||||
else:
|
|
||||||
symetryface.append(face)
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise Exception(f"Direction { self.direction } is not implemented")
|
|
||||||
|
|
||||||
###
|
|
||||||
# Grains
|
|
||||||
##
|
|
||||||
ox = geompy.MakeVectorDXDYDZ(1, 0, 0)
|
|
||||||
oy = geompy.MakeVectorDXDYDZ(0, 1, 0)
|
|
||||||
oz = geompy.MakeVectorDXDYDZ(0, 0, 1)
|
|
||||||
xy = geompy.MakeVectorDXDYDZ(1, 1, 0)
|
|
||||||
xmy = geompy.MakeVectorDXDYDZ(1, -1, 0)
|
|
||||||
|
|
||||||
grain = geompy.MakeSpherePntR(geompy.MakeVertex(*spos1), self.radius)
|
|
||||||
lattice1 = geompy.MakeMultiTranslation2D(grain, ox, self.L, xn, oy, self.L, yn)
|
|
||||||
lattice1 = geompy.MakeMultiTranslation1D(lattice1, oz, self.L, zn)
|
|
||||||
|
|
||||||
#grain = geompy.MakeSpherePntR(geompy.MakeVertex(*spos2), radius)
|
|
||||||
#lattice2 = geompy.MakeMultiTranslation2D(grain, xy, length, xn + 1, xmy, length, yn + 1)
|
|
||||||
#lattice2 = geompy.MakeMultiTranslation1D(lattice2, oz, L, zn)
|
|
||||||
lattice2 = geompy.MakeTranslation(lattice1, 0.5 * self.L, 0.5 * self.L, 0.5 * self.L)
|
|
||||||
|
|
||||||
grains = geompy.ExtractShapes(lattice1, geompy.ShapeType["SOLID"], True)
|
|
||||||
grains += geompy.ExtractShapes(lattice2, geompy.ShapeType["SOLID"], True)
|
|
||||||
grains = geompy.MakeFuseList(grains, False, False)
|
|
||||||
|
|
||||||
grains = geompy.MakeScaleTransform(grains, oo, scale)
|
|
||||||
grainsOrigin = None
|
|
||||||
|
|
||||||
if self.filletsEnabled:
|
|
||||||
grainsOrigin = geompy.MakeScaleTransform(grains, oo, 1 / scale)
|
|
||||||
grains = geompy.MakeFilletAll(grains, self.fillets * scale)
|
|
||||||
|
|
||||||
###
|
|
||||||
# Groups
|
|
||||||
##
|
|
||||||
shape = geompy.MakeCutList(poreCell, [grains])
|
|
||||||
shape = geompy.MakeScaleTransform(shape, oo, 1 / scale, theName = "bodyCentered")
|
|
||||||
|
|
||||||
sall = geompy.CreateGroup(shape, geompy.ShapeType["FACE"])
|
|
||||||
geompy.UnionIDs(sall,
|
|
||||||
geompy.SubShapeAllIDs(shape, geompy.ShapeType["FACE"]))
|
|
||||||
|
|
||||||
inlet = geompy.CreateGroup(shape, geompy.ShapeType["FACE"], theName = "inlet")
|
|
||||||
inletshape = geompy.MakeCutList(inletface, [grains])
|
|
||||||
inletshape = geompy.MakeScaleTransform(inletshape, oo, 1 / scale)
|
|
||||||
geompy.UnionList(inlet, geompy.SubShapeAll(
|
|
||||||
geompy.GetInPlace(shape, inletshape, True), geompy.ShapeType["FACE"]))
|
|
||||||
|
|
||||||
outlet = geompy.CreateGroup(shape, geompy.ShapeType["FACE"], theName = "outlet")
|
|
||||||
outletshape = geompy.MakeCutList(outletface, [grains])
|
|
||||||
outletshape = geompy.MakeScaleTransform(outletshape, oo, 1 / scale)
|
|
||||||
geompy.UnionList(outlet, geompy.SubShapeAll(
|
|
||||||
geompy.GetInPlace(shape, outletshape, True), geompy.ShapeType["FACE"]))
|
|
||||||
|
|
||||||
symetry = []
|
|
||||||
for (n, face) in enumerate(symetryface):
|
|
||||||
name = "symetry" + str(n)
|
|
||||||
symetry.append(geompy.CreateGroup(shape, geompy.ShapeType["FACE"], theName = name))
|
|
||||||
symetryshape = geompy.MakeCutList(face, [grains])
|
|
||||||
symetryshape = geompy.MakeScaleTransform(symetryshape, oo, 1 / scale)
|
|
||||||
geompy.UnionList(symetry[n], geompy.SubShapeAll(
|
|
||||||
geompy.GetInPlace(shape, symetryshape, True), geompy.ShapeType["FACE"]))
|
|
||||||
|
|
||||||
groups = []
|
|
||||||
groups.append(inlet)
|
|
||||||
groups.append(outlet)
|
|
||||||
groups.extend(symetry)
|
|
||||||
|
|
||||||
if self.filletsEnabled:
|
|
||||||
strips = geompy.CreateGroup(shape, geompy.ShapeType["FACE"], theName = "strips")
|
|
||||||
shapeShell = geompy.ExtractShapes(shape, geompy.ShapeType["SHELL"], True)
|
|
||||||
stripsShape = geompy.MakeCutList(shapeShell[0], groups + [grainsOrigin])
|
|
||||||
geompy.UnionList(strips, geompy.SubShapeAll(
|
|
||||||
geompy.GetInPlace(shape, stripsShape, True), geompy.ShapeType["FACE"]))
|
|
||||||
groups.append(strips)
|
|
||||||
|
|
||||||
wall = geompy.CutListOfGroups([sall], groups, theName = "wall")
|
|
||||||
groups.append(wall)
|
|
||||||
|
|
||||||
return shape, groups
|
|
||||||
|
|
@ -1,230 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
from math import pi, sqrt
|
|
||||||
from anisotropy.salomepl import geometry
|
|
||||||
|
|
||||||
class FaceCentered(object):
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
|
|
||||||
self.direction = kwargs.get("direction", [1, 0, 0])
|
|
||||||
self.theta = kwargs.get("theta", 0.01)
|
|
||||||
self.L = kwargs.get("L", 1)
|
|
||||||
self.r0 = kwargs.get("r0", self.L * sqrt(2) / 4)
|
|
||||||
self.radius = kwargs.get("radius", self.r0 / (1 - self.theta))
|
|
||||||
self.filletsEnabled = kwargs.get("filletsEnabled", False)
|
|
||||||
self.fillets = kwargs.get("fillets", 0)
|
|
||||||
self.volumeCell = None
|
|
||||||
|
|
||||||
|
|
||||||
def build(self):
|
|
||||||
|
|
||||||
geompy = geometry.getGeom()
|
|
||||||
|
|
||||||
###
|
|
||||||
# Pore Cell
|
|
||||||
##
|
|
||||||
if self.direction in [[1, 0, 0], [0, 0, 1]]:
|
|
||||||
###
|
|
||||||
# Parameters
|
|
||||||
##
|
|
||||||
xn, yn, zn = 3, 3, 3
|
|
||||||
|
|
||||||
length = 2 * self.r0
|
|
||||||
width = self.L / 2
|
|
||||||
diag = self.L * sqrt(3)
|
|
||||||
height = diag / 3
|
|
||||||
|
|
||||||
point = []
|
|
||||||
xl = sqrt(length ** 2 + length ** 2) * 0.5
|
|
||||||
yw = xl
|
|
||||||
zh = width
|
|
||||||
|
|
||||||
scale = 100
|
|
||||||
oo = geompy.MakeVertex(0, 0, 0)
|
|
||||||
spos1 = (-width * (xn - 1), 0, -width * (zn - 2))
|
|
||||||
spos2 = (-width * xn, 0, -width * (zn - 1))
|
|
||||||
|
|
||||||
###
|
|
||||||
# Bounding box
|
|
||||||
##
|
|
||||||
if self.direction == [1, 0, 0]:
|
|
||||||
sk = geompy.Sketcher3D()
|
|
||||||
sk.addPointsAbsolute(0, 0, -zh)
|
|
||||||
sk.addPointsAbsolute(-xl, yw, -zh)
|
|
||||||
sk.addPointsAbsolute(-xl, yw, zh)
|
|
||||||
sk.addPointsAbsolute(0, 0, zh)
|
|
||||||
sk.addPointsAbsolute(0, 0, -zh)
|
|
||||||
|
|
||||||
inletface = geompy.MakeFaceWires([sk.wire()], True)
|
|
||||||
vecflow = geompy.GetNormal(inletface)
|
|
||||||
poreCell = geompy.MakePrismVecH(inletface, vecflow, length)
|
|
||||||
|
|
||||||
elif self.direction == [0, 0, 1]:
|
|
||||||
sk = geompy.Sketcher3D()
|
|
||||||
sk.addPointsAbsolute(0, 0, -zh)
|
|
||||||
sk.addPointsAbsolute(xl, yw, -zh)
|
|
||||||
sk.addPointsAbsolute(0, 2 * yw, -zh)
|
|
||||||
sk.addPointsAbsolute(-xl, yw, -zh)
|
|
||||||
sk.addPointsAbsolute(0, 0, -zh)
|
|
||||||
|
|
||||||
inletface = geompy.MakeFaceWires([sk.wire()], True)
|
|
||||||
vecflow = geompy.GetNormal(inletface)
|
|
||||||
poreCell = geompy.MakePrismVecH(inletface, vecflow, 2 * zh)
|
|
||||||
|
|
||||||
[_, _, self.volumeCell] = geompy.BasicProperties(poreCell, theTolerance = 1e-06)
|
|
||||||
|
|
||||||
inletface = geompy.MakeScaleTransform(inletface, oo, scale)
|
|
||||||
poreCell = geompy.MakeScaleTransform(poreCell, oo, scale)
|
|
||||||
|
|
||||||
faces = geompy.ExtractShapes(poreCell, geompy.ShapeType["FACE"], False)
|
|
||||||
symetryface = []
|
|
||||||
|
|
||||||
for face in faces:
|
|
||||||
norm = geompy.GetNormal(face)
|
|
||||||
angle = round(geompy.GetAngle(norm, vecflow), 0)
|
|
||||||
|
|
||||||
if (angle == 0 or angle == 180) and not face == inletface:
|
|
||||||
outletface = face
|
|
||||||
|
|
||||||
else:
|
|
||||||
symetryface.append(face)
|
|
||||||
|
|
||||||
elif self.direction == [1, 1, 1]:
|
|
||||||
###
|
|
||||||
# Parameters
|
|
||||||
##
|
|
||||||
xn, yn, zn = 3, 3, 3
|
|
||||||
|
|
||||||
length = 2 * self.r0
|
|
||||||
width = self.L / 2
|
|
||||||
diag = self.L * sqrt(3)
|
|
||||||
height = diag / 3
|
|
||||||
|
|
||||||
point = []
|
|
||||||
xl, yw, zh = -(xn - 2) * self.L / 3, -(yn - 2) * self.L / 3, -(zn - 2) * self.L / 3
|
|
||||||
point.append((-2 * width / 3 + xl, -2 * width / 3 + yw, width / 3 + zh))
|
|
||||||
point.append((0 + xl, -width + yw, 0 + zh))
|
|
||||||
point.append((width / 3 + xl, -2 * width / 3 + yw, -2 * width / 3 + zh))
|
|
||||||
point.append((0 + xl, 0 + yw, -width + zh))
|
|
||||||
point.append((-2 * width / 3 + xl, width / 3 + yw, -2 * width / 3 + zh))
|
|
||||||
point.append((-width + xl, 0 + yw, 0 + zh))
|
|
||||||
point.append((-2 * width / 3 + xl, -2 * width / 3 + yw, width / 3 + zh))
|
|
||||||
|
|
||||||
scale = 100
|
|
||||||
oo = geompy.MakeVertex(0, 0, 0)
|
|
||||||
spos1 = (-width * (xn - 1), 0, -width * (zn - 2))
|
|
||||||
spos2 = (-width * xn, 0, -width * (zn - 1))
|
|
||||||
|
|
||||||
###
|
|
||||||
# Bounding box
|
|
||||||
##
|
|
||||||
sk = geompy.Sketcher3D()
|
|
||||||
|
|
||||||
for p in point:
|
|
||||||
sk.addPointsAbsolute(*p)
|
|
||||||
|
|
||||||
inletface = geompy.MakeFaceWires([sk.wire()], False)
|
|
||||||
vecflow = geompy.GetNormal(inletface)
|
|
||||||
poreCell = geompy.MakePrismVecH(inletface, vecflow, self.L * sqrt(3))
|
|
||||||
|
|
||||||
[_, _, self.volumeCell] = geompy.BasicProperties(poreCell, theTolerance = 1e-06)
|
|
||||||
|
|
||||||
inletface = geompy.MakeScaleTransform(inletface, oo, scale)
|
|
||||||
poreCell = geompy.MakeScaleTransform(poreCell, oo, scale)
|
|
||||||
|
|
||||||
faces = geompy.ExtractShapes(poreCell, geompy.ShapeType["FACE"], False)
|
|
||||||
symetryface = []
|
|
||||||
|
|
||||||
for face in faces:
|
|
||||||
norm = geompy.GetNormal(face)
|
|
||||||
angle = round(geompy.GetAngle(norm, vecflow), 0)
|
|
||||||
|
|
||||||
if (angle == 0 or angle == 180) and not face == inletface:
|
|
||||||
outletface = face
|
|
||||||
|
|
||||||
else:
|
|
||||||
symetryface.append(face)
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise Exception(f"Direction { self.direction } is not implemented")
|
|
||||||
|
|
||||||
###
|
|
||||||
# Grains
|
|
||||||
##
|
|
||||||
ox = geompy.MakeVectorDXDYDZ(1, 0, 0)
|
|
||||||
oy = geompy.MakeVectorDXDYDZ(0, 1, 0)
|
|
||||||
oz = geompy.MakeVectorDXDYDZ(0, 0, 1)
|
|
||||||
xy = geompy.MakeVectorDXDYDZ(1, 1, 0)
|
|
||||||
xmy = geompy.MakeVectorDXDYDZ(1, -1, 0)
|
|
||||||
|
|
||||||
grain = geompy.MakeSpherePntR(geompy.MakeVertex(*spos1), self.radius)
|
|
||||||
lattice1 = geompy.MakeMultiTranslation2D(grain, xy, length, xn, xmy, length, yn)
|
|
||||||
lattice1 = geompy.MakeMultiTranslation1D(lattice1, oz, self.L, zn - 1)
|
|
||||||
|
|
||||||
grain = geompy.MakeSpherePntR(geompy.MakeVertex(*spos2), self.radius)
|
|
||||||
lattice2 = geompy.MakeMultiTranslation2D(grain, xy, length, xn + 1, xmy, length, yn + 1)
|
|
||||||
lattice2 = geompy.MakeMultiTranslation1D(lattice2, oz, self.L, zn)
|
|
||||||
|
|
||||||
grains = geompy.ExtractShapes(lattice1, geompy.ShapeType["SOLID"], True)
|
|
||||||
grains += geompy.ExtractShapes(lattice2, geompy.ShapeType["SOLID"], True)
|
|
||||||
grains = geompy.MakeFuseList(grains, False, False)
|
|
||||||
|
|
||||||
grains = geompy.MakeScaleTransform(grains, oo, scale)
|
|
||||||
grainsOrigin = None
|
|
||||||
|
|
||||||
if self.filletsEnabled:
|
|
||||||
grainsOrigin = geompy.MakeScaleTransform(grains, oo, 1 / scale)
|
|
||||||
grains = geompy.MakeFilletAll(grains, self.fillets * scale)
|
|
||||||
|
|
||||||
###
|
|
||||||
# Groups
|
|
||||||
##
|
|
||||||
shape = geompy.MakeCutList(poreCell, [grains])
|
|
||||||
shape = geompy.MakeScaleTransform(shape, oo, 1 / scale, theName = "faceCentered")
|
|
||||||
|
|
||||||
sall = geompy.CreateGroup(shape, geompy.ShapeType["FACE"])
|
|
||||||
geompy.UnionIDs(sall,
|
|
||||||
geompy.SubShapeAllIDs(shape, geompy.ShapeType["FACE"]))
|
|
||||||
|
|
||||||
inlet = geompy.CreateGroup(shape, geompy.ShapeType["FACE"], theName = "inlet")
|
|
||||||
inletshape = geompy.MakeCutList(inletface, [grains])
|
|
||||||
inletshape = geompy.MakeScaleTransform(inletshape, oo, 1 / scale)
|
|
||||||
geompy.UnionList(inlet, geompy.SubShapeAll(
|
|
||||||
geompy.GetInPlace(shape, inletshape, True), geompy.ShapeType["FACE"]))
|
|
||||||
|
|
||||||
outlet = geompy.CreateGroup(shape, geompy.ShapeType["FACE"], theName = "outlet")
|
|
||||||
outletshape = geompy.MakeCutList(outletface, [grains])
|
|
||||||
outletshape = geompy.MakeScaleTransform(outletshape, oo, 1 / scale)
|
|
||||||
geompy.UnionList(outlet, geompy.SubShapeAll(
|
|
||||||
geompy.GetInPlace(shape, outletshape, True), geompy.ShapeType["FACE"]))
|
|
||||||
|
|
||||||
symetry = []
|
|
||||||
for (n, face) in enumerate(symetryface):
|
|
||||||
name = "symetry" + str(n)
|
|
||||||
symetry.append(geompy.CreateGroup(shape, geompy.ShapeType["FACE"], theName = name))
|
|
||||||
symetryshape = geompy.MakeCutList(face, [grains])
|
|
||||||
symetryshape = geompy.MakeScaleTransform(symetryshape, oo, 1 / scale)
|
|
||||||
geompy.UnionList(symetry[n], geompy.SubShapeAll(
|
|
||||||
geompy.GetInPlace(shape, symetryshape, True), geompy.ShapeType["FACE"]))
|
|
||||||
|
|
||||||
groups = []
|
|
||||||
groups.append(inlet)
|
|
||||||
groups.append(outlet)
|
|
||||||
groups.extend(symetry)
|
|
||||||
|
|
||||||
if self.filletsEnabled:
|
|
||||||
strips = geompy.CreateGroup(shape, geompy.ShapeType["FACE"], theName = "strips")
|
|
||||||
shapeShell = geompy.ExtractShapes(shape, geompy.ShapeType["SHELL"], True)
|
|
||||||
stripsShape = geompy.MakeCutList(shapeShell[0], groups + [grainsOrigin])
|
|
||||||
geompy.UnionList(strips, geompy.SubShapeAll(
|
|
||||||
geompy.GetInPlace(shape, stripsShape, True), geompy.ShapeType["FACE"]))
|
|
||||||
groups.append(strips)
|
|
||||||
|
|
||||||
wall = geompy.CutListOfGroups([sall], groups, theName = "wall")
|
|
||||||
groups.append(wall)
|
|
||||||
|
|
||||||
return shape, groups
|
|
||||||
|
|
||||||
|
|
@ -1,215 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This file is part of anisotropy.
|
|
||||||
# License: GNU GPL version 3, see the file "LICENSE" for details.
|
|
||||||
|
|
||||||
from math import pi, sqrt
|
|
||||||
from anisotropy.salomepl import geometry
|
|
||||||
|
|
||||||
class Simple(object):
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
|
|
||||||
self.direction = kwargs.get("direction", [1, 0, 0])
|
|
||||||
self.theta = kwargs.get("theta", 0.01)
|
|
||||||
self.r0 = kwargs.get("r0", 1)
|
|
||||||
self.L = kwargs.get("L", 2 * self.r0)
|
|
||||||
self.radius = kwargs.get("radius", self.r0 / (1 - self.theta))
|
|
||||||
self.filletsEnabled = kwargs.get("filletsEnabled", False)
|
|
||||||
self.fillets = kwargs.get("fillets", 0)
|
|
||||||
self.volumeCell = None
|
|
||||||
|
|
||||||
|
|
||||||
def build(self):
|
|
||||||
|
|
||||||
geompy = geometry.getGeom()
|
|
||||||
|
|
||||||
###
|
|
||||||
# Pore Cell
|
|
||||||
##
|
|
||||||
if self.direction in [[1, 0, 0], [0, 0, 1]]:
|
|
||||||
###
|
|
||||||
# Parameters
|
|
||||||
##
|
|
||||||
xn, yn, zn = 3, 3, 3
|
|
||||||
|
|
||||||
length = self.L * sqrt(2)
|
|
||||||
width = self.L * sqrt(2)
|
|
||||||
height = self.L
|
|
||||||
|
|
||||||
xl = sqrt(length ** 2 * 0.5)
|
|
||||||
yw = xl
|
|
||||||
zh = height
|
|
||||||
|
|
||||||
scale = 100
|
|
||||||
oo = geompy.MakeVertex(0, 0, 0)
|
|
||||||
|
|
||||||
###
|
|
||||||
# Bounding box
|
|
||||||
##
|
|
||||||
if self.direction == [1, 0, 0]:
|
|
||||||
sk = geompy.Sketcher3D()
|
|
||||||
sk.addPointsAbsolute(xl, 0, 0)
|
|
||||||
sk.addPointsAbsolute(0, yw, 0)
|
|
||||||
sk.addPointsAbsolute(0, yw, zh)
|
|
||||||
sk.addPointsAbsolute(xl, 0, zh)
|
|
||||||
sk.addPointsAbsolute(xl, 0, 0)
|
|
||||||
|
|
||||||
inletface = geompy.MakeFaceWires([sk.wire()], True)
|
|
||||||
vecflow = geompy.GetNormal(inletface)
|
|
||||||
poreCell = geompy.MakePrismVecH(inletface, vecflow, width)
|
|
||||||
|
|
||||||
elif self.direction == [0, 0, 1]:
|
|
||||||
sk = geompy.Sketcher3D()
|
|
||||||
sk.addPointsAbsolute(0, yw, 0)
|
|
||||||
sk.addPointsAbsolute(xl, 0, 0)
|
|
||||||
sk.addPointsAbsolute(2 * xl, yw, 0)
|
|
||||||
sk.addPointsAbsolute(xl, 2 * yw, 0)
|
|
||||||
sk.addPointsAbsolute(0, yw, 0)
|
|
||||||
|
|
||||||
inletface = geompy.MakeFaceWires([sk.wire()], True)
|
|
||||||
vecflow = geompy.GetNormal(inletface)
|
|
||||||
poreCell = geompy.MakePrismVecH(inletface, vecflow, height)
|
|
||||||
|
|
||||||
[_, _, self.volumeCell] = geompy.BasicProperties(poreCell, theTolerance = 1e-06)
|
|
||||||
|
|
||||||
inletface = geompy.MakeScaleTransform(inletface, oo, scale)
|
|
||||||
poreCell = geompy.MakeScaleTransform(poreCell, oo, scale)
|
|
||||||
|
|
||||||
faces = geompy.ExtractShapes(poreCell, geompy.ShapeType["FACE"], False)
|
|
||||||
symetryface = []
|
|
||||||
|
|
||||||
for face in faces:
|
|
||||||
norm = geompy.GetNormal(face)
|
|
||||||
angle = round(geompy.GetAngle(norm, vecflow), 0)
|
|
||||||
|
|
||||||
if (angle == 0 or angle == 180) and not face == inletface:
|
|
||||||
outletface = face
|
|
||||||
|
|
||||||
else:
|
|
||||||
symetryface.append(face)
|
|
||||||
|
|
||||||
elif self.direction == [1, 1, 1]:
|
|
||||||
###
|
|
||||||
# Parameters
|
|
||||||
##
|
|
||||||
xn, yn, zn = 3, 3, 3
|
|
||||||
|
|
||||||
length = self.L * sqrt(2)
|
|
||||||
width = self.L * sqrt(2)
|
|
||||||
height = self.L
|
|
||||||
|
|
||||||
point = []
|
|
||||||
xl, yw, zh = -self.L - self.L / 6, -self.L - self.L / 6, -self.L / 6
|
|
||||||
point.append((self.L + xl, self.L + yw, self.L + zh))
|
|
||||||
point.append((5 * self.L / 3 + xl, 2 * self.L / 3 + yw, 2 * self.L / 3 + zh))
|
|
||||||
point.append((2 * self.L + xl, self.L + yw, 0 + zh))
|
|
||||||
point.append((5 * self.L / 3 + xl, 5 * self.L / 3 + yw, -self.L / 3 + zh))
|
|
||||||
point.append((self.L + xl, 2 * self.L + yw, 0 + zh))
|
|
||||||
point.append((2 * self.L / 3 + xl, 5 * self.L / 3 + yw, 2 * self.L / 3 + zh))
|
|
||||||
point.append((self.L + xl, self.L + yw, self.L + zh))
|
|
||||||
|
|
||||||
scale = 100
|
|
||||||
oo = geompy.MakeVertex(0, 0, 0)
|
|
||||||
|
|
||||||
###
|
|
||||||
# Bounding box
|
|
||||||
##
|
|
||||||
sk = geompy.Sketcher3D()
|
|
||||||
|
|
||||||
for p in point:
|
|
||||||
sk.addPointsAbsolute(*p)
|
|
||||||
|
|
||||||
inletface = geompy.MakeFaceWires([sk.wire()], False)
|
|
||||||
vecflow = geompy.GetNormal(inletface)
|
|
||||||
poreCell = geompy.MakePrismVecH(inletface, vecflow, self.L * sqrt(3))
|
|
||||||
|
|
||||||
[_, _, self.volumeCell] = geompy.BasicProperties(poreCell, theTolerance = 1e-06)
|
|
||||||
|
|
||||||
inletface = geompy.MakeScaleTransform(inletface, oo, scale)
|
|
||||||
poreCell = geompy.MakeScaleTransform(poreCell, oo, scale)
|
|
||||||
|
|
||||||
faces = geompy.ExtractShapes(poreCell, geompy.ShapeType["FACE"], False)
|
|
||||||
symetryface = []
|
|
||||||
|
|
||||||
for face in faces:
|
|
||||||
norm = geompy.GetNormal(face)
|
|
||||||
angle = round(geompy.GetAngle(norm, vecflow), 0)
|
|
||||||
|
|
||||||
if (angle == 0 or angle == 180) and not face == inletface:
|
|
||||||
outletface = face
|
|
||||||
|
|
||||||
else:
|
|
||||||
symetryface.append(face)
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise Exception(f"Direction { self.direction } is not implemented")
|
|
||||||
|
|
||||||
###
|
|
||||||
# Grains
|
|
||||||
##
|
|
||||||
ox = geompy.MakeVectorDXDYDZ(1, 0, 0)
|
|
||||||
oy = geompy.MakeVectorDXDYDZ(0, 1, 0)
|
|
||||||
oz = geompy.MakeVectorDXDYDZ(0, 0, 1)
|
|
||||||
|
|
||||||
grain = geompy.MakeSphereR(self.radius)
|
|
||||||
lattice = geompy.MakeMultiTranslation2D(grain, ox, self.L, xn, oy, self.L, yn)
|
|
||||||
lattice = geompy.MakeMultiTranslation1D(lattice, oz, self.L, zn)
|
|
||||||
|
|
||||||
grains = geompy.ExtractShapes(lattice, geompy.ShapeType["SOLID"], True)
|
|
||||||
grains = geompy.MakeFuseList(grains, False, False)
|
|
||||||
|
|
||||||
grains = geompy.MakeScaleTransform(grains, oo, scale)
|
|
||||||
grainsOrigin = None
|
|
||||||
|
|
||||||
if self.filletsEnabled:
|
|
||||||
grainsOrigin = geompy.MakeScaleTransform(grains, oo, 1 / scale)
|
|
||||||
grains = geompy.MakeFilletAll(grains, self.fillets * scale)
|
|
||||||
|
|
||||||
###
|
|
||||||
# Groups
|
|
||||||
##
|
|
||||||
shape = geompy.MakeCutList(poreCell, [grains])
|
|
||||||
shape = geompy.MakeScaleTransform(shape, oo, 1 / scale, theName = "simple")
|
|
||||||
|
|
||||||
sall = geompy.CreateGroup(shape, geompy.ShapeType["FACE"])
|
|
||||||
geompy.UnionIDs(sall,
|
|
||||||
geompy.SubShapeAllIDs(shape, geompy.ShapeType["FACE"]))
|
|
||||||
|
|
||||||
inlet = geompy.CreateGroup(shape, geompy.ShapeType["FACE"], theName = "inlet")
|
|
||||||
inletshape = geompy.MakeCutList(inletface, [grains])
|
|
||||||
inletshape = geompy.MakeScaleTransform(inletshape, oo, 1 / scale)
|
|
||||||
geompy.UnionList(inlet, geompy.SubShapeAll(
|
|
||||||
geompy.GetInPlace(shape, inletshape, True), geompy.ShapeType["FACE"]))
|
|
||||||
|
|
||||||
outlet = geompy.CreateGroup(shape, geompy.ShapeType["FACE"], theName = "outlet")
|
|
||||||
outletshape = geompy.MakeCutList(outletface, [grains])
|
|
||||||
outletshape = geompy.MakeScaleTransform(outletshape, oo, 1 / scale)
|
|
||||||
geompy.UnionList(outlet, geompy.SubShapeAll(
|
|
||||||
geompy.GetInPlace(shape, outletshape, True), geompy.ShapeType["FACE"]))
|
|
||||||
|
|
||||||
symetry = []
|
|
||||||
for (n, face) in enumerate(symetryface):
|
|
||||||
name = "symetry" + str(n)
|
|
||||||
symetry.append(geompy.CreateGroup(shape, geompy.ShapeType["FACE"], theName = name))
|
|
||||||
symetryshape = geompy.MakeCutList(face, [grains])
|
|
||||||
symetryshape = geompy.MakeScaleTransform(symetryshape, oo, 1 / scale)
|
|
||||||
geompy.UnionList(symetry[n], geompy.SubShapeAll(
|
|
||||||
geompy.GetInPlace(shape, symetryshape, True), geompy.ShapeType["FACE"]))
|
|
||||||
|
|
||||||
groups = []
|
|
||||||
groups.append(inlet)
|
|
||||||
groups.append(outlet)
|
|
||||||
groups.extend(symetry)
|
|
||||||
|
|
||||||
if self.filletsEnabled:
|
|
||||||
strips = geompy.CreateGroup(shape, geompy.ShapeType["FACE"], theName = "strips")
|
|
||||||
shapeShell = geompy.ExtractShapes(shape, geompy.ShapeType["SHELL"], True)
|
|
||||||
stripsShape = geompy.MakeCutList(shapeShell[0], groups + [grainsOrigin])
|
|
||||||
geompy.UnionList(strips, geompy.SubShapeAll(
|
|
||||||
geompy.GetInPlace(shape, stripsShape, True), geompy.ShapeType["FACE"]))
|
|
||||||
groups.append(strips)
|
|
||||||
|
|
||||||
wall = geompy.CutListOfGroups([sall], groups, theName = "wall")
|
|
||||||
groups.append(wall)
|
|
||||||
|
|
||||||
return shape, groups
|
|
||||||
|
|
21
anisotropy/shaping/__init__.py
Normal file
21
anisotropy/shaping/__init__.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Shaping is a library for using OCC shapes, provides more convient
|
||||||
|
functionality with power NumPy and Python OOP and contains interesing
|
||||||
|
primitives.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
from .shape import Shape
|
||||||
|
from .periodic import Periodic
|
||||||
|
|
||||||
|
from . import primitives
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"utils",
|
||||||
|
"primitives",
|
||||||
|
"Shape",
|
||||||
|
"Periodic"
|
||||||
|
]
|
69
anisotropy/shaping/periodic.py
Normal file
69
anisotropy/shaping/periodic.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from . import Shape
|
||||||
|
|
||||||
|
|
||||||
|
class Periodic(Shape):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
alpha: float = None,
|
||||||
|
r0: float = 1,
|
||||||
|
L: float = None,
|
||||||
|
filletsEnabled: bool = True,
|
||||||
|
gamma = None,
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
|
"""A Periodic object contains periodic structure.
|
||||||
|
|
||||||
|
:param alpha:
|
||||||
|
Spheres overlap parameter.
|
||||||
|
:param r0:
|
||||||
|
Initial spheres radius.
|
||||||
|
:param L:
|
||||||
|
Side length of periodic cell. Depends on r0.
|
||||||
|
:param gamma:
|
||||||
|
Angle between lines that connect centers of spheres.
|
||||||
|
:param filletsEnabled:
|
||||||
|
If True, calculate fillets beetween spheres.
|
||||||
|
"""
|
||||||
|
Shape.__init__(self)
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
self.alpha = alpha
|
||||||
|
self.r0 = r0
|
||||||
|
self.L = L
|
||||||
|
|
||||||
|
# for lattice
|
||||||
|
# self.theta = 0.5 * pi
|
||||||
|
|
||||||
|
self.gamma = gamma or np.pi - 2 * (0.5 * 0.5 * np.pi)
|
||||||
|
self.filletsEnabled = filletsEnabled
|
||||||
|
self.filletsScale = 0.8
|
||||||
|
|
||||||
|
# scale parameters for precision boolean operations
|
||||||
|
self.maximize = 1e+2
|
||||||
|
self.minimize = 1e-2
|
||||||
|
|
||||||
|
@property
|
||||||
|
def radius(self) -> float:
|
||||||
|
"""Spheres radius
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Radius.
|
||||||
|
"""
|
||||||
|
return self.r0 / (1 - self.alpha)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fillets(self):
|
||||||
|
"""Fillets radius beetween spheres.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Radius.
|
||||||
|
"""
|
||||||
|
analytical = self.r0 * np.sqrt(2) / np.sqrt(1 - np.cos(self.gamma)) - self.radius
|
||||||
|
rTol = 3
|
||||||
|
minRound = np.fix(10 ** rTol * analytical) * 10 ** -rTol
|
||||||
|
|
||||||
|
return minRound * self.filletsScale
|
510
anisotropy/shaping/primitives.py
Normal file
510
anisotropy/shaping/primitives.py
Normal file
@ -0,0 +1,510 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
from numpy import ndarray
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import netgen.occ as ng_occ
|
||||||
|
|
||||||
|
from . import Periodic
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
|
def simple(alpha: float, direction: list | ndarray, **kwargs) -> Periodic:
|
||||||
|
"""Simple periodic structure.
|
||||||
|
|
||||||
|
:param alpha:
|
||||||
|
Spheres overlap parameter.
|
||||||
|
:param direction:
|
||||||
|
Flow direction vector. This parameter affects the geometry type
|
||||||
|
and boundary (faces) names.
|
||||||
|
:return:
|
||||||
|
Periodic object.
|
||||||
|
"""
|
||||||
|
# base object
|
||||||
|
pc = Periodic(
|
||||||
|
alpha = alpha,
|
||||||
|
gamma = np.pi - 2 * (0.5 * 0.5 * np.pi)
|
||||||
|
)
|
||||||
|
|
||||||
|
# additional parameters
|
||||||
|
pc.__dict__.update(kwargs)
|
||||||
|
pc.L = 2 * pc.r0
|
||||||
|
pc.direction = np.asarray(direction)
|
||||||
|
pc.length = 0.
|
||||||
|
|
||||||
|
# additional attributes
|
||||||
|
pc.cell: ng_occ.Solid = None
|
||||||
|
pc.lattice: ng_occ.Solid = None
|
||||||
|
|
||||||
|
# constants
|
||||||
|
zero = ng_occ.Pnt(0, 0, 0)
|
||||||
|
|
||||||
|
# Lattice
|
||||||
|
spheres = []
|
||||||
|
|
||||||
|
for zn in range(3):
|
||||||
|
z = zn * pc.L
|
||||||
|
|
||||||
|
for yn in range(3):
|
||||||
|
y = yn * pc.L
|
||||||
|
|
||||||
|
for xn in range(3):
|
||||||
|
x = xn * pc.L
|
||||||
|
|
||||||
|
spheres.append(ng_occ.Sphere(ng_occ.Pnt(x, y, z), pc.radius))
|
||||||
|
|
||||||
|
pc.lattice = np.sum(spheres)
|
||||||
|
|
||||||
|
if pc.filletsEnabled:
|
||||||
|
pc.lattice = pc.lattice.Scale(zero, pc.maximize)
|
||||||
|
pc.lattice = (
|
||||||
|
pc.lattice
|
||||||
|
.MakeFillet(pc.lattice.edges, pc.fillets * pc.maximize)
|
||||||
|
.Scale(zero, pc.minimize)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Inlet face
|
||||||
|
if np.all(pc.direction == [1., 0., 0.]):
|
||||||
|
length = pc.L * np.sqrt(2)
|
||||||
|
width = pc.L * np.sqrt(2)
|
||||||
|
height = pc.L
|
||||||
|
|
||||||
|
xl = np.sqrt(length ** 2 * 0.5)
|
||||||
|
yw = xl
|
||||||
|
zh = height
|
||||||
|
|
||||||
|
vertices = np.array([
|
||||||
|
(xl, 0, 0),
|
||||||
|
(0, yw, 0),
|
||||||
|
(0, yw, zh),
|
||||||
|
(xl, 0, zh)
|
||||||
|
])
|
||||||
|
extr = width
|
||||||
|
|
||||||
|
elif np.all(pc.direction == [0., 0., 1.]):
|
||||||
|
length = pc.L * np.sqrt(2)
|
||||||
|
width = pc.L * np.sqrt(2)
|
||||||
|
height = pc.L
|
||||||
|
|
||||||
|
xl = np.sqrt(length ** 2 * 0.5)
|
||||||
|
yw = xl
|
||||||
|
zh = height
|
||||||
|
|
||||||
|
vertices = np.array([
|
||||||
|
(0, yw, 0),
|
||||||
|
(xl, 0, 0),
|
||||||
|
(2 * xl, yw, 0),
|
||||||
|
(xl, 2 * yw, 0)
|
||||||
|
])
|
||||||
|
extr = height
|
||||||
|
|
||||||
|
elif np.all(pc.direction == [1., 1., 1.]):
|
||||||
|
length = pc.L * np.sqrt(2)
|
||||||
|
width = pc.L * np.sqrt(2)
|
||||||
|
height = pc.L
|
||||||
|
|
||||||
|
xl = -pc.L - pc.L / 6
|
||||||
|
yw = -pc.L - pc.L / 6
|
||||||
|
zh = -pc.L / 6
|
||||||
|
|
||||||
|
vertices = np.array([
|
||||||
|
(pc.L + xl, pc.L + yw, pc.L + zh),
|
||||||
|
(5 * pc.L / 3 + xl, 2 * pc.L / 3 + yw, 2 * pc.L / 3 + zh),
|
||||||
|
(2 * pc.L + xl, pc.L + yw, 0 + zh),
|
||||||
|
(5 * pc.L / 3 + xl, 5 * pc.L / 3 + yw, -pc.L / 3 + zh),
|
||||||
|
(pc.L + xl, 2 * pc.L + yw, 0 + zh),
|
||||||
|
(2 * pc.L / 3 + xl, 5 * pc.L / 3 + yw, 2 * pc.L / 3 + zh)
|
||||||
|
])
|
||||||
|
extr = pc.L * np.sqrt(3)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception(f"Direction { pc.direction } is not implemented")
|
||||||
|
|
||||||
|
# Cell
|
||||||
|
circuit = ng_occ.Wire([
|
||||||
|
ng_occ.Segment(ng_occ.Pnt(*v1), ng_occ.Pnt(*v2))
|
||||||
|
for v1, v2 in zip(vertices, np.roll(vertices, -1, axis = 0))
|
||||||
|
])
|
||||||
|
inletface = ng_occ.Face(circuit)
|
||||||
|
inletface.name = "inlet"
|
||||||
|
|
||||||
|
vecFlow = utils.normal(inletface)
|
||||||
|
pc.cell = inletface.Extrude(extr, ng_occ.Vec(*vecFlow))
|
||||||
|
pc.length = extr
|
||||||
|
|
||||||
|
# Boundary faces
|
||||||
|
symetryId = 0
|
||||||
|
|
||||||
|
for face in pc.cell.faces:
|
||||||
|
fNorm = utils.normal(face)
|
||||||
|
fAngle = utils.angle(vecFlow, fNorm)
|
||||||
|
|
||||||
|
if fAngle == 0 or fAngle == np.pi:
|
||||||
|
if np.all(utils.pos(face.center) == utils.pos(inletface.center)):
|
||||||
|
face.name = "inlet"
|
||||||
|
|
||||||
|
else:
|
||||||
|
face.name = "outlet"
|
||||||
|
|
||||||
|
else:
|
||||||
|
face.name = f"symetry{ symetryId }"
|
||||||
|
symetryId += 1
|
||||||
|
|
||||||
|
# Main shape
|
||||||
|
pc.shape = pc.cell - pc.lattice
|
||||||
|
|
||||||
|
assert len(pc.shape.solids) == 1, "Expected single solid shape"
|
||||||
|
|
||||||
|
pc.shape = pc.shape.solids[0]
|
||||||
|
|
||||||
|
# Boundary faces (walls)
|
||||||
|
for face in pc.shape.faces:
|
||||||
|
if face.name not in ["inlet", "outlet", *[ f"symetry{ n }" for n in range(6) ]]:
|
||||||
|
face.name = "wall"
|
||||||
|
|
||||||
|
return pc
|
||||||
|
|
||||||
|
|
||||||
|
def body_centered(alpha: float, direction: list | ndarray, **kwargs) -> Periodic:
|
||||||
|
"""Body centered periodic structure.
|
||||||
|
|
||||||
|
:param alpha:
|
||||||
|
Spheres overlap parameter.
|
||||||
|
:param direction:
|
||||||
|
Flow direction vector. This parameter affects the geometry type
|
||||||
|
and boundary (faces) names.
|
||||||
|
:return:
|
||||||
|
Periodic object.
|
||||||
|
"""
|
||||||
|
# base object
|
||||||
|
pc = Periodic(
|
||||||
|
alpha = alpha,
|
||||||
|
gamma = np.pi - 2 * np.arccos(np.sqrt(2 / 3))
|
||||||
|
)
|
||||||
|
|
||||||
|
# additional parameters
|
||||||
|
pc.__dict__.update(kwargs)
|
||||||
|
pc.L = 4 / np.sqrt(3) * pc.r0
|
||||||
|
pc.direction = np.asarray(direction)
|
||||||
|
|
||||||
|
# additional attributes
|
||||||
|
pc.cell: ng_occ.Solid = None
|
||||||
|
pc.lattice: ng_occ.Solid = None
|
||||||
|
pc.length = 0.
|
||||||
|
|
||||||
|
# constants
|
||||||
|
zero = ng_occ.Pnt(0, 0, 0)
|
||||||
|
|
||||||
|
# Lattice
|
||||||
|
spheres = []
|
||||||
|
|
||||||
|
for zn in range(3):
|
||||||
|
z = zn * pc.L
|
||||||
|
z2 = z - 0.5 * pc.L
|
||||||
|
|
||||||
|
for yn in range(3):
|
||||||
|
y = yn * pc.L
|
||||||
|
y2 = y - 0.5 * pc.L
|
||||||
|
|
||||||
|
for xn in range(3):
|
||||||
|
x = xn * pc.L
|
||||||
|
x2 = x - 0.5 * pc.L
|
||||||
|
|
||||||
|
spheres.append(ng_occ.Sphere(ng_occ.Pnt(x, y, z), pc.radius))
|
||||||
|
# shifted
|
||||||
|
spheres.append(ng_occ.Sphere(ng_occ.Pnt(x2, y2, z2), pc.radius))
|
||||||
|
|
||||||
|
pc.lattice = np.sum(spheres)
|
||||||
|
|
||||||
|
if pc.filletsEnabled:
|
||||||
|
pc.lattice = pc.lattice.Scale(zero, pc.maximize)
|
||||||
|
pc.lattice = (
|
||||||
|
pc.lattice
|
||||||
|
.MakeFillet(pc.lattice.edges, pc.fillets * pc.maximize)
|
||||||
|
.Scale(zero, pc.minimize)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Inlet face
|
||||||
|
if np.all(pc.direction == [1., 0., 0.]):
|
||||||
|
# length = 2 * pc.r0
|
||||||
|
# width = pc.L / 2
|
||||||
|
diag = pc.L * np.sqrt(2)
|
||||||
|
height = pc.L
|
||||||
|
|
||||||
|
xl = np.sqrt(diag ** 2 + diag ** 2) * 0.5
|
||||||
|
yw = xl
|
||||||
|
zh = height
|
||||||
|
|
||||||
|
vertices = np.array([
|
||||||
|
(xl, 0, 0),
|
||||||
|
(0, yw, 0),
|
||||||
|
(0, yw, zh),
|
||||||
|
(xl, 0, zh)
|
||||||
|
])
|
||||||
|
extr = diag
|
||||||
|
|
||||||
|
elif np.all(pc.direction == [0., 0., 1.]):
|
||||||
|
# length = 2 * pc.r0
|
||||||
|
# width = pc.L / 2
|
||||||
|
diag = pc.L * np.sqrt(2)
|
||||||
|
height = pc.L
|
||||||
|
|
||||||
|
xl = np.sqrt(diag ** 2 + diag ** 2) * 0.5
|
||||||
|
yw = xl
|
||||||
|
zh = height
|
||||||
|
|
||||||
|
vertices = np.array([
|
||||||
|
(0, yw, 0),
|
||||||
|
(xl, 0, 0),
|
||||||
|
(2 * xl, yw, 0),
|
||||||
|
(xl, 2 * yw, 0)
|
||||||
|
])
|
||||||
|
extr = height
|
||||||
|
|
||||||
|
elif np.all(pc.direction == [1., 1., 1.]):
|
||||||
|
# length = 2 * pc.r0
|
||||||
|
# width = pc.L / 2
|
||||||
|
diag = pc.L * np.sqrt(2)
|
||||||
|
height = diag / 3
|
||||||
|
|
||||||
|
xl = -pc.L / 4
|
||||||
|
yw = -pc.L / 4
|
||||||
|
zh = -pc.L / 4
|
||||||
|
|
||||||
|
vertices = np.array([
|
||||||
|
(pc.L / 3 + xl, pc.L / 3 + yw, 4 * pc.L / 3 + zh),
|
||||||
|
(pc.L + xl, 0 + yw, pc.L + zh),
|
||||||
|
(4 * pc.L / 3 + xl, pc.L / 3 + yw, pc.L / 3 + zh),
|
||||||
|
(pc.L + xl, pc.L + yw, 0 + zh),
|
||||||
|
(pc.L / 3 + xl, 4 * pc.L / 3 + yw, pc.L / 3 + zh),
|
||||||
|
(0 + xl, pc.L + yw, pc.L + zh)
|
||||||
|
])
|
||||||
|
extr = pc.L * np.sqrt(3)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception(f"Direction { pc.direction } is not implemented")
|
||||||
|
|
||||||
|
# Cell
|
||||||
|
circuit = ng_occ.Wire([
|
||||||
|
ng_occ.Segment(ng_occ.Pnt(*v1), ng_occ.Pnt(*v2))
|
||||||
|
for v1, v2 in zip(vertices, np.roll(vertices, -1, axis = 0))
|
||||||
|
])
|
||||||
|
inletface = ng_occ.Face(circuit)
|
||||||
|
inletface.name = "inlet"
|
||||||
|
|
||||||
|
vecFlow = utils.normal(inletface)
|
||||||
|
pc.cell = inletface.Extrude(extr, ng_occ.Vec(*vecFlow))
|
||||||
|
pc.length = extr
|
||||||
|
|
||||||
|
# Boundary faces
|
||||||
|
symetryId = 0
|
||||||
|
|
||||||
|
for face in pc.cell.faces:
|
||||||
|
fNorm = utils.normal(face)
|
||||||
|
fAngle = utils.angle(vecFlow, fNorm)
|
||||||
|
|
||||||
|
if fAngle == 0 or fAngle == np.pi:
|
||||||
|
if np.all(utils.pos(face.center) == utils.pos(inletface.center)):
|
||||||
|
face.name = "inlet"
|
||||||
|
|
||||||
|
else:
|
||||||
|
face.name = "outlet"
|
||||||
|
|
||||||
|
else:
|
||||||
|
face.name = f"symetry{ symetryId }"
|
||||||
|
symetryId += 1
|
||||||
|
|
||||||
|
# Main shape
|
||||||
|
pc.shape = pc.cell - pc.lattice
|
||||||
|
|
||||||
|
assert len(pc.shape.solids) == 1, "Expected single solid shape"
|
||||||
|
|
||||||
|
pc.shape = pc.shape.solids[0]
|
||||||
|
|
||||||
|
# Boundary faces (walls)
|
||||||
|
for face in pc.shape.faces:
|
||||||
|
if face.name not in ["inlet", "outlet", *[ f"symetry{ n }" for n in range(6) ]]:
|
||||||
|
face.name = "wall"
|
||||||
|
|
||||||
|
return pc
|
||||||
|
|
||||||
|
|
||||||
|
def face_centered(alpha: float, direction: list | ndarray, **kwargs) -> Periodic:
|
||||||
|
"""Face centered periodic structure.
|
||||||
|
|
||||||
|
:param alpha:
|
||||||
|
Spheres overlap parameter.
|
||||||
|
:param direction:
|
||||||
|
Flow direction vector. This parameter affects the geometry type
|
||||||
|
and boundary (faces) names.
|
||||||
|
:return:
|
||||||
|
Periodic object.
|
||||||
|
"""
|
||||||
|
# base class
|
||||||
|
pc = Periodic(
|
||||||
|
alpha = alpha,
|
||||||
|
gamma = 2 / 3 * np.pi
|
||||||
|
)
|
||||||
|
|
||||||
|
# additional parameters
|
||||||
|
pc.__dict__.update(kwargs)
|
||||||
|
pc.L = pc.r0 * 4 / np.sqrt(2)
|
||||||
|
pc.direction = np.asarray(direction)
|
||||||
|
pc.length = 0.
|
||||||
|
|
||||||
|
# additional attributes
|
||||||
|
pc.cell: ng_occ.Solid = None
|
||||||
|
pc.lattice: ng_occ.Solid = None
|
||||||
|
|
||||||
|
# constants
|
||||||
|
zero = ng_occ.Pnt(0, 0, 0)
|
||||||
|
|
||||||
|
# Lattice
|
||||||
|
spheres = []
|
||||||
|
x0 = 0
|
||||||
|
x20 = 0
|
||||||
|
z0 = -0.5 * pc.L * (3 - 2)
|
||||||
|
z20 = -0.5 * pc.L * (3 - 1)
|
||||||
|
|
||||||
|
for zn in range(3):
|
||||||
|
z = z0 + zn * pc.L
|
||||||
|
z2 = z20 + zn * pc.L
|
||||||
|
|
||||||
|
for yn in range(3):
|
||||||
|
y = yn * 2 * pc.r0
|
||||||
|
y2 = yn * 2 * pc.r0 + pc.r0
|
||||||
|
|
||||||
|
for xn in range(3):
|
||||||
|
x = x0 + xn * 2 * pc.r0
|
||||||
|
x2 = x20 + xn * 2 * pc.r0 + pc.r0
|
||||||
|
|
||||||
|
spheres.append(
|
||||||
|
ng_occ.Sphere(ng_occ.Pnt(x, y, z), pc.radius)
|
||||||
|
.Rotate(ng_occ.Axis(ng_occ.Pnt(x, y, z), ng_occ.X), 45)
|
||||||
|
.Rotate(ng_occ.Axis(ng_occ.Pnt(x, y, z), ng_occ.Z), 45)
|
||||||
|
)
|
||||||
|
# shifted
|
||||||
|
spheres.append(
|
||||||
|
ng_occ.Sphere(ng_occ.Pnt(x2, y2, z2), pc.radius)
|
||||||
|
.Rotate(ng_occ.Axis(ng_occ.Pnt(x2, y2, z2), ng_occ.X), 45)
|
||||||
|
.Rotate(ng_occ.Axis(ng_occ.Pnt(x2, y2, z2), ng_occ.Z), 45)
|
||||||
|
)
|
||||||
|
|
||||||
|
pc.lattice = (
|
||||||
|
np.sum(spheres)
|
||||||
|
.Move(ng_occ.Vec(-pc.r0 * 2, -pc.r0 * 2, 0))
|
||||||
|
.Rotate(ng_occ.Axis(zero, ng_occ.Z), 45)
|
||||||
|
)
|
||||||
|
|
||||||
|
if pc.filletsEnabled:
|
||||||
|
pc.lattice = pc.lattice.Scale(zero, pc.maximize)
|
||||||
|
pc.lattice = (
|
||||||
|
pc.lattice
|
||||||
|
.MakeFillet(pc.lattice.edges, pc.fillets * pc.maximize)
|
||||||
|
.Scale(zero, pc.minimize)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Inlet face
|
||||||
|
if np.all(pc.direction == [1., 0., 0.]):
|
||||||
|
length = 2 * pc.r0
|
||||||
|
width = pc.L / 2
|
||||||
|
# diag = pc.L * np.sqrt(3)
|
||||||
|
# height = diag / 3
|
||||||
|
|
||||||
|
xl = np.sqrt(length ** 2 + length ** 2) * 0.5
|
||||||
|
yw = xl
|
||||||
|
zh = width
|
||||||
|
|
||||||
|
vertices = np.array([
|
||||||
|
(0, 0, -zh),
|
||||||
|
(-xl, yw, -zh),
|
||||||
|
(-xl, yw, zh),
|
||||||
|
(0, 0, zh)
|
||||||
|
])
|
||||||
|
extr = length
|
||||||
|
|
||||||
|
elif np.all(pc.direction == [0., 0., 1.]):
|
||||||
|
length = 2 * pc.r0
|
||||||
|
width = pc.L / 2
|
||||||
|
# diag = pc.L * np.sqrt(3)
|
||||||
|
# height = diag / 3
|
||||||
|
|
||||||
|
xl = np.sqrt(length ** 2 + length ** 2) * 0.5
|
||||||
|
yw = xl
|
||||||
|
zh = width
|
||||||
|
|
||||||
|
vertices = np.array([
|
||||||
|
(0, 0, -zh),
|
||||||
|
(xl, yw, -zh),
|
||||||
|
(0, 2 * yw, -zh),
|
||||||
|
(-xl, yw, -zh)
|
||||||
|
])
|
||||||
|
extr = 2 * width
|
||||||
|
|
||||||
|
elif np.all(pc.direction == [1., 1., 1.]):
|
||||||
|
length = 2 * pc.r0
|
||||||
|
width = pc.L / 2
|
||||||
|
# diag = pc.L * np.sqrt(3)
|
||||||
|
# height = diag / 3
|
||||||
|
|
||||||
|
xl = -(3 - 2) * pc.L / 3
|
||||||
|
yw = -(3 - 2) * pc.L / 3
|
||||||
|
zh = -(3 - 2) * pc.L / 3
|
||||||
|
|
||||||
|
vertices = np.array([
|
||||||
|
(-2 * width / 3 + xl, -2 * width / 3 + yw, width / 3 + zh),
|
||||||
|
(0 + xl, -width + yw, 0 + zh),
|
||||||
|
(width / 3 + xl, -2 * width / 3 + yw, -2 * width / 3 + zh),
|
||||||
|
(0 + xl, 0 + yw, -width + zh),
|
||||||
|
(-2 * width / 3 + xl, width / 3 + yw, -2 * width / 3 + zh),
|
||||||
|
(-width + xl, 0 + yw, 0 + zh)
|
||||||
|
])
|
||||||
|
extr = np.sqrt(3) * pc.L
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception(f"Direction { pc.direction } is not implemented")
|
||||||
|
|
||||||
|
# Cell
|
||||||
|
circuit = ng_occ.Wire([
|
||||||
|
ng_occ.Segment(ng_occ.Pnt(*v1), ng_occ.Pnt(*v2))
|
||||||
|
for v1, v2 in zip(vertices, np.roll(vertices, -1, axis = 0))
|
||||||
|
])
|
||||||
|
inletface = ng_occ.Face(circuit)
|
||||||
|
inletface.name = "inlet"
|
||||||
|
|
||||||
|
vecFlow = utils.normal(inletface)
|
||||||
|
pc.cell = inletface.Extrude(extr, ng_occ.Vec(*vecFlow))
|
||||||
|
pc.length = extr
|
||||||
|
|
||||||
|
# Boundary faces
|
||||||
|
symetryId = 0
|
||||||
|
|
||||||
|
for face in pc.cell.faces:
|
||||||
|
fNorm = utils.normal(face)
|
||||||
|
fAngle = utils.angle(vecFlow, fNorm)
|
||||||
|
|
||||||
|
if fAngle == 0 or fAngle == np.pi:
|
||||||
|
if np.all(utils.pos(face.center) == utils.pos(inletface.center)):
|
||||||
|
face.name = "inlet"
|
||||||
|
|
||||||
|
else:
|
||||||
|
face.name = "outlet"
|
||||||
|
|
||||||
|
else:
|
||||||
|
face.name = f"symetry{ symetryId }"
|
||||||
|
symetryId += 1
|
||||||
|
|
||||||
|
# Main shape
|
||||||
|
pc.shape = pc.cell - pc.lattice
|
||||||
|
|
||||||
|
assert len(pc.shape.solids) == 1, "Expected single solid shape"
|
||||||
|
|
||||||
|
pc.shape = pc.shape.solids[0]
|
||||||
|
|
||||||
|
# Boundary faces (walls)
|
||||||
|
for face in pc.shape.faces:
|
||||||
|
if face.name not in ["inlet", "outlet", *[ f"symetry{ n }" for n in range(6) ]]:
|
||||||
|
face.name = "wall"
|
||||||
|
|
||||||
|
return pc
|
121
anisotropy/shaping/shape.py
Normal file
121
anisotropy/shaping/shape.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
from numpy import ndarray
|
||||||
|
from os import PathLike
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import netgen.occ as ng_occ
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
|
class Shape(object):
|
||||||
|
def __init__(self):
|
||||||
|
"""A Shape object contains OCC shape.
|
||||||
|
"""
|
||||||
|
self.shape = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def geometry(self) -> ng_occ.OCCGeometry:
|
||||||
|
"""Shape as OCCGeometry object.
|
||||||
|
"""
|
||||||
|
return ng_occ.OCCGeometry(self.shape)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self) -> ng_occ.TopAbs_ShapeEnum:
|
||||||
|
"""Type of the shape. (shortcut)
|
||||||
|
"""
|
||||||
|
return self.shape.type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def volume(self) -> float:
|
||||||
|
"""Volume of the shape. (shortcut)
|
||||||
|
"""
|
||||||
|
return self.shape.volume
|
||||||
|
|
||||||
|
@property
|
||||||
|
def center(self) -> ndarray:
|
||||||
|
"""Center of the shape.
|
||||||
|
"""
|
||||||
|
return np.array(utils.pos(self.shape.center))
|
||||||
|
|
||||||
|
def write(self, filename: PathLike):
|
||||||
|
"""Export a shape to the file.
|
||||||
|
Supported formats: step.
|
||||||
|
|
||||||
|
:param filename:
|
||||||
|
Path of the file.
|
||||||
|
"""
|
||||||
|
path = pathlib.Path(filename).resolve()
|
||||||
|
ext = path.suffix[1: ]
|
||||||
|
|
||||||
|
if ext == "step":
|
||||||
|
self.shape.WriteStep(str(path))
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(f"Shape format '{ ext }' is not supported")
|
||||||
|
|
||||||
|
def read(self, filename: PathLike):
|
||||||
|
"""Import a shape from the file.
|
||||||
|
Supported formats: step, iges, brep.
|
||||||
|
|
||||||
|
:param filename:
|
||||||
|
Path of the file.
|
||||||
|
"""
|
||||||
|
path = pathlib.Path(filename).resolve()
|
||||||
|
ext = path.suffix[1: ]
|
||||||
|
|
||||||
|
if ext in ["step", "iges", "brep"]:
|
||||||
|
self.shape = ng_occ.OCCGeometry(str(path)).shape
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(f"Shape format '{ext}' is not supported")
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def patches(
|
||||||
|
self,
|
||||||
|
group: bool = False,
|
||||||
|
shiftIndex: bool = False,
|
||||||
|
prefix: str = None
|
||||||
|
) -> list | dict:
|
||||||
|
"""Get patches indices with their names.
|
||||||
|
|
||||||
|
:param group:
|
||||||
|
Group indices together with the same patches names.
|
||||||
|
:param shiftIndex:
|
||||||
|
Start numerating with one instead of zero.
|
||||||
|
:param prefix:
|
||||||
|
Add string prefix to the index.
|
||||||
|
:return:
|
||||||
|
List if group = False else dictionary.
|
||||||
|
"""
|
||||||
|
if group:
|
||||||
|
patches_ = {}
|
||||||
|
|
||||||
|
for idn, face in enumerate(self.shape.faces):
|
||||||
|
if shiftIndex:
|
||||||
|
idn += 1
|
||||||
|
|
||||||
|
item = idn if not prefix else prefix + str(idn)
|
||||||
|
|
||||||
|
if patches_.get(face.name):
|
||||||
|
patches_[face.name].append(item)
|
||||||
|
|
||||||
|
else:
|
||||||
|
patches_[face.name] = [ item ]
|
||||||
|
|
||||||
|
else:
|
||||||
|
patches_ = []
|
||||||
|
|
||||||
|
for idn, face in enumerate(self.shape.faces):
|
||||||
|
if shiftIndex:
|
||||||
|
idn += 1
|
||||||
|
|
||||||
|
item = idn if not prefix else prefix + str(idn)
|
||||||
|
|
||||||
|
patches_.append((item, face.name))
|
||||||
|
|
||||||
|
return patches_
|
47
anisotropy/shaping/utils.py
Normal file
47
anisotropy/shaping/utils.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from numpy import ndarray
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import netgen.occ as ng_occ
|
||||||
|
|
||||||
|
|
||||||
|
def pos(point: ng_occ.gp_Pnt) -> ndarray:
|
||||||
|
"""Extract coordinates from point.
|
||||||
|
|
||||||
|
:param point:
|
||||||
|
OCC point object
|
||||||
|
:return:
|
||||||
|
Array of coordinates.
|
||||||
|
"""
|
||||||
|
return np.array([ point.x, point.y, point.z ])
|
||||||
|
|
||||||
|
|
||||||
|
def normal(face: ng_occ.Face) -> ndarray:
|
||||||
|
"""Calculate normal vector from face.
|
||||||
|
|
||||||
|
:param face:
|
||||||
|
OCC face object.
|
||||||
|
:return:
|
||||||
|
Normal vector from face.
|
||||||
|
"""
|
||||||
|
_, u, v = face.surf.D1(0, 0)
|
||||||
|
|
||||||
|
return np.cross([u.x, u.y, u.z], [v.x, v.y, v.z])
|
||||||
|
|
||||||
|
|
||||||
|
def angle(vec1: ndarray, vec2: ndarray) -> float:
|
||||||
|
"""Angle between two vectors.
|
||||||
|
|
||||||
|
:param vec1:
|
||||||
|
Array of points that represents first vector.
|
||||||
|
:param vec2:
|
||||||
|
Array of points that represents second vector.
|
||||||
|
:return:
|
||||||
|
Angle between two vectors in radians.
|
||||||
|
"""
|
||||||
|
inner = np.inner(vec1, vec2)
|
||||||
|
norms = np.linalg.norm(vec1) * np.linalg.norm(vec2)
|
||||||
|
cos = inner / norms
|
||||||
|
|
||||||
|
return np.arccos(np.clip(cos, -1.0, 1.0))
|
8
anisotropy/solving/__init__.py
Normal file
8
anisotropy/solving/__init__.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from .onephase import FlowOnePhase
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"FlowOnePhase"
|
||||||
|
]
|
268
anisotropy/solving/onephase.py
Normal file
268
anisotropy/solving/onephase.py
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import anisotropy.openfoam as of
|
||||||
|
from anisotropy.openfoam import presets
|
||||||
|
from anisotropy.openfoam import commands
|
||||||
|
|
||||||
|
|
||||||
|
class FlowOnePhase:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
path: str = None,
|
||||||
|
direction: list[float] = None,
|
||||||
|
patches: dict = None,
|
||||||
|
pressureInlet: float = None,
|
||||||
|
pressureOutlet: float = None,
|
||||||
|
pressureInternal: float = None,
|
||||||
|
velocityInlet: float = None,
|
||||||
|
velocityOutlet: float = None,
|
||||||
|
velocityInternal: float = None,
|
||||||
|
density: float = None,
|
||||||
|
viscosityKinematic: float = None,
|
||||||
|
scale: list[float] = None,
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
|
|
||||||
|
self.direction = direction
|
||||||
|
self.patches = patches
|
||||||
|
self.path = path
|
||||||
|
self.case = None
|
||||||
|
self.boundaryNames = ["inlet", "outlet", "symetry", "wall"]
|
||||||
|
|
||||||
|
self.pressureInlet = pressureInlet
|
||||||
|
self.pressureOutlet = pressureOutlet
|
||||||
|
self.pressureInternal = pressureInternal
|
||||||
|
self.velocityInlet = velocityInlet
|
||||||
|
self.velocityOutlet = velocityOutlet
|
||||||
|
self.velocityInternal = velocityInternal
|
||||||
|
self.density = density
|
||||||
|
self.viscosityKinematic = viscosityKinematic
|
||||||
|
self.scale = scale
|
||||||
|
|
||||||
|
def controlDict(self) -> of.FoamFile:
|
||||||
|
ff = presets.controlDict()
|
||||||
|
ff.update(
|
||||||
|
startFrom = "latestTime",
|
||||||
|
endTime = 5000,
|
||||||
|
writeInterval = 100,
|
||||||
|
runTimeModifiable = "true"
|
||||||
|
)
|
||||||
|
|
||||||
|
return ff
|
||||||
|
|
||||||
|
def fvSchemes(self) -> of.FoamFile:
|
||||||
|
ff = presets.fvSchemes()
|
||||||
|
|
||||||
|
return ff
|
||||||
|
|
||||||
|
def fvSolution(self) -> of.FoamFile:
|
||||||
|
ff = presets.fvSolution()
|
||||||
|
ff["solvers"]["U"].update(
|
||||||
|
nSweeps = 2,
|
||||||
|
tolerance = 1e-08,
|
||||||
|
smoother = "GaussSeidel"
|
||||||
|
)
|
||||||
|
ff["solvers"]["Phi"] = dict(
|
||||||
|
solver = "GAMG",
|
||||||
|
smoother = "DIC",
|
||||||
|
cacheAgglomeration = "yes",
|
||||||
|
agglomerator = "faceAreaPair",
|
||||||
|
nCellsInCoarsestLevel = 10,
|
||||||
|
mergeLevels = 1,
|
||||||
|
tolerance = 1e-06,
|
||||||
|
relTol = 0.01
|
||||||
|
)
|
||||||
|
ff["potentialFlow"] = dict(
|
||||||
|
nNonOrthogonalCorrectors = 13,
|
||||||
|
PhiRefCell = 0,
|
||||||
|
PhiRefPoint = 0,
|
||||||
|
PhiRefValue = 0,
|
||||||
|
Phi = 0
|
||||||
|
)
|
||||||
|
ff["cache"] = { "grad(U)": None }
|
||||||
|
ff["SIMPLE"].update(
|
||||||
|
nNonOrthogonalCorrectors = 6,
|
||||||
|
residualControl = dict(
|
||||||
|
p = 1e-05,
|
||||||
|
U = 1e-05
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ff["relaxationFactors"]["equations"]["U"] = 0.5
|
||||||
|
|
||||||
|
return ff
|
||||||
|
|
||||||
|
def transportProperties(self) -> of.FoamFile:
|
||||||
|
ff = presets.transportProperties()
|
||||||
|
ff.update(
|
||||||
|
nu = self.viscosityKinematic
|
||||||
|
)
|
||||||
|
|
||||||
|
return ff
|
||||||
|
|
||||||
|
def turbulenceProperties(self) -> of.FoamFile:
|
||||||
|
ff = presets.turbulenceProperties()
|
||||||
|
ff.content = dict(
|
||||||
|
simulationType = "laminar"
|
||||||
|
)
|
||||||
|
|
||||||
|
return ff
|
||||||
|
|
||||||
|
def p(self) -> of.FoamFile:
|
||||||
|
ff = presets.p()
|
||||||
|
ff["boundaryField"] = {}
|
||||||
|
|
||||||
|
for boundary in self.boundaryNames:
|
||||||
|
if boundary == "inlet":
|
||||||
|
ff["boundaryField"][boundary] = dict(
|
||||||
|
type = "fixedValue",
|
||||||
|
value = of.utils.uniform(self.pressureInlet / self.density)
|
||||||
|
)
|
||||||
|
|
||||||
|
elif boundary == "outlet":
|
||||||
|
ff["boundaryField"][boundary] = dict(
|
||||||
|
type = "fixedValue",
|
||||||
|
value = of.utils.uniform(self.pressureOutlet / self.density)
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
ff["boundaryField"][boundary] = dict(
|
||||||
|
type = "zeroGradient"
|
||||||
|
)
|
||||||
|
|
||||||
|
return ff
|
||||||
|
|
||||||
|
def U_approx(self) -> of.FoamFile:
|
||||||
|
ff = presets.U()
|
||||||
|
ff["boundaryField"] = {}
|
||||||
|
|
||||||
|
for boundary in self.boundaryNames:
|
||||||
|
if boundary == "inlet":
|
||||||
|
ff["boundaryField"][boundary] = dict(
|
||||||
|
type = "fixedValue",
|
||||||
|
value = of.utils.uniform(np.array(self.direction) * -6e-5)
|
||||||
|
)
|
||||||
|
|
||||||
|
elif boundary == "outlet":
|
||||||
|
ff["boundaryField"][boundary] = dict(
|
||||||
|
type = "zeroGradient",
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
ff["boundaryField"][boundary] = dict(
|
||||||
|
type = "fixedValue",
|
||||||
|
value = of.utils.uniform([0., 0., 0.])
|
||||||
|
)
|
||||||
|
|
||||||
|
return ff
|
||||||
|
|
||||||
|
def U(self) -> of.FoamFile:
|
||||||
|
ff = presets.U()
|
||||||
|
ff["boundaryField"] = {}
|
||||||
|
|
||||||
|
for boundary in self.boundaryNames:
|
||||||
|
if boundary == "inlet":
|
||||||
|
ff["boundaryField"][boundary] = dict(
|
||||||
|
type = "pressureInletVelocity",
|
||||||
|
value = of.utils.uniform(self.velocityInlet)
|
||||||
|
)
|
||||||
|
|
||||||
|
elif boundary == "outlet":
|
||||||
|
ff["boundaryField"][boundary] = dict(
|
||||||
|
type = "zeroGradient",
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
ff["boundaryField"][boundary] = dict(
|
||||||
|
type = "fixedValue",
|
||||||
|
value = of.utils.uniform([0., 0., 0.])
|
||||||
|
)
|
||||||
|
|
||||||
|
return ff
|
||||||
|
|
||||||
|
def createPatchDict(self) -> of.FoamFile:
|
||||||
|
# initial 43 unnamed patches ->
|
||||||
|
# 6 named patches (inlet, outlet, wall, symetry 0 to 3 or 5) ->
|
||||||
|
# 4 inGroups (inlet, outlet, wall, symetry)
|
||||||
|
|
||||||
|
ff = presets.createPatchDict()
|
||||||
|
ff["patches"] = []
|
||||||
|
|
||||||
|
for name in self.patches.keys():
|
||||||
|
if name == "inlet":
|
||||||
|
patchGroup = "inlet"
|
||||||
|
patchType = "patch"
|
||||||
|
|
||||||
|
elif name == "outlet":
|
||||||
|
patchGroup = "outlet"
|
||||||
|
patchType = "patch"
|
||||||
|
|
||||||
|
elif name == "wall":
|
||||||
|
patchGroup = "wall"
|
||||||
|
patchType = "wall"
|
||||||
|
|
||||||
|
else:
|
||||||
|
patchGroup = "symetry"
|
||||||
|
patchType = "symetryPlane"
|
||||||
|
|
||||||
|
ff["patches"].append({
|
||||||
|
"name": name,
|
||||||
|
"patchInfo": {
|
||||||
|
"type": patchType,
|
||||||
|
"inGroups": [patchGroup]
|
||||||
|
},
|
||||||
|
"constructFrom": "patches",
|
||||||
|
"patches": self.patches[name]
|
||||||
|
})
|
||||||
|
|
||||||
|
return ff
|
||||||
|
|
||||||
|
def generate(self, approximation: bool = False, meshfile: str = "mesh.mesh"):
|
||||||
|
|
||||||
|
self.case = of.FoamCase(
|
||||||
|
path = self.path,
|
||||||
|
files = [
|
||||||
|
self.controlDict(),
|
||||||
|
self.fvSchemes(),
|
||||||
|
self.fvSolution(),
|
||||||
|
self.transportProperties(),
|
||||||
|
self.turbulenceProperties(),
|
||||||
|
self.p()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.patches is not None:
|
||||||
|
self.case += self.createPatchDict()
|
||||||
|
|
||||||
|
self.case += self.U_approx() if approximation else self.U()
|
||||||
|
|
||||||
|
self.case.write(self.path)
|
||||||
|
|
||||||
|
self.case.chdir()
|
||||||
|
|
||||||
|
commands.netgenNeutralToFoam(meshfile)
|
||||||
|
|
||||||
|
# TODO: contain
|
||||||
|
if self.case.contains("createPatchDict"):
|
||||||
|
commands.createPatch()
|
||||||
|
|
||||||
|
commands.checkMesh()
|
||||||
|
|
||||||
|
if self.scale is not None:
|
||||||
|
commands.transformPoints({
|
||||||
|
"scale": self.scale
|
||||||
|
})
|
||||||
|
|
||||||
|
commands.renumberMesh()
|
||||||
|
|
||||||
|
if approximation:
|
||||||
|
commands.potentialFoam()
|
||||||
|
|
||||||
|
# replace velocity for the main simulation
|
||||||
|
self.case += self.U()
|
||||||
|
self.case.write(self.path)
|
||||||
|
|
||||||
|
commands.simpleFoam()
|
||||||
|
|
||||||
|
self.case.chback()
|
3
data/.gitignore
vendored
Normal file
3
data/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
*.png
|
||||||
|
*.tiff
|
||||||
|
*.sql
|
916
data/analyze.ipynb
Normal file
916
data/analyze.ipynb
Normal file
File diff suppressed because one or more lines are too long
BIN
data/anisotropy.db
Normal file
BIN
data/anisotropy.db
Normal file
Binary file not shown.
57
data/anisotropy.toml
Normal file
57
data/anisotropy.toml
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
[[structures]]
|
||||||
|
label = "simple"
|
||||||
|
alpha = [ 0.01, 0.27,]
|
||||||
|
alphaStep = 0.005
|
||||||
|
directions = [ [ 1.0, 0.0, 0.0,], [ 0.0, 0.0, 1.0,], [ 1.0, 1.0, 1.0,],]
|
||||||
|
r0 = 1
|
||||||
|
filletsEnabled = true
|
||||||
|
pressureInlet = 1
|
||||||
|
pressureOutlet = 0
|
||||||
|
pressureInternal = 0
|
||||||
|
velocityInlet = [ 0.0, 0.0, 0.0,]
|
||||||
|
velocityInternal = [ 0.0, 0.0, 0.0,]
|
||||||
|
density = 1000
|
||||||
|
viscosity = 0.001
|
||||||
|
scale = [ 1e-5, 1e-5, 1e-5,]
|
||||||
|
|
||||||
|
[[structures]]
|
||||||
|
label = "bodyCentered"
|
||||||
|
alpha = [ 0.01, 0.16,]
|
||||||
|
alphaStep = 0.005
|
||||||
|
directions = [ [ 1.0, 0.0, 0.0,], [ 0.0, 0.0, 1.0,], [ 1.0, 1.0, 1.0,],]
|
||||||
|
r0 = 1
|
||||||
|
filletsEnabled = true
|
||||||
|
pressureInlet = 1
|
||||||
|
pressureOutlet = 0
|
||||||
|
pressureInternal = 0
|
||||||
|
velocityInlet = [ 0.0, 0.0, 0.0,]
|
||||||
|
velocityInternal = [ 0.0, 0.0, 0.0,]
|
||||||
|
density = 1000
|
||||||
|
viscosity = 0.001
|
||||||
|
scale = [ 1e-5, 1e-5, 1e-5,]
|
||||||
|
|
||||||
|
[[structures]]
|
||||||
|
label = "faceCentered"
|
||||||
|
alpha = [ 0.01, 0.12,]
|
||||||
|
alphaStep = 0.005
|
||||||
|
directions = [ [ 1.0, 0.0, 0.0,], [ 0.0, 0.0, 1.0,], [ 1.0, 1.0, 1.0,],]
|
||||||
|
r0 = 1
|
||||||
|
filletsEnabled = true
|
||||||
|
pressureInlet = 1
|
||||||
|
pressureOutlet = 0
|
||||||
|
pressureInternal = 0
|
||||||
|
velocityInlet = [ 0.0, 0.0, 0.0,]
|
||||||
|
velocityInternal = [ 0.0, 0.0, 0.0,]
|
||||||
|
density = 1000
|
||||||
|
viscosity = 0.001
|
||||||
|
scale = [ 1e-5, 1e-5, 1e-5,]
|
||||||
|
|
||||||
|
[options]
|
||||||
|
nprocs = 4
|
||||||
|
stage = "shape"
|
||||||
|
overwrite = false
|
||||||
|
database = "anisotropy.db"
|
||||||
|
build = "build"
|
||||||
|
logs = "logs"
|
||||||
|
shapefile = "shape.step"
|
||||||
|
meshfile = "mesh.mesh"
|
@ -20,6 +20,7 @@ help:
|
|||||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||||
%: Makefile
|
%: Makefile
|
||||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
|
[ -d "$(BUILDDIR)/html" ] && cp -vf "$(BUILDDIR)/html" anisotropy/
|
||||||
|
|
||||||
update:
|
update:
|
||||||
@$(SPHINXAPIDOC) -f -o "$(SOURCEDIR)" "$(PROJECTDIR)"
|
@$(SPHINXAPIDOC) -f -o "$(SOURCEDIR)" "$(PROJECTDIR)"
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user