Mod: improved openfoam interface

Mod: improved runners exception handlers
Delete: some useless trash
New: post process pipeline block
This commit is contained in:
L-Nafaryus 2021-12-21 20:36:01 +05:00
parent 8e769ec1ce
commit 49368cc681
No known key found for this signature in database
GPG Key ID: C76D8DCD2727DBB7
28 changed files with 367 additions and 1319 deletions

View File

@ -124,7 +124,7 @@ def init(path, verbose):
)
@click.option(
"-s", "--stage", "stage",
type = click.Choice(["all", "shape", "mesh", "flow", "postProcessing"]),
type = click.Choice(["all", "shape", "mesh", "flow", "postProcess"]),
default = "all",
help = "Current computation stage"
)
@ -146,7 +146,10 @@ def init(path, verbose):
count = True,
help = "Increase verbose level"
)
def compute(path, configFile, nprocs, stage, overwrite, params, verbose):
@click.option(
"--exec-id", "execution"
)
def compute(path, configFile, nprocs, stage, overwrite, params, verbose, execution):
from anisotropy.core.runner import UltimateRunner
from anisotropy.core.config import DefaultConfig
from anisotropy.core.utils import setupLogger
@ -170,7 +173,7 @@ def compute(path, configFile, nprocs, stage, overwrite, params, verbose):
overwrite = overwrite
)
runner = UltimateRunner(config = config)
runner = UltimateRunner(config = config, exec_id = execution)
runner.fill()
runner.start()

View File

@ -1,380 +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(),
}
paramsAll.append(entryNew)
return paramsAll
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
sGeometry, sMesh = dict(
simple = (Simple, SimpleMesh),
bodyCentered = (BodyCentered, BodyCenteredMesh),
faceCentered = (FaceCentered, FaceCenteredMesh)
)[p["structure"]["type"]]
# Shape
logger.info("Constructing shape ...")
geometry = sGeometry(**p["structure"])
geometry.build()
# Mesh
logger.info("Prepairing mesh ...")
mesh = sMesh(geometry)
mesh.build()
logger.info("Computing mesh ...")
out, err, returncode = mesh.compute()
if not returncode:
mesh.removePyramids()
mesh.createGroups()
casePath = self.getCasePath(path)
os.makedirs(casePath, exist_ok = True)
logger.info("Exporting mesh ...")
out, err, returncode = mesh.export(os.path.join(casePath, "mesh.unv"))
if returncode:
logger.error(err)
# NOTE: edit from here
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")
out, err, returncode = openfoam.checkMesh()
if out: logger.warning(out)
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"]

View File

@ -2,4 +2,24 @@
# This file is part of anisotropy.
# License: GNU GPL version 3, see the file "LICENSE" for details.
from os import path
import logging
logger = logging.getLogger(__name__)
from anisotropy.openfoam.runnerPresets import postProcess
from anisotropy.openfoam import datReader
class PostProcess(object):
def __init__(self, dirpath):
self.path = path.abspath(dirpath)
def flowRate(self, patch: str):
func = "patchFlowRate(patch={})".format(patch)
filepath = path.join(self.path, "postProcessing", func, "0", "surfaceFieldValue.dat")
postProcess(func, cwd = self.path)
surfaceFieldValue = datReader(filepath)
return surfaceFieldValue["sum(phi)"][-1]

View File

