Mod: cli in dev

Mod: some new tests
Mod: some new db models in dev for flow
Mod: default configuration file (flow)
Move: merge genmesh with cli
This commit is contained in:
L-Nafaryus 2021-08-09 18:04:49 +05:00
parent 513fd1be52
commit 85383f4cbc
7 changed files with 422 additions and 194 deletions

View File

@ -1,20 +1,85 @@
import click
from anisotropy.anisotropy import Anisotropy
pass_anisotropy = click.make_pass_decorator(Anisotropy)
#pass_anisotropy = click.make_pass_decorator(Anisotropy)
def version():
msg = "Missed package anisotropy"
try:
from anisotropy import Anisotropy
msg = Anisotropy.version()
except ImportError:
pass
return msg
@click.group()
@click.version_option(version = "", message = Anisotropy.version())
@click.pass_context
def anisotropy(ctx):
ctx.obj = Anisotropy()
@click.version_option(version = "", message = version())
def anisotropy():
pass
@anisotropy.command()
@click.option("-s", "--stage", "stage", type = click.Choice(["all", "mesh", "flow"]), default = "all")
@click.option("-p", "--param", "params", metavar = "key=value", multiple = True)
@pass_anisotropy
def compute(anisotropy, stage, params):
pass
def compute(stage, params):
from anisotropy import Anisotropy
model = Anisotropy()
model.setupDB()
if model.isEmptyDB():
paramsAll = model.loadFromScratch()
for entry in paramsAll:
model.updateDB(entry)
model.loadDB(type, direction, theta)
# TODO: merge cli params with db params here
model.evalParams()
model.updateDB()
# TODO: do smth with output
if stage == "all" or stage == "mesh":
((out, err, code), elapsed) = model.computeMesh(type, direction, theta)
if stage == "all" or stage == "flow":
((out, err, code), elapsed) = model.computeFlow(type, direction, theta)
@anisotropy.command()
@click.argument("root")
@click.argument("name")
@click.argument("direction")
@click.argument("theta", type = click.FLOAT)
def _compute_mesh(root, name, direction, theta):
# [Salome Environment]
###
# Args
##
direction = list(map(lambda num: float(num), direction[1:-1].split(",")))
###
# Modules
##
import salome
sys.path.extend([
root,
os.path.join(root, "env/lib/python3.9/site-packages")
])
from anisotropy import Anisotropy
###
model = Anisotropy()
model.setupDB()
model.loadDB(type, direction, theta)
model.genmesh()
###
# CLI entry
##
anisotropy()

View File

