diff --git a/anisotropy/config/default.toml b/anisotropy/config/default.toml index faf2c01..f72c3b4 100644 --- a/anisotropy/config/default.toml +++ b/anisotropy/config/default.toml @@ -78,7 +78,7 @@ faceCentered = true fuseEdges = true checkChartBoundary = false - [structures.flowapprox] + [structures.flowapproximation] transportProperties.nu = 1e-6 pressure.boundaryField.inlet = { type = "fixedValue", value = 1e-3 } @@ -167,7 +167,7 @@ faceCentered = true fuseEdges = true checkChartBoundary = false - [structures.flowapprox] + [structures.flowapproximation] transportProperties.nu = 1e-6 pressure.boundaryField.inlet = { type = "fixedValue", value = 1e-3 } @@ -256,7 +256,7 @@ faceCentered = true fuseEdges = true checkChartBoundary = false - [structures.flowapprox] + [structures.flowapproximation] transportProperties.nu = 1e-6 pressure.boundaryField.inlet = { type = "fixedValue", value = 1e-3 } diff --git a/anisotropy/core/cli.py b/anisotropy/core/cli.py index 7db3ff2..2cc542e 100644 --- a/anisotropy/core/cli.py +++ b/anisotropy/core/cli.py @@ -4,7 +4,7 @@ import click import ast -import os, shutil +import os, sys, shutil import logging class LiteralOption(click.Option): @@ -192,7 +192,7 @@ def compute(stage, nprocs, force, update, params, path): logger.info(f"Stage: { stage }") if stage == "all" or stage == "mesh": - if not case.params.get("meshresult", {}).get("status") == "Done" or force: + if not case.params.get("meshresult", {}).get("meshStatus") == "Done" or force: (out, err, returncode), elapsed = timer(case.computeMesh)(path) if out: logger.info(out) @@ -201,7 +201,7 @@ def compute(stage, nprocs, force, update, params, path): case.load(type, direction, theta) if case.params.get("meshresult"): - case.params["meshresult"]["calculationTime"] = elapsed + case.params["meshresult"]["meshCalculationTime"] = elapsed case.update() if returncode: @@ -212,7 +212,7 @@ def compute(stage, nprocs, force, update, params, path): logger.info("Mesh exists. Skipping ...") if stage == "all" or stage == "flow": - if not case.params.get("flowresult", {}).get("status") == "Done" or force: + if not case.params.get("flowresult", {}).get("flowStatus") == "Done" or force: (out, err, returncode), elapsed = timer(case.computeFlow)(path) if out: logger.info(out) @@ -221,7 +221,7 @@ def compute(stage, nprocs, force, update, params, path): case.load(type, direction, theta) if case.params.get("flowresult"): - case.params["flowresult"]["calculationTime"] = elapsed + case.params["flowresult"]["flowCalculationTime"] = elapsed case.update() if returncode: @@ -281,7 +281,7 @@ def kill(path, pidfile): SalomeManager().killall() @anisotropy.command( - help = "! Not a user command" + help = "! Internal command" ) @click.argument("root") @click.argument("type") @@ -392,9 +392,118 @@ def postprocessing(path, plot): plt.show() +@anisotropy.command() +@click.option( + "-p", "--param", "params", + metavar = "key=value", + multiple = True, + cls = KeyValueOption, + help = "Select by control parameter (type, direction, theta)" +) +@click.option( + "-P", "--path", "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 query result to CSV." +) +@click.argument( + "fields", + required = False +) +def query(params, path, printlist, export, fields): + from anisotropy import env + from anisotropy.core.database import Database, Structure + from pandas import DataFrame + 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) + + fields = [ field.strip() for field in fields.split(",") ] if fields else [] + + ### + 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 + + + if fields: + for field in fields: + if field not in df_keys: + click.echo(f"Unknown field '{ field }'. Try to use '--list' flag to see all avaliable fields.") + + return + + df = df[fields] + + if export: + df.to_csv(export, sep = ";") + + else: + click.echo(df.to_string()) + + ### # CLI entry ## if __name__ == "__main__": - anisotropy() + 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) diff --git a/anisotropy/core/database.py b/anisotropy/core/database.py index e953121..018d427 100644 --- a/anisotropy/core/database.py +++ b/anisotropy/core/database.py @@ -29,8 +29,8 @@ def tryUntilDone(func): ret = func(*args, **kwargs) done = True - except OperationalError: - pass + except OperationalError as e: + logger.error(e) return ret @@ -110,10 +110,10 @@ class Database(object): if flowQuery.exists(): params["flow"] = flowQuery.dicts().get() - flowapproxQuery = flowQuery.get().flowapprox + flowapproximationQuery = flowQuery.get().flowapproximations - if flowapproxQuery.exists(): - params["flowapprox"] = flowapproxQuery.dicts().get() + if flowapproximationQuery.exists(): + params["flowapproximation"] = flowapproximationQuery.dicts().get() flowresultsQuery = flowQuery.get().flowresults @@ -173,22 +173,73 @@ class Database(object): ) ) - structureID = tryUntilDone(self._updateStructure)(params["structure"], query) + structureID = tryUntilDone(self._updateStructure)(params.get("structure", {}), query) - meshID = tryUntilDone(self._updateMesh)(params["mesh"], query, structureID) + 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["flow"], query, structureID) + flowID = tryUntilDone(self._updateFlow)(params.get("flow", {}), query, structureID) - tryUntilDone(self._updateFlowApproximation)(params.get("flowapprox", {}), query, flowID) + 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) diff --git a/anisotropy/core/main.py b/anisotropy/core/main.py index 20a11d8..49cc1d1 100644 --- a/anisotropy/core/main.py +++ b/anisotropy/core/main.py @@ -101,8 +101,7 @@ class Anisotropy(object): buf = toml.load(config).get("structures") paramsAll = [] - # TODO: custom config and merge - + for entry in buf: # Shortcuts _theta = entry["structure"]["theta"] @@ -133,12 +132,12 @@ class Anisotropy(object): "mesh": mesh, "submesh": deepcopy(entry["submesh"]), "flow": deepcopy(entry["flow"]), - "flowapprox": deepcopy(entry["flowapprox"]) + "flowapproximation": deepcopy(entry["flowapproximation"]) } # For `type = fixedValue` only - _velocity = entryNew["flowapprox"]["velocity"]["boundaryField"]["inlet"]["value"] - entryNew["flowapprox"]["velocity"]["boundaryField"]["inlet"]["value"] = [ + _velocity = entryNew["flowapproximation"]["velocity"]["boundaryField"]["inlet"]["value"] + entryNew["flowapproximation"]["velocity"]["boundaryField"]["inlet"]["value"] = [ val * _velocity for val in entryNew["structure"]["direction"] ] @@ -380,7 +379,7 @@ class Anisotropy(object): foamCase = [ "0", "constant", "system" ] flow = self.params["flow"] - flowapprox = self.params["flowapprox"] + flowapproximation = self.params["flowapproximation"] # ISSUE: ideasUnvToFoam cannot import mesh with '-case' flag so 'os.chdir' for that casePath = self.getCasePath() @@ -447,8 +446,8 @@ class Anisotropy(object): openfoam.renumberMesh() - pressureBF = flowapprox["pressure"]["boundaryField"] - velocityBF = flowapprox["velocity"]["boundaryField"] + pressureBF = flowapproximation["pressure"]["boundaryField"] + velocityBF = flowapproximation["velocity"]["boundaryField"] openfoam.foamDictionary( "0/p", diff --git a/anisotropy/core/models.py b/anisotropy/core/models.py index 6c2c631..9bd46c9 100644 --- a/anisotropy/core/models.py +++ b/anisotropy/core/models.py @@ -138,8 +138,9 @@ class MeshResult(BaseModel): prisms = IntegerField(null = True) pyramids = IntegerField(null = True) - status = TextField(null = True, default = "Idle") - calculationTime = TimeField(null = True) + meshStatus = TextField(null = True, default = "Idle") + meshCalculationTime = TimeField(null = True) + class Flow(BaseModel): flow_id = AutoField() @@ -153,17 +154,20 @@ class Flow(BaseModel): class FlowApproximation(BaseModel): flow_approximation_id = AutoField() - flow_id = ForeignKeyField(Flow, backref = "flowapprox") + 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) - status = TextField(null = True, default = "Idle") - calculationTime = TimeField(null = True) + flowStatus = TextField(null = True, default = "Idle") + flowCalculationTime = TimeField(null = True) diff --git a/docs/=source/static/er-diagram.png b/docs/=source/static/er-diagram.png deleted file mode 100644 index b748659..0000000 Binary files a/docs/=source/static/er-diagram.png and /dev/null differ diff --git a/docs/source/notes/database.rst b/docs/source/notes/database.rst index aea9d16..159d96e 100644 --- a/docs/source/notes/database.rst +++ b/docs/source/notes/database.rst @@ -5,3 +5,21 @@ Current anisotropy database hierarchy: .. figure:: ../static/er-diagram.png :align: center + +peewee migration +---------------- + +Example of Sqlite database migration and etc: + +.. code-block:: python + + from playhouse.migrate import SqliteMigrator, migrate + from peewee import SqliteDatabase, FloatField + + db = SqliteDatabase("anisotropy.db") + migrator = SqliteDatabase(db) + + migrate( + migrator.rename_column("MeshResult", "status", "meshStatus"), + migrator.add_column("FlowResult", "porosity", FloatField(null = True)) + )