@ -9,44 +9,36 @@ from os import path
from anisotropy.core.config import DefaultConfig
import logging
from anisotropy.core.postProcess import PostProcess
from anisotropy.core.utils import ParallelRunner, Timer
logger = logging.getLogger(__name__)
from anisotropy.database import Database, tables as T
from anisotropy.shaping import Simple, BodyCentered, FaceCentered
from anisotropy.shaping import Simple, BodyCentered, FaceCentered, Shape
from anisotropy.meshing import Mesh
from anisotropy.openfoam.presets import CreatePatchDict
from anisotropy.solving import OnePhaseFlow
from multiprocessing import current_process, parent_process
class UltimateRunner(object):
def __init__(self, config = None, exec_id: int = None): #t_exec = None, t_shape = None):
def __init__(self, config = None, exec_id: int = None, typo: str = "master"):
# Configuration file
self.config = config or DefaultConfig()
# Process recognition
typo = True if not exec_id else False
#if current_process().name == "MainProcess" and parent_process() == None:
# current_process().name = "master"
self.typo = typo
# Database preparation
if typo: #current_process().name == "master":
self.database = Database(path = self.config["database"])
if typo: #current_process().name == "master":
with self.database:
self.t_exec = T.Execution(date = datetime.now())
self.t_exec.save()
#self.t_shape = None
else:
#self.t_exec = self.database.getExecution(exec_id)
if exec_id:
if self.database.getExecution(exec_id):
self.exec_id = exec_id
#self.t_exec = t_exec
#self.t_shape = t_shape
if not self.exec_id:
with self.database:
self.exec_id = T.Execution.create(date=datetime.now())
# Parameters
self.shape = None
@ -59,12 +51,20 @@ class UltimateRunner(object):
def createRow(self):
# create a row in each table for the current case
with self.database:
t_shape = T.Shape(exec_id = self.exec_id, **self.config.params)
t_shape.save()
t_mesh = T.Mesh(shape_id = t_shape.shape_id)
t_mesh.save()
t_flow = T.FlowOnephase(mesh_id = t_mesh.mesh_id)
t_flow.save()
shape = self.database.getShape(execution = self.exec_id, **self.config.params)
if not shape:
shape = T.Shape.create(exec_id = self.exec_id, **self.config.params)
mesh = self.database.getMesh(execution = self.exec_id, **self.config.params)
if not mesh:
mesh = T.Mesh.create(shape_id = shape)
flow = self.database.getFlowOnephase(execution = self.exec_id, **self.config.params)
if not flow:
flow = T.FlowOnephase.create(mesh_id = mesh)
def fill(self):
self.config.expand()
@ -75,19 +75,11 @@ class UltimateRunner(object):
config.chooseParams(idn)
config.minimize()
#with self.database:
# t_shape = T.Shape(
# exec_id = self.t_exec,
# **case
# )
# t_shape.save()
self.queue.append(UltimateRunner(
config = config,
exec_id = self.t_exec.exec_id
#t_exec = self.t_exec,
#t_shape = t_shape
))
kwargs = {
"config": config,
"exec_id": self.exec_id
}
self.queue.append(kwargs)
def start(self, queue: list = None, nprocs: int = None):
@ -97,44 +89,22 @@ class UltimateRunner(object):
parallel = ParallelRunner(nprocs = nprocs)
parallel.start()
for runner in self.queue:
parallel.append(runner.pipeline, args = [self.config["stage"]])
for kwargs in self.queue:
parallel.append(self.subrunner, kwargs = kwargs)
parallel.wait()
# TODO: if runner done - remove from queue; results from parallel function
def casepath(self):
#with self.database:
# params = T.Shape.get(
# T.Shape.exec_id == self.t_exec,
# T.Shape.shape_id == self.t_shape.shape_id
# )
params = self.config.params
shapeParams = self.database.getShape(
params["label"],
params["direction"],
params["alpha"],
self.exec_id
)
execution = "execution-{}".format(self.exec_id)
case = "{}-[{},{},{}]-{}".format(params["label"], *[ str(d) for d in params["direction"] ], params["alpha"])
#alpha = "alpha-{}".format(shapeParams.alpha)
#dirpath = path.join(self.config["build"], shapeParams.label, direction, alpha)
dirpath = path.join(self.config["build"], execution, case)
return path.abspath(dirpath)
def computeShape(self):
#if current_process().name == "master":
# return
#with self.database:
# params = T.Shape.get(
# T.Shape.exec_id == self.t_exec,
# T.Shape.shape_id == self.t_shape.shape_id
# )
out, err, returncode = "", "", 0
params = self.config.params
shapeParams = self.database.getShape(
params["label"],
@ -161,14 +131,19 @@ class UltimateRunner(object):
r0 = shapeParams.r0,
filletsEnabled = shapeParams.filletsEnabled
)
#out, err, returncode = self.shape.build()
# TODO: wrap build function for exceptions
try:
self.shape.build()
except Exception as e:
err = e
returncode = 1
if not returncode:
os.makedirs(self.casepath(), exist_ok = True)
out, err, returncode = self.shape.export(path.join(self.casepath(), filename))
if returncode == 0:
if not returncode:
shapeParams.shapeStatus = "done"
else:
@ -180,18 +155,7 @@ class UltimateRunner(object):
shapeParams.save()
def computeMesh(self):
#if not self.type == "worker":
# return
#with self.database:
# t_params = T.Shape.get(
# T.Shape.exec_id == self.t_exec,
# T.Shape.shape_id == self.t_shape.shape_id
# )
# params = T.Mesh.get(
# T.Mesh.shape_id == self.t_shape.shape_id
# )
out, err, returncode = "", "", 0
params = self.config.params
meshParams = self.database.getMesh(
params["label"],
@ -206,16 +170,33 @@ class UltimateRunner(object):
filename = "mesh.mesh"
timer = Timer()
# TODO: load from object or file
if not self.shape:
filename = "shape.step"
filepath = path.join(self.casepath(), filename)
if not path.exists(filepath) and not path.isfile(filepath):
err = f"File not found: { filepath }"
returncode = 2
if not returncode:
self.shape = Shape()
self.shape.load(filepath)
if not returncode:
self.mesh = Mesh(self.shape.shape)
#out, err, returncode = self.mesh.build()
# TODO: wrap build function for exceptions
try:
self.mesh.build()
except Exception as e:
err = e
returncode = 1
if not returncode:
os.makedirs(self.casepath(), exist_ok = True)
out, err, returncode = self.mesh.export(path.join(self.casepath(), filename))
if returncode == 0:
if not returncode:
meshParams.meshStatus = "done"
else:
@ -227,21 +208,6 @@ class UltimateRunner(object):
meshParams.save()
def computeFlow(self):
# if not self.type == "worker":
# return
#with self.database:
# t_params = T.Shape.get(
# T.Shape.exec_id == self.t_exec,
# T.Shape.shape_id == self.t_shape.shape_id
# )
# m_params = T.Mesh.get(
# T.Mesh.shape_id == self.t_shape.shape_id
# )
# params = T.FlowOnephase.get(
# T.FlowOnephase.mesh_id == self.t_mesh.mesh_id
# )
params = self.config.params
flowParams = self.database.getFlowOnephase(
params["label"],
@ -255,55 +221,26 @@ class UltimateRunner(object):
))
timer = Timer()
self.flow = OnePhaseFlow(path = self.casepath())
self.flow = OnePhaseFlow(params["direction"], path = self.casepath())
# initial 43 unnamed patches ->
# 6 named patches (inlet, outlet, wall, symetry0 - 3/5) ->
# 4 inGroups (inlet, outlet, wall, symetry)
createPatchDict = CreatePatchDict()
createPatchDict["patches"] = []
patches = {}
if not self.shape:
filename = "shape.step"
filepath = path.join(self.casepath(), filename)
for n, patch in enumerate(self.shape.shape.faces):
# shifted index
n += 1
name = patch.name
if not path.exists(filepath) and not path.isfile(filepath):
err = f"File not found: { filepath }"
returncode = 2
if patches.get(name):
patches[name].append(f"patch{ n }")
if not returncode:
self.shape = Shape()
self.shape.load(filepath)
else:
patches[name] = [ f"patch{ n }" ]
for name in 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"
createPatchDict["patches"].append({
"name": name,
"patchInfo": {
"type": patchType,
"inGroups": [patchGroup]
},
"constructFrom": "patches",
"patches": patches[name]
})
faces = [ (n, face.name) for n, face in enumerate(self.shape.shape.faces) ]
createPatchDict = OnePhaseFlow.facesToPatches(faces)
self.flow.append(createPatchDict)
self.flow.write()
# Build a flow
try:
out, err, returncode = self.flow.build()
@ -323,16 +260,30 @@ class UltimateRunner(object):
flowParams.flowExecutionTime = timer.elapsed()
flowParams.save()
def pipeline(self, stage: str = None):
self.database = Database(path = self.config["database"])
self.createRow()
def computePostProcess(self):
params = self.config.params
flowParams = self.database.getFlowOnephase(
params["label"],
params["direction"],
params["alpha"],
self.exec_id
)
logger.info("Computing post process for {} with direction = {} and alpha = {}".format(
params["label"], params["direction"], params["alpha"]
))
postProcess = PostProcess(self.casepath())
if flowParams.flowStatus == "done":
flowParams.flowRate = postProcess.flowRate("outlet")
with self.database:
flowParams.save()
def pipeline(self, stage: str = None):
stage = stage or self.config["stage"]
# TODO: fix flow
# TODO: change case path to execDATE/label-direction-theta/*
# TODO: fix nprocs
#try:
if stage in ["shape", "all"]:
self.computeShape()
@ -342,10 +293,16 @@ class UltimateRunner(object):
if stage in ["flow", "all"]:
self.computeFlow()
#elif stage in ["postProcess", "all"]:
# self.postProcess()
#except Exception as e:
# logger.error(e)
if stage in ["postProcess", "all"]:
self.computePostProcess()
#logger.info("Pipeline done")
@staticmethod
def subrunner(*args, **kwargs):
runner = UltimateRunner(config = kwargs["config"], exec_id = kwargs["exec_id"], typo = "worker")
runner.createRow()
runner.pipeline()

