From 85383f4cbc8014f2286bd442cb53a50ff7eb1a17 Mon Sep 17 00:00:00 2001 From: L-Nafaryus Date: Mon, 9 Aug 2021 18:04:49 +0500 Subject: [PATCH] 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 --- anisotropy/__main__.py | 85 +++++++++++-- anisotropy/core.py | 261 +++++++++++++++++++++++++++++++++++++-- anisotropy/default.toml | 53 ++++++++ anisotropy/genmesh.py | 164 ------------------------ anisotropy/models.py | 6 + salomepl/mesh.py | 2 +- tests/test_anisotropy.py | 45 +++++-- 7 files changed, 422 insertions(+), 194 deletions(-) delete mode 100644 anisotropy/genmesh.py diff --git a/anisotropy/__main__.py b/anisotropy/__main__.py index 925e065..db9e169 100644 --- a/anisotropy/__main__.py +++ b/anisotropy/__main__.py @@ -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() diff --git a/anisotropy/core.py b/anisotropy/core.py index 9ef632d..573752d 100644 --- a/anisotropy/core.py +++ b/anisotropy/core.py @@ -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 diff --git a/anisotropy/default.toml b/anisotropy/default.toml index bf87684..ba54327 100644 --- a/anisotropy/default.toml +++ b/anisotropy/default.toml @@ -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" } diff --git a/anisotropy/genmesh.py b/anisotropy/genmesh.py deleted file mode 100644 index 737ae39..0000000 --- a/anisotropy/genmesh.py +++ /dev/null @@ -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() diff --git a/anisotropy/models.py b/anisotropy/models.py index cf84899..d23db07 100644 --- a/anisotropy/models.py +++ b/anisotropy/models.py @@ -118,3 +118,9 @@ class MeshResult(BaseModel): calculationTime = TimeField(null = True) +class Flow(BaseModel): + # TODO: flow model + pass + +class FlowResults(BaseModel): + pass diff --git a/salomepl/mesh.py b/salomepl/mesh.py index edb7979..8af8314 100644 --- a/salomepl/mesh.py +++ b/salomepl/mesh.py @@ -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() diff --git a/tests/test_anisotropy.py b/tests/test_anisotropy.py index aadd959..76928cd 100644 --- a/tests/test_anisotropy.py +++ b/tests/test_anisotropy.py @@ -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()