@ -54,7 +54,7 @@ env.update(dict(
CONFIG = os.path.join(env["ROOT"], "conf/config.toml")
))
env["db_path"] = env["BUILD"]
env["salome_port"] = 2810
#if os.path.exists(env["CONFIG"]):
# config = toml.load(env["CONFIG"])
@ -157,7 +157,7 @@ class Anisotropy(object):
return "\n".join([ f"{ k }: { v }" for k, v in versions.items() ])
def loadScratch(self):
def loadFromScratch(self):
if not os.path.exists(self.env["DEFAULT_CONFIG"]):
logger.error("Missed default configuration file")
return
@ -200,6 +200,7 @@ class Anisotropy(object):
paramsAll.append(entryNew)
return paramsAll
self.setupDB()
for entry in paramsAll:
@ -499,14 +500,260 @@ class Anisotropy(object):
self.params = sorted(self.params, key = lambda entry: f"{ entry['name'] } { entry['geometry']['direction'] } { entry['geometry']['theta'] }")
@timer
def computeMesh(self, name, direction, theta):
scriptpath = os.path.join(self.env["ROOT"], "anisotropy/genmesh.py")
def computeMesh(self, type, direction, theta):
scriptpath = os.path.join(self.env["ROOT"], "anisotropy/__main__.py")
port = 2900
return salomepl.utils.runSalome(port, scriptpath, self.env["ROOT"], name, direction, theta)
return salomepl.utils.runSalome(port, scriptpath, self.env["ROOT"], "_compute_mesh", type, direction, theta)
def genmesh(self):
import salome
p = self.params
logger.info("\n".join([
"genmesh:",
f"structure type:\t{ p['structure']['type'] }",
f"coefficient:\t{ p['structure']['theta'] }",
f"fillet:\t{ p['structure']['fillets'] }",
f"flow direction:\t{ p['structure']['direction'] }"
]))
salome.salome_init()
###
# Shape
##
geompy = salomepl.geometry.getGeom()
structure = dict(
simple = Simple,
bodyCentered = BodyCentered,
faceCentered = FaceCentered
)[p["structure"]["type"]]
shape, groups = structure(**p["structure"]).build()
[length, surfaceArea, volume] = geompy.BasicProperties(shape, theTolerance = 1e-06)
logger.info("\n".join([
"shape:",
f"edges length:\t{ length }",
f"surface area:\t{ surfaceArea }",
f"volume:\t{ volume }"
]))
###
# 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)
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)
model.updateDB()
returncode, errors = mesh.compute()
if not returncode:
mesh.removePyramids()
mesh.assignGroups()
casePath = model.getCasePath()
os.makedirs(casePath, exist_ok = True)
mesh.exportUNV(os.path.join(casePath, "mesh.unv"))
meshStats = mesh.stats()
p["meshresults"] = dict(
surfaceArea = surfaceArea,
volume = volume,
**meshStats
)
model.updateDB()
logger.info("mesh stats:\n{}".format(
"\n".join(map(lambda v: f"{ v[0] }:\t{ v[1] }", meshStats.items()))
))
else:
logger.error(errors)
p["meshresults"] = dict(
surfaceArea = surfaceArea,
volume = volume
)
model.updateDB()
salome.salome_close()
@timer
def computeFlow(self, type, direction, theta):
###
# Case preparation
##
foamCase = [ "0", "constant", "system" ]
# ISSUE: ideasUnvToFoam cannot import mesh with '-case' flag so 'os.chdir' for that
os.chdir(self.getCasePath())
openfoam.foamClean()
for d in foamCase:
shutil.copytree(
os.path.join(ROOT, "openfoam/template", d),
os.path.join(case, d)
)
###
# Mesh manipulations
##
if not os.path.exists("mesh.unv"):
logger.error(f"missed 'mesh.unv'")
os.chdir(self.env["ROOT"])
return 1
_, returncode = openfoam.ideasUnvToFoam("mesh.unv")
if returncode:
os.chdir(self.env["ROOT"])
return 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 = openfoam.checkMesh()
if out:
logger.info(out)
# TODO: replace all task variables
openfoam.transformPoints(task.flow.scale)
###
# Decomposition and initial approximation
##
openfoam.foamDictionary(
"constant/transportProperties",
"nu",
str(task.flow.constant.nu)
)
openfoam.decomposePar()
openfoam.renumberMesh()
pressureBF = task.flow.approx.pressure.boundaryField
velocityBF = task.flow.approx.velocity.boundaryField
direction = {
"[1, 0, 0]": 0,
"[0, 0, 1]": 1,
"[1, 1, 1]": 2
}[str(task.geometry.direction)]
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[direction])
)
openfoam.potentialFoam()
###
# Main computation
##
pressureBF = task.flow.main.pressure.boundaryField
velocityBF = task.flow.main.velocity.boundaryField
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])
)
returncode, out = openfoam.simpleFoam()
if out:
logger.info(out)
###
# Check results
##
elapsed = time.monotonic() - stime
logger.info("computeFlow: elapsed time: {}".format(timedelta(seconds = elapsed)))
if returncode == 0:
task.status.flow = True
task.statistics.flowTime = elapsed
postProcessing = "postProcessing/flowRatePatch(name=outlet)/0/surfaceFieldValue.dat"
with open(postProcessing, "r") as io:
lastLine = io.readlines()[-1]
flowRate = float(lastLine.replace(" ", "").replace("\n", "").split("\t")[1])
task.statistics.flowRate = flowRate
with open(os.path.join(case, "task.toml"), "w") as io:
toml.dump(dict(task), io)
os.chdir(ROOT)
return returncode
def computeFlow(self):
pass
def _queue(self):
pass