View File

@ -74,11 +74,10 @@ def setupLogger(level: int, filepath: str = None):
os.makedirs(filepath, exist_ok = True)
filehandler = logging.FileHandler(
os.path.join(filepath, "{}.log".format(logger.name))
os.path.join(filepath, "{}.log".format("anisotropy"))
)
filehandler.setLevel(level)
filehandler.setLevel(logging.INFO)
filehandler.setFormatter(CustomFormatter())
#logger.addHandler(filehandler)
logging.root.addHandler(filehandler)
@ -182,21 +181,6 @@ def expand(source, sep = "_"):
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

View File

@ -44,9 +44,9 @@ class Database(SqliteDatabase):
def getExecution(self, idn):
query = models.Execution.select().where(models.Execution.exec_id == idn)
self.connect()
with self:
table = query.get() if query.exists() else None
self.close()
return table
@ -58,7 +58,7 @@ class Database(SqliteDatabase):
return table
def getShape(self, label, direction, alpha, execution = None):
def getShape(self, label = None, direction = None, alpha = None, execution = None, **kwargs):
execution = execution or self.getLatest()
query = (
models.Shape
@ -77,7 +77,7 @@ class Database(SqliteDatabase):
return table
def getMesh(self, label, direction, alpha, execution = None):
def getMesh(self, label = None, direction = None, alpha = None, execution = None, **kwargs):
execution = execution or self.getLatest()
query = (
models.Mesh
@ -97,7 +97,7 @@ class Database(SqliteDatabase):
return table
def getFlowOnephase(self, label, direction, alpha, execution = None):
def getFlowOnephase(self, label = None, direction = None, alpha = None, execution = None, **kwargs):
execution = execution or self.getLatest()
query = (
models.FlowOnephase

View File

@ -60,7 +60,6 @@ class Mesh(object):
ext = os.path.splitext(filename)[1][1: ]
try:
# TODO: write correct boundary names
if ext == "vol":
self.mesh.Save(filename)

View File

@ -2,13 +2,7 @@
# This file is part of anisotropy.
# License: GNU GPL version 3, see the file "LICENSE" for details.
#from .meshConversion import ideasUnvToFoam, netgenNeutralToFoam
#from .meshManipulation import createPatch, transformPoints, checkMesh, renumberMesh
#from .miscellaneous import foamDictionary
#from .parallelProcessing import decomposePar
#from .solvers import potentialFoam, simpleFoam
from .utils import version, uniform #, foamClean
from .utils import version, uniform, datReader
from .foamfile import FoamFile
from .foamcase import FoamCase
from .runner import FoamRunner

View File

@ -1,13 +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)
def netgenNeutralToFoam(mesh: str, case: str = None) -> (str, int):
return application("netgenNeutralToFoam", mesh, case = case, stderr = True)

View File

@ -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(r"\*\*\*", 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)

View File

@ -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)

View File

@ -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)

View File

@ -48,25 +48,30 @@ class FoamRunner(object):
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)
self.output, self.error = proc.communicate()
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 self.logpath:
with open(self.logpath, "w") as io:
if self.output:
io.write(self.output)
if self.error:
io.write(self.error)
io.write(f"Exit code { self.returncode }")
if not self.returncode == 0 and self.exit:
raise Exception(f"Subprocess failed: { self.error }")

View File

@ -9,29 +9,39 @@ from .runner import FoamRunner
# meshConversion
##
def netgenNeutralToFoam(meshfile: str, **kwargs) -> tuple[str, str, int]:
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 ]
return FoamRunner(command, args = args, **kwargs).run()
runner = FoamRunner(command, args = args, **kwargs)
if run:
runner.run()
return runner
def ideasUnvToFoam(meshfile: str, **kwargs) -> tuple[str, str, int]:
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 ]
return FoamRunner(command, args = args, **kwargs).run()
runner = FoamRunner(command, args = args, **kwargs)
if run:
runner.run()
return runner
###
# meshManipulation
##
def createPatch(dictfile: str = None, overwrite: bool = True, **kwargs) -> tuple[str, str, int]:
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)
@ -43,10 +53,15 @@ def createPatch(dictfile: str = None, overwrite: bool = True, **kwargs) -> tuple
if overwrite:
args.append("-overwrite")
return FoamRunner(command, args = args, **kwargs).run()
runner = FoamRunner(command, args = args, **kwargs)
if run:
runner.run()
return runner
def transformPoints(transformations: dict, **kwargs) -> tuple[str, str, int]:
def transformPoints(transformations: dict, run: bool = True, **kwargs) -> FoamRunner:
command = "transformPoints"
kwargs.update(logpath = kwargs.get("logpath", f"{ command }.log"))
kwargs.update(exit = True)
@ -64,10 +79,15 @@ def transformPoints(transformations: dict, **kwargs) -> tuple[str, str, int]:
args.append(", ".join(arg))
return FoamRunner(command, args = args, **kwargs).run()
runner = FoamRunner(command, args = args, **kwargs)
if run:
runner.run()
return runner
def checkMesh(allGeometry: bool = True, allTopology: bool = True, **kwargs) -> tuple[str, str, int]:
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)
@ -79,10 +99,15 @@ def checkMesh(allGeometry: bool = True, allTopology: bool = True, **kwargs) -> t
if allTopology:
args.append("-allTopology")
return FoamRunner(command, args = args, **kwargs).run()
runner = FoamRunner(command, args = args, **kwargs)
if run:
runner.run()
return runner
def renumberMesh(overwrite: bool = True, **kwargs) -> tuple[str, str, int]:
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)
@ -91,7 +116,12 @@ def renumberMesh(overwrite: bool = True, **kwargs) -> tuple[str, str, int]:
if overwrite:
args.append("-overwrite")
return FoamRunner(command, args = args, **kwargs).run()
runner = FoamRunner(command, args = args, **kwargs)
if run:
runner.run()
return runner
###
@ -105,20 +135,25 @@ def renumberMesh(overwrite: bool = True, **kwargs) -> tuple[str, str, int]:
# parallelProcessing
##
def decomposePar(**kwargs) -> tuple[str, str, int]:
def decomposePar(run: bool = True, **kwargs) -> FoamRunner:
command = "decomposePar"
kwargs.update(logpath = kwargs.get("logpath", f"{command}.log"))
kwargs.update(exit = True)
args = []
return FoamRunner(command, args = args, **kwargs).run()
runner = FoamRunner(command, args = args, **kwargs)
if run:
runner.run()
return runner
###
# solvers
##
def potentialFoam(parallel: bool = False, **kwargs) -> tuple[str, str, int]:
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)
@ -128,10 +163,15 @@ def potentialFoam(parallel: bool = False, **kwargs) -> tuple[str, str, int]:
args.append("-parallel")
kwargs.update(mpi = True)
return FoamRunner(command, args = args, **kwargs).run()
runner = FoamRunner(command, args = args, **kwargs)
if run:
runner.run()
return runner
def simpleFoam(parallel: bool = False, **kwargs) -> tuple[str, str, int]:
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)
@ -141,4 +181,32 @@ def simpleFoam(parallel: bool = False, **kwargs) -> tuple[str, str, int]:
args.append("-parallel")
kwargs.update(mpi = True)
return FoamRunner(command, args = args, **kwargs).run()
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