View File

@ -74,6 +74,24 @@ faceCentered = true
fuseEdges = true
checkChartBoundary = false
[flow]
scale = [ 1e-5, 1e-5, 1e-5 ]
constant.nu = 1e-6
approx.pressure.boundaryField.inlet = { type = "fixedValue", value = 1e-3 }
approx.pressure.boundaryField.outlet = { type = "fixedValue", value = 0 }
# multiplication velocity value with direction vector
approx.velocity.boundaryField.inlet = { type = "fixedValue", value = 6e-5 }
approx.velocity.boundaryField.outlet = { type = "zeroGradient", value = "None" }
pressure.boundaryField.inlet = { type = "fixedValue", value = 1e-3 }
pressure.boundaryField.outlet = { type = "fixedValue", value = 0 }
velocity.boundaryField.inlet = { type = "fixedValue", value = 0.0 }
velocity.boundaryField.outlet = { type = "zeroGradient", value = "None" }
[[structures]]
[structures.structure]
type = "bodyCentered"
@ -141,6 +159,24 @@ faceCentered = true
fuseEdges = true
checkChartBoundary = false
[flow]
scale = [ 1e-5, 1e-5, 1e-5 ]
constant.nu = 1e-6
approx.pressure.boundaryField.inlet = { type = "fixedValue", value = 1e-3 }
approx.pressure.boundaryField.outlet = { type = "fixedValue", value = 0 }
# multiplication velocity value with direction vector
approx.velocity.boundaryField.inlet = { type = "fixedValue", value = 6e-5 }
approx.velocity.boundaryField.outlet = { type = "zeroGradient", value = "None" }
pressure.boundaryField.inlet = { type = "fixedValue", value = 1e-3 }
pressure.boundaryField.outlet = { type = "fixedValue", value = 0 }
velocity.boundaryField.inlet = { type = "fixedValue", value = 0.0 }
velocity.boundaryField.outlet = { type = "zeroGradient", value = "None" }
[[structures]]
[structures.structure]
type = "faceCentered"
@ -208,4 +244,21 @@ faceCentered = true
fuseEdges = true
checkChartBoundary = false
[flow]
scale = [ 1e-5, 1e-5, 1e-5 ]
constant.nu = 1e-6
approx.pressure.boundaryField.inlet = { type = "fixedValue", value = 1e-3 }
approx.pressure.boundaryField.outlet = { type = "fixedValue", value = 0 }
# multiplication velocity value with direction vector
approx.velocity.boundaryField.inlet = { type = "fixedValue", value = 6e-5 }
approx.velocity.boundaryField.outlet = { type = "zeroGradient", value = "None" }
pressure.boundaryField.inlet = { type = "fixedValue", value = 1e-3 }
pressure.boundaryField.outlet = { type = "fixedValue", value = 0 }
velocity.boundaryField.inlet = { type = "fixedValue", value = 0.0 }
velocity.boundaryField.outlet = { type = "zeroGradient", value = "None" }

View File