View File

@ -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

View File

@ -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);
}
}
// ************************************************************************* //

View File

@ -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;
}
}
// ************************************************************************* //

View File

@ -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;
// ************************************************************************* //

View File

@ -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;
// ************************************************************************* //

View File

@ -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;
}
// ************************************************************************* //

View File

@ -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;
}
}
// ************************************************************************* //

View File

@ -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);
}
);
// ************************************************************************* //

View File

@ -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);
}
// ************************************************************************* //

View File

@ -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;
}
// ************************************************************************* //

View File

@ -1,84 +0,0 @@
/*--------------------------------*- C++ -*----------------------------------*\
| ========= | |
| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
| \\ / O peration | Version: missed |
| \\ / A nd | |
| \\/ 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 yes;
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(p) /* empty */ ;
}
SIMPLE
{
nNonOrthogonalCorrectors 10;
residualControl
{
p 1e-05;
U 1e-05;
}
}
relaxationFactors
{
fields
{
p 0.3;
}
equations
{
U 0.5;
}
}
// ************************************************************************* //

View File

@ -4,6 +4,7 @@
import os
import shutil
from numpy import ndarray
#from .application import application
def version() -> str:
@ -30,7 +31,7 @@ def foamCleanCustom(case: str = None):
# application("foamCleanTutorials", useMPI = False, case = case, stderr = True)
def uniform(value) -> str:
if type(value) == list or type(value) == tuple:
if type(value) == list or type(value) == tuple or type(value) == ndarray:
return f"uniform ({ value[0] } { value[1] } { value[2] })"
elif type(value) == int or type(value) == float:
@ -38,3 +39,51 @@ def uniform(value) -> str:
else:
return ""
def datReader(filename: str):
header = []
content = []
with open(filename, "r") as io:
for line in io.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:
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))
return output