@ -1,164 +0,0 @@
###
# This file executes inside salome environment
#
# salome starts at user home directory
##
import os, sys
import math
import logging
import salome
import click
@click.command()
@click.argument("root")
@click.argument("name")
@click.argument("direction")
@click.argument("theta", type = click.FLOAT)
def genmesh(root, name, direction, theta):
print(root)
print(name)
print(direction)
print(theta)
###
# Args
##
direction = list(map(lambda num: float(num), direction[1:-1].split(",")))
###
# Modules
##
sys.path.extend([
root,
os.path.join(root, "env/lib/python3.9/site-packages")
])
from anisotropy import (
Anisotropy,
logger,
Simple,
BodyCentered,
FaceCentered
)
import salomepl
###
# Model
##
model = Anisotropy()
model.setupDB()
model.loadDB(name, direction, theta)
model.evalParams()
p = model.params
###
# Entry
##
logger.info("\n".join([
"genmesh:",
f"structure type:\t{ p['structure']['type'] }",
f"coefficient:\t{ p['structure']['theta'] }",
f"fillet:\t{ p['structure']['fillets'] }",
f"flow direction:\t{ p['structure']['direction'] }"
]))
salome.salome_init()
###
# Shape
##
geompy = salomepl.geometry.getGeom()
structure = dict(
simple = Simple,
bodyCentered = BodyCentered,
faceCentered = FaceCentered
)[p["structure"]["type"]]
shape, groups = structure(**p["structure"]).build()
[length, surfaceArea, volume] = geompy.BasicProperties(shape, theTolerance = 1e-06)
logger.info("\n".join([
"shape:",
f"edges length:\t{ length }",
f"surface area:\t{ surfaceArea }",
f"volume:\t{ volume }"
]))
###
# 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)
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)
model.updateDB()
returncode, errors = mesh.compute()
if not returncode:
# TODO: MeshResult
pass
else:
logger.error(errors)
mesh.removePyramids()
mesh.assignGroups()
casePath = model.getCasePath()
os.makedirs(casePath, exist_ok = True)
mesh.exportUNV(os.path.join(casePath, "mesh.unv"))
meshStats = mesh.stats()
p["meshresults"] = dict(
#mesh_id = p["mesh"]["mesh_id"],
surfaceArea = surfaceArea,
volume = volume,
**meshStats
)
model.updateDB()
logger.info("mesh stats:\n{}".format(
"\n".join(map(lambda v: f"{ v[0] }:\t{ v[1] }", meshStats.items()))
))
salome.salome_close()
genmesh()

View File

@ -118,3 +118,9 @@ class MeshResult(BaseModel):
calculationTime = TimeField(null = True)
class Flow(BaseModel):
# TODO: flow model
pass
class FlowResults(BaseModel):
pass

View File

@ -7,7 +7,7 @@ try:
from salome.smesh import smeshBuilder
except ImportError:
logger.warning("[Warning] Trying to get SALOME mesh modules outside SALOME environment. Modules won't be imported.")
logger.warning("Trying to get SALOME mesh modules outside SALOME environment. Modules won't be imported.")
if globals().get("smeshBuilder"):
smesh = smeshBuilder.New()

View File

@ -1,17 +1,38 @@
import os
import unittest
class TestAnisotropy:
def test_import(self):
unittest.TestLoader.sortTestMethodsUsing = None
class TestAnisotropy(unittest.TestCase):
def setUp(self):
import anisotropy
self.model = anisotropy.Anisotropy()
def test_db(self):
import anisotropy
a = anisotropy.Anisotropy()
a.setupDB()
a.evalEnvParameters()
a.updateDB()
if os.path.exists("build/anisotropy.db"):
os.remove("build/anisotropy.db")
def test_01_create_db(self):
self.model.setupDB()
path = os.path.join(self.model.env["db_path"], "anisotropy.db")
self.assertTrue(os.path.exists(path))
def test_02_load_scratch(self):
passed = True
try:
self.model.loadScratch()
except Exception as e:
passed = False
self.assertTrue(passed)
def test_03_load_db(self):
self.model.setupDB()
self.model.loadDB("simple", [1.0, 0.0, 0.0], 0.01)
self.assertEqual(self.model.params["structure"]["type"], "simple")
def test_04_updateDB(self):
self.model.updateDB()
if __name__ == "__main__":
unittest.main()