View File

@ -43,6 +43,14 @@ class Shape(object):
return out, err, returncode
def load(self, filename: str):
ext = os.path.splitext(filename)[1][1:]
if ext in ["step", "iges", "brep"]:
self.shape = OCCGeometry(filename).shape
else:
raise NotImplementedError(f"{ext} is not supported")
def normal(self, face: FACE) -> numpy.array:
"""

View File

@ -5,12 +5,13 @@
import anisotropy.openfoam.presets as F
import anisotropy.openfoam.runnerPresets as R
from anisotropy.openfoam import FoamCase, uniform
from numpy import array
import logging
logger = logging.getLogger(__name__)
class OnePhaseFlow(FoamCase):
def __init__(self, path: str = None):
def __init__(self, direction, path: str = None):
FoamCase.__init__(self, path = path)
controlDict = F.ControlDict()
@ -80,7 +81,7 @@ class OnePhaseFlow(FoamCase):
)
u["boundaryField"][boundary] = dict(
type = "fixedValue",
value = uniform([0, 0, -6e-5]) # * direction
value = uniform(array(direction) * -6e-5) # uniform([0, 0, -6e-5])
)
elif boundary == "outlet":
@ -111,6 +112,55 @@ class OnePhaseFlow(FoamCase):
u
])
@staticmethod
def facesToPatches(faces: tuple[int, str]):
# initial 43 unnamed patches ->
# 6 named patches (inlet, outlet, wall, symetry0 - 3/5) ->
# 4 inGroups (inlet, outlet, wall, symetry)
createPatchDict = F.CreatePatchDict()
createPatchDict["patches"] = []
patches = {}
for n, name in faces:
# shifted index
n += 1
if patches.get(name):
patches[name].append(f"patch{n}")
else:
patches[name] = [f"patch{n}"]
for name in 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"
createPatchDict["patches"].append({
"name": name,
"patchInfo": {
"type": patchType,
"inGroups": [patchGroup]
},
"constructFrom": "patches",
"patches": patches[name]
})
return createPatchDict
def build(self) -> tuple[str, str, int]:
# TODO: configure working directory (FoamCase)
with self:
@ -129,7 +179,7 @@ class OnePhaseFlow(FoamCase):
self.U["boundaryField"]["outlet"] = dict(
type = "pressureInletVelocity",
value = uniform([0, 0, 0]) # * direction
value = uniform([0, 0, 0])
)
self